package semorg.gui.util;

import java.sql.SQLException;
import java.util.Vector;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

import semorg.gui.ClientWindow;
import semorg.gui.CompanyInternalPresentationWindow;
import semorg.gui.CompanyWindow;
import semorg.gui.MainWindow;
import semorg.gui.PublicPresentationWindow;
import semorg.gui.SeminarTypeWindow;
import semorg.gui.list.ClientListWindow;
import semorg.gui.list.CompanyInternalPresentationListWindow;
import semorg.gui.list.CompanyListWindow;
import semorg.gui.list.PublicPresentationListWindow;
import semorg.gui.list.SeminarTypeListWindow;
import semorg.sql.tables.AbstractTable;
import semorg.sql.tables.Client;
import semorg.sql.tables.Company;
import semorg.sql.tables.CompanyInternalPresentation;
import semorg.sql.tables.PublicPresentation;
import semorg.sql.tables.SeminarType;
import semorg.sql.tables.SimpleIDKey;
import semorg.sql.util.DBConstraint;
import semorg.sql.util.DistinctVector;
import semorg.sql.util.Utility;

/**
 * This class provides a SWT composite which allows the user to manage several
 * associations.
 */
public class ForeignKeyConstraintSelector extends Composite {

    /**
     * Instances from this type allow to manage the association to a
     * {@link Company} object.
     */
    public final static int FK_COMPANY = 1;

    /**
     * Instances from this type allow to manage the association to a
     * {@link Client} object.
     */
    public final static int FK_CLIENT = 2;

    /**
     * Instances from this type allow to manage the association to a
     * {@link SeminarType} object.
     */
    public final static int FK_SEMINARTYPE = 3;

    /**
     * Instances from this type allow to manage the association to a
     * {@link CompanyInternalPresentation} object.
     */
    public final static int FK_COMPANYINTERNALPRESENTATION = 4;

    /**
     * Instances from this type allow to manage the association to a
     * {@link PublicPresentation} object.
     */
    public final static int FK_PUBLICPRESENTATION = 5;

    /** A text field to display some information about the associated object. */
    private final Text selectedValueText;

    /**
     * SWT ToolBar widget which contains some Buttons to manage the association
     * to a object.
     */
    private final ToolBar toolBar;

    /** Button to show the available objects for associating. */
    private final ToolItem showListItem;

    /** Button to disconnect the current item and the yet associated item. */
    private final ToolItem disconnectItem;

    /** Button to create a new item which gets associated to the current item. */
    private final ToolItem newItem;

    /** Button to delete the associated object. */
    private final ToolItem deleteItem;

    /** The current type of this control. */
    private final int type;

    /** The id of the associated object. */
    private int selectedId = AbstractTable.NULL_ID;

    /** <tt>true</tt> if the control is enabled, <tt>false</tt> otherwise. */
    private boolean controlEnabled;

    /** Creates an instance of this class using the given parameters. */
    public ForeignKeyConstraintSelector(final Composite parent, int type) {
	super(parent, SWT.NULL);
	this.type = type;
	this.controlEnabled = true;
	RowLayout mainLayout = new RowLayout();
	mainLayout.marginLeft = 0;
	mainLayout.marginRight = 0;
	mainLayout.marginTop = 0;
	mainLayout.marginBottom = 0;
	this.setLayout(mainLayout);
	selectedValueText = new Text(this, SWT.SINGLE | SWT.BORDER);
	selectedValueText.setBackground(getDisplay().getSystemColor(
		SWT.COLOR_GRAY));
	selectedValueText.setEditable(false);
	RowData valueTextrowData = new RowData();
	valueTextrowData.width = 130;
	selectedValueText.setLayoutData(valueTextrowData);
	toolBar = new ToolBar(this, SWT.FLAT);

	newItem = new ToolItem(toolBar, SWT.PUSH);
	newItem.setImage(Utility.getImage("new.png"));
	newItem.setToolTipText(Messages
		.getString("GUIText.AddNewItemButtonToolTip"));
	newItem.setEnabled(true);
	newItem.addSelectionListener(new SelectionAdapter() {
	    public void widgetSelected(SelectionEvent e) {
		switch (ForeignKeyConstraintSelector.this.type) {
		case FK_COMPANY:
		    new CompanyWindow(ForeignKeyConstraintSelector.this
			    .getShell(), null,
			    ForeignKeyConstraintSelector.this);
		    break;
		case FK_CLIENT:
		    new ClientWindow(ForeignKeyConstraintSelector.this
			    .getShell(), null,
			    ForeignKeyConstraintSelector.this);
		    break;
		case FK_SEMINARTYPE:
		    new SeminarTypeWindow(ForeignKeyConstraintSelector.this
			    .getShell(), null,
			    ForeignKeyConstraintSelector.this);
		    break;
		case FK_COMPANYINTERNALPRESENTATION:
		    new CompanyInternalPresentationWindow(
			    ForeignKeyConstraintSelector.this.getShell(), null,
			    ForeignKeyConstraintSelector.this);
		    break;
		case FK_PUBLICPRESENTATION:
		    new PublicPresentationWindow(
			    ForeignKeyConstraintSelector.this.getShell(), null,
			    ForeignKeyConstraintSelector.this);
		    break;
		}
	    }
	});

	showListItem = new ToolItem(toolBar, SWT.PUSH);
	showListItem.setImage(Utility.getImage("choose.png"));
	showListItem.setToolTipText(Messages
		.getString("GUIText.ChooseItemButtonToolTip"));
	showListItem.addSelectionListener(new SelectionAdapter() {
	    public void widgetSelected(SelectionEvent e) {
		switch (ForeignKeyConstraintSelector.this.type) {
		case FK_COMPANY:
		    new CompanyListWindow(ForeignKeyConstraintSelector.this);
		    break;
		case FK_CLIENT:
		    new ClientListWindow(ForeignKeyConstraintSelector.this);
		    break;
		case FK_SEMINARTYPE:
		    new SeminarTypeListWindow(ForeignKeyConstraintSelector.this);
		    break;
		case FK_COMPANYINTERNALPRESENTATION:
		    new CompanyInternalPresentationListWindow(
			    ForeignKeyConstraintSelector.this);
		    break;
		case FK_PUBLICPRESENTATION:
		    new PublicPresentationListWindow(
			    ForeignKeyConstraintSelector.this);
		    break;
		}
	    }

	});

	disconnectItem = new ToolItem(toolBar, SWT.PUSH);
	disconnectItem.setImage(Utility.getImage("disconnect.png"));
	disconnectItem.setToolTipText(Messages
		.getString("GUIText.DisconnectItemButtonToolTip"));
	disconnectItem.addSelectionListener(new SelectionAdapter() {
	    public void widgetSelected(SelectionEvent e) {
		setSelectedId(AbstractTable.NULL_ID);
	    }
	});

	deleteItem = new ToolItem(toolBar, SWT.PUSH);
	deleteItem.setImage(Utility.getImage("delete.png"));
	deleteItem.setToolTipText(Messages
		.getString("GUIText.DeleteItemButtonToolTip"));
	deleteItem.setEnabled(false);
	deleteItem.addSelectionListener(new SelectionAdapter() {
	    public void widgetSelected(SelectionEvent e) {
		onDelete();
	    }
	});

	setSelectedId(AbstractTable.NULL_ID);
    }

    /**
     * Updates the text shown in the {@link #selectedValueText} widget.
     */
    private void updateText() {
	if (selectedId == AbstractTable.NULL_ID)
	    selectedValueText.setText("");
	else {
	    Vector<DBConstraint> keyConstraint = new Vector<DBConstraint>();
	    try {
		switch (type) {
		case FK_COMPANY:
		    keyConstraint.add(new DBConstraint(Company.getColumns()
			    .get(0).getInternalColumnName(),
			    DBConstraint.REL_EQ, new Integer(selectedId),
			    DBConstraint.CONJ_END));
		    DistinctVector<Company> companies = Company
			    .getVectorFromDB(keyConstraint, null);
		    Company company = companies.iterator().next();
		    selectedValueText.setText(company.getShortName());
		    break;
		case FK_CLIENT:
		    keyConstraint.add(new DBConstraint(Client.getColumns().get(
			    0).getInternalColumnName(), DBConstraint.REL_EQ,
			    new Integer(selectedId), DBConstraint.CONJ_END));
		    DistinctVector<Client> clients = Client.getVectorFromDB(
			    keyConstraint, null);
		    Client client = clients.iterator().next();
		    selectedValueText.setText(client.getName() + ", "
			    + client.getFirstname());
		    break;
		case FK_SEMINARTYPE:
		    keyConstraint.add(new DBConstraint(SeminarType.getColumns()
			    .get(0).getInternalColumnName(),
			    DBConstraint.REL_EQ, new Integer(selectedId),
			    DBConstraint.CONJ_END));
		    DistinctVector<SeminarType> seminarTypes = SeminarType
			    .getVectorFromDB(keyConstraint, null);
		    SeminarType seminarType = seminarTypes.iterator().next();
		    selectedValueText.setText(seminarType.getShortTitle());
		    break;
		case FK_COMPANYINTERNALPRESENTATION:
		    keyConstraint.add(new DBConstraint(
			    CompanyInternalPresentation.getColumns().get(0)
				    .getInternalColumnName(),
			    DBConstraint.REL_EQ, new Integer(selectedId),
			    DBConstraint.CONJ_END));
		    DistinctVector<CompanyInternalPresentation> companyInternalPresentations = CompanyInternalPresentation
			    .getVectorFromDB(keyConstraint, null);
		    CompanyInternalPresentation companyInternalPresentation = companyInternalPresentations
			    .iterator().next();
		    selectedValueText.setText(companyInternalPresentation
			    .getShortDescription());
		    break;
		case FK_PUBLICPRESENTATION:
		    keyConstraint.add(new DBConstraint(PublicPresentation
			    .getColumns().get(0).getInternalColumnName(),
			    DBConstraint.REL_EQ, new Integer(selectedId),
			    DBConstraint.CONJ_END));
		    DistinctVector<PublicPresentation> publicPresentations = PublicPresentation
			    .getVectorFromDB(keyConstraint, null);
		    PublicPresentation publicPresentation = publicPresentations
			    .iterator().next();
		    selectedValueText.setText(publicPresentation
			    .getShortDescription());
		    break;
		}
	    } catch (Exception e) {
		selectedId = AbstractTable.NULL_ID;
		selectedValueText.setText("");
	    }
	}
    }

    /**
     * Sets the {@link #selectedId} to the given id and enables the items
     * {@link #disconnectItem} and {@link #deleteItem} if necessary.
     * 
     * @param selectedId
     *                the id of the new associated object.
     */
    public void setSelectedId(int selectedId) {
	this.selectedId = selectedId;
	disconnectItem.setEnabled(controlEnabled
		&& selectedId != AbstractTable.NULL_ID);

	deleteItem.setEnabled(controlEnabled
		&& selectedId != AbstractTable.NULL_ID);

	updateText();
    }

    /**
     * Adds a {@link ModifyListener} to the text field
     * {@link #selectedValueText}.
     * 
     * @param listener
     *                the listener to add.
     */
    public void addModifyListener(ModifyListener listener) {
	selectedValueText.addModifyListener(listener);
    }

    /**
     * Enables or disables the {@link #disconnectItem}, {@link #deleteItem} and
     * {@link #showListItem}.<br>
     * <br>
     * The member {@link #controlEnabled} is also set to the given value. If the
     * items got enabled depends also on the fact, whether there is a object
     * associated or not.
     * 
     * @param enabled
     *                if <tt>true</tt> the widgets get enabled, if
     *                <tt>false</tt> disabled.
     */
    public void setEnabled(boolean enabled) {
	this.controlEnabled = enabled;
	disconnectItem.setEnabled(controlEnabled
		&& selectedId != AbstractTable.NULL_ID);

	deleteItem.setEnabled(controlEnabled
		&& selectedId != AbstractTable.NULL_ID);

	showListItem.setEnabled(controlEnabled);
    }

    /**
     * Sets a pair of key and value to this control needed for testing with
     * ATOSj.
     * 
     * @param key
     *                the key (type) of the value set
     * @param value
     *                the value set to the control
     */
    public void setData(String key, String value) {
	super.setData(key, value);
	if (key.equals(Utility.ATOSJ_COMPONENT_NAME_KEY)) {
	    showListItem.setData(key, value + "_opener");
	    disconnectItem.setData(key, value + "_disconnector");
	    deleteItem.setData(key, value + "_delete");
	    newItem.setData(key, value + "_new");
	}
    }

    /**
     * Checks whether the given id is locked,i.e. the object to the id is edited
     * elsewhere, or not.
     * 
     * @param id
     *                the id to check.
     * @return <tt>true</tt> if the id is locked, otherwise <tt>false</tt>.
     */
    private boolean isLockedId(int id) {
	switch (this.type) {
	case FK_COMPANY:
	    return Company.lockedIds.contains(new Integer(id));
	case FK_CLIENT:
	    return Client.lockedIds.contains(new Integer(id));
	case FK_SEMINARTYPE:
	    return SeminarType.lockedIds.contains(new Integer(id));
	case FK_COMPANYINTERNALPRESENTATION:
	    return CompanyInternalPresentation.lockedIds.contains(new Integer(
		    id));
	case FK_PUBLICPRESENTATION:
	    return PublicPresentation.lockedIds.contains(new Integer(id));
	}
	return true; // should never reached
    }

    /**
     * This method implements the functionality of the deletion button, i.e. it
     * deletes the selected element if it isn't locked. <br>
     * <br>
     */
    private void onDelete() {

	if (selectedId != AbstractTable.NULL_ID) {
	    String question = determineDeleteMessage();
	    MessageDialog dlg = new MessageDialog(
		    MainWindow.getInstance().getShell(),
		    Messages.getString("GUIText.DeleteItemMessageDialogTitle"),
		    null,
		    question,
		    MessageDialog.QUESTION,
		    new String[] {
			    Messages
				    .getString("GUIText.DeleteItemMessageDialogYesButtonText"),
			    Messages
				    .getString("GUIText.DeleteItemMessageDialogNoButtonText") },
		    0);

	    if (dlg.open() != 0) { // no was selected or dialog closed
		return;
	    }

	    if (isLockedId(selectedId)) {
		MessageDialog.openInformation(MainWindow.getInstance()
			.getShell(), Messages
			.getString("GUIText.DeleteErrorDialogTitle"), Messages
			.getString("GUIText.DeleteErrorDialogSingularText"));
		return;
	    }

	    try {
		if (deleteFromDB()) {
		    setSelectedId(AbstractTable.NULL_ID);
		    updateText();
		}
	    } catch (SQLException e) {
		e.printStackTrace();
	    }
	}
    }

    /**
     * This method determines the right deletion dialog hint text against the
     * type of the foreign key of the data record managed by this control.
     * 
     * @return String containing the right deletion dialog hint text.
     */
    private String determineDeleteMessage() {
	String question = "";

	switch (type) {
	case FK_COMPANY:
	    question = Messages
		    .getString("GUIText.DeleteCompanyItemMessageDialogText");
	    break;
	case FK_CLIENT:
	    question = Messages
		    .getString("GUIText.DeleteClientItemMessageDialogText");
	    break;
	case FK_SEMINARTYPE:
	    question = Messages
		    .getString("GUIText.DeleteSeminarTypeItemMessageDialogText");
	    break;
	case FK_COMPANYINTERNALPRESENTATION:
	    question = Messages
		    .getString("GUIText.DeleteCompanyInternalPresentationItemMessageDialogText");
	    break;
	case FK_PUBLICPRESENTATION:
	    question = Messages
		    .getString("GUIText.DeletePublicPresentationItemMessageDialogText");
	    break;
	}

	return question;
    }

    /**
     * Deletes the record corresponding to {@link #selectedId} (the associated
     * object) from the database.
     * 
     * @return <tt>true</tt> if the deletion was successful, <tt>false</tt>
     *         otherwise.
     */
    private boolean deleteFromDB() throws SQLException {
	DistinctVector<SimpleIDKey> idToDelete = new DistinctVector<SimpleIDKey>();

	switch (this.type) {
	case FK_COMPANY:
	    idToDelete.add(Company.getCompany(selectedId));
	    Company.removeFromDB(idToDelete);
	    return true;
	case FK_CLIENT:
	    idToDelete.add(Client.getClient(selectedId));
	    Client.removeFromDB(idToDelete);
	    return true;
	case FK_SEMINARTYPE:
	    idToDelete.add(SeminarType.getSeminarTypeFromDB(selectedId));
	    SeminarType.removeFromDB(idToDelete);
	    return true;
	case FK_COMPANYINTERNALPRESENTATION:
	    idToDelete.add(CompanyInternalPresentation
		    .getCompanyInternalPresentation(selectedId));
	    CompanyInternalPresentation.removeFromDB(idToDelete);
	    return true;
	case FK_PUBLICPRESENTATION:
	    idToDelete.add(PublicPresentation.getPresentation(selectedId));
	    PublicPresentation.removeFromDB(idToDelete);
	    return true;
	}

	return false;
    }

    public int getSelectedId() {
	return selectedId;
    }

}
