package semorg.gui.list;

import java.util.Vector;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

import semorg.gui.util.AssociationTabControl;
import semorg.gui.util.ConfigureListDlg;
import semorg.gui.util.DBTableChangedListener;
import semorg.gui.util.ExtensibleSearchControl;
import semorg.gui.util.ForeignKeyConstraintSelector;
import semorg.gui.util.Messages;
import semorg.gui.util.TableColumnProperty;
import semorg.sql.tables.AbstractTable;
import semorg.sql.util.DBColumn;
import semorg.sql.util.Utility;

/**
 * Super class for all list window classes. Encapsulates all properties and
 * methods, which have all list window classes in common.
 */
public abstract class ListWindow {

    /** The window itself. */
    protected Shell shell;

    /** The toolbar of the list window. */
    protected Composite toolBar;

    /** Item which allows to filter entries in the list window. */
    protected ToolItem filterItem;

    /**
     * MVC wrapper around the GUI table for seperating presentation from data.
     * Uses a ConentProvider for the data and LabelProvider for the
     * presentation.
     */
    protected TableViewer mainTableViewer;

    /** The GUI table wrapped by the {@link #mainTableViewer}. */
    protected Table mainTable;

    /** The DBColumns corresponding to the displayed data records. */
    protected final Vector<DBColumn> columns;

    /** The search widget in the list window. */
    ExtensibleSearchControl filter;

    /**
     * String which is used for sorting the rows of the GUI table. <br>
     * <br>
     * Since the db function is used for the sorting it's just an SQL string
     * like <code>ORDER BY (column) [ASC|DSC]</code>.
     */
    protected String sortString = null;

    /** The button to create a new data record. */
    protected ToolItem newItem;

    /** The button to edit an existing data record. */
    protected ToolItem editItem;

    /** The button to delete an existing data record. */
    protected ToolItem deleteItem;

    /** The button to select an existing data record. */
    protected Button chooseButton;

    /** The button to configure the GUI table. */
    private ToolItem configureItem;

    /** A listener which reacts on changes of the database table. */
    private DBTableChangedListener updateListener;

    /**
     * Constructor for list windows called from the main menu, i.e. non-popup
     * list windows.
     * 
     * @param parent
     *                the parent window of the list window
     * @param columns
     *                the columns corresponding to the displayed data.
     */
    public ListWindow(Shell parent, Vector<DBColumn> columns) {
	this.columns = columns;

	shell = new Shell(parent, SWT.SHELL_TRIM | SWT.MODELESS);
	FormLayout mainLayout = new FormLayout();
	shell.setLayout(mainLayout);

	shell.addListener(SWT.Close, new Listener() {
	    public void handleEvent(Event event) {
		event.doit = false;
		setVisible(false);
	    }
	});

	mainTableViewer = new TableViewer(shell, SWT.BORDER | SWT.SINGLE
		| SWT.FULL_SELECTION);
	mainTable = mainTableViewer.getTable();

	mainTableViewer
		.addSelectionChangedListener(new ISelectionChangedListener() {

		    public void selectionChanged(SelectionChangedEvent event) {
			StructuredSelection selection = (StructuredSelection) event
				.getSelection();
			// these buttons become selectable when at least one
			// entry is selected
			editItem.setEnabled(selection.size() > 0);
			deleteItem.setEnabled(selection.size() > 0);
		    }
		});

	mainTableViewer.setContentProvider(new ArrayContentProvider());
	createTableBasics();
	createSpecializedMainTableListener();

	createListToolbarBasics(false); // non-popup list window
	createSpecializedToolBarListeners();

	mainTable.setLayoutData(Utility.getFormData(toolBar, 5, 100, -1, 0, 1,
		100, -1));

    }

    /**
     * Constructor for ListWindow instances created by user operations in the
     * {@link semorg.gui.util.AssociationTabControl}.
     * 
     * @param parent
     *                the parent shell (window) of the new list window
     * @param columns
     *                the columns to be displayed in the new list window
     * @param caller
     *                the {@link AssociationTabControl} instance which invoked
     *                this constructor
     */
    public ListWindow(Shell parent, Vector<DBColumn> columns,
	    AssociationTabControl caller) {
	this.columns = columns;

	shell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
	FormLayout mainLayout = new FormLayout();
	shell.setLayout(mainLayout);

	// when window closed the listener will be removed
	shell.addListener(SWT.Close, new Listener() {
	    public void handleEvent(Event event) {
		AbstractTable.removeDBTableChangedListener(updateListener);
	    }
	});

	shell.setSize(600, 180);

	mainTableViewer = new TableViewer(shell, SWT.BORDER | SWT.MULTI
		| SWT.FULL_SELECTION);
	mainTable = mainTableViewer.getTable();
	mainTableViewer.setContentProvider(new ArrayContentProvider());
	createTableBasics();
	createSpecializedMainTableListener(caller);

	Composite buttonArea = createButtonArea();
	createChooseButtonListener(caller);

	createListToolbarBasics(true);

	mainTable.setLayoutData(Utility.getFormData(toolBar, 5, buttonArea, -1,
		0, 1, 100, -1));
	buttonArea.setLayoutData(Utility.getFormData(null, 0, 100, 0, 0, 1,
		100, -1));
    }

    /**
     * Constructor for list window instances invoked by instances of the class
     * {@link semorg.gui.util.ForeignKeyConstraintSelector}.
     * 
     * @param parent
     *                the parent shell (window) of the new list window
     * @param columns
     *                the columns to be displayed in the new list window
     * @param caller
     *                the {@link ForeignKeyConstraintSelector} instance which
     *                invoked this constructor
     */
    public ListWindow(Shell parent, Vector<DBColumn> columns,
	    ForeignKeyConstraintSelector caller) {
	this.columns = columns;

	shell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
	FormLayout mainLayout = new FormLayout();
	shell.setLayout(mainLayout);

	shell.addListener(SWT.Close, new Listener() {
	    public void handleEvent(Event event) {
		AbstractTable.removeDBTableChangedListener(updateListener);
	    }
	});

	shell.setSize(600, 180);

	mainTableViewer = new TableViewer(shell, SWT.BORDER | SWT.MULTI
		| SWT.FULL_SELECTION);
	mainTable = mainTableViewer.getTable();
	mainTableViewer.setContentProvider(new ArrayContentProvider());
	createTableBasics();
	createSpecializedMainTableListener(caller);

	Composite buttonArea = createButtonArea();
	createChooseButtonListener(caller);

	createListToolbarBasics(true);

	mainTable.setLayoutData(Utility.getFormData(toolBar, 5, buttonArea, -1,
		0, 1, 100, -1));
	buttonArea.setLayoutData(Utility.getFormData(null, 0, 100, 0, 0, 1,
		100, -1));
    }

    /**
     * Creates a composite containing the buttons choose and abort and returns
     * it.
     * 
     * @return the composite including two buttons
     */
    private Composite createButtonArea() {
	Composite buttonArea = new Composite(shell, SWT.NULL);
	buttonArea.setLayout(new RowLayout());

	chooseButton = new Button(buttonArea, SWT.PUSH);
	chooseButton.setText(Messages.getString("GUIText.ChooseButtonText"));

	Button abortButton = new Button(buttonArea, SWT.PUSH);
	abortButton.setText(Messages.getString("GUIText.AbortButtonText"));
	abortButton.addSelectionListener(new SelectionAdapter() {
	    public void widgetSelected(SelectionEvent e) {
		shell.close();
	    }
	});

	return buttonArea;
    }

    /** Sets the list window to active. */
    protected void setActive() {
	shell.setActive();
    }

    /**
     * Sets the visibility of the list window according to the given parameter.
     * 
     * @param vis
     *                if <tt>true</tt> the list window is set to visible, else (<tt>false</tt>)
     *                it is set to invisible
     */
    public void setVisible(boolean vis) {
	shell.setVisible(vis);
    }

    /**
     * Updates the GUI table with the actual records from a database query. <br>
     * <br>
     * For example if the rows of the GUI table are sorted by a column or a
     * record has been deleted.
     */
    public void update() {
	mainTableViewer.setInput(getTableInputFromDB());
    }

    /**
     * Creates the toolbar composite and its basic components. <br>
     * <br>
     * The appearance of the toolbar in a popup list window and normal list
     * window differs (i.e. not all buttons are displayed in popup window), thus
     * you have to specify for which kind of list window the toolbar is meant
     * for.
     * 
     * @param isPopup
     *                Indicates whether the list window is used as a popup
     *                window (<tt>true</tt>) or as a normal list window
     *                called by the main window menu (<tt>false</tt>).
     */
    protected void createListToolbarBasics(boolean isPopup) {
	toolBar = new Composite(shell, SWT.NONE);
	filter = new ExtensibleSearchControl(toolBar, 8);

	toolBar.setLayout(new RowLayout());

	filter.setColumns(columns);
	filter.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "ListFilter");
	filter.addExtensionListener(new SelectionListener() {

	    public void widgetSelected(SelectionEvent event) {
		shell.layout();
	    }

	    public void widgetDefaultSelected(SelectionEvent event) {
		widgetSelected(event);
	    }
	});

	final ToolBar viewToolBar = new ToolBar(toolBar, SWT.FLAT);

	filterItem = new ToolItem(viewToolBar, SWT.CHECK);
	filterItem.setImage(Utility.getImage("filter.png"));
	filterItem.setToolTipText(Messages
		.getString("GUIText.FilterItemButtonToolTip"));
	filterItem.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "FilterItem");
	filterItem.addSelectionListener(new SelectionListener() {

	    public void widgetSelected(SelectionEvent event) {
		update();
	    }

	    public void widgetDefaultSelected(SelectionEvent event) {
		widgetSelected(event);
	    }
	});

	ToolItem updateItem = new ToolItem(viewToolBar, SWT.PUSH);
	updateItem.setImage(Utility.getImage("update.png"));
	updateItem.setToolTipText(Messages
		.getString("GUIText.UpdateItemButtonToolTip"));
	updateItem.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "UpdateItem");
	updateItem.addSelectionListener(new SelectionListener() {

	    public void widgetSelected(SelectionEvent event) {
		update();
	    }

	    public void widgetDefaultSelected(SelectionEvent event) {
		widgetSelected(event);
	    }
	});

	viewToolBar.pack();

	if (!isPopup) {
	    final ToolBar dataToolBar = new ToolBar(toolBar, SWT.FLAT);

	    newItem = new ToolItem(dataToolBar, SWT.PUSH);
	    newItem.setImage(Utility.getImage("new.png"));
	    newItem.setToolTipText(Messages
		    .getString("GUIText.AddNewItemButtonToolTip"));
	    newItem.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "Neu");

	    editItem = new ToolItem(dataToolBar, SWT.PUSH);
	    editItem.setImage(Utility.getImage("edit.png"));
	    editItem.setEnabled(false);
	    editItem.setToolTipText(Messages
		    .getString("GUIText.EditItemButtonToolTip"));
	    editItem.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "EditItem");

	    deleteItem = new ToolItem(dataToolBar, SWT.PUSH);
	    deleteItem.setImage(Utility.getImage("delete.png"));
	    deleteItem.setToolTipText(Messages
		    .getString("GUIText.DeleteItemButtonToolTip"));
	    deleteItem.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "DeleteItem");
	    deleteItem.setEnabled(false);

	    dataToolBar.pack();

	}

	final ToolBar adminToolBar = new ToolBar(toolBar, SWT.FLAT);

	configureItem = new ToolItem(adminToolBar, SWT.PUSH);
	configureItem.setImage(Utility.getImage("configure.png"));
	configureItem.setToolTipText(Messages
		.getString("GUIText.ConfigureListItemButtonToolTip"));
	configureItem
		.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "ConfigureItem");
	configureItem.setEnabled(true);

	configureItem.addSelectionListener(new SelectionAdapter() {

	    public void widgetSelected(SelectionEvent arg0) {

		ConfigureListDlg dlg = new ConfigureListDlg(Display
			.getCurrent().getActiveShell(), mainTable.getColumns(),
			mainTable.getColumnOrder());

		if (dlg.open() == IDialogConstants.OK_ID) {
		    copyColumnProperties(dlg.getColumnProperties());
		}
	    }

	    private void copyColumnProperties(
		    TableColumnProperty[] dlgColumnProperties) {
		TableColumn[] tableColumns = mainTable.getColumns();
		int[] columnOrder = new int[tableColumns.length];
		for (int i = 0; i < tableColumns.length; i++) {
		    tableColumns[i].setWidth(dlgColumnProperties[i].getWidth());
		    tableColumns[i].setResizable(dlgColumnProperties[i]
			    .getResizable());
		    tableColumns[i].setAlignment(dlgColumnProperties[i]
			    .getAlignment());
		    columnOrder[dlgColumnProperties[i].getColumnNumber()] = dlgColumnProperties[i]
			    .getIndex();
		}
		mainTable.setColumnOrder(columnOrder);
	    }
	});

	if (!isPopup) {
	    ToolItem printItem = new ToolItem(adminToolBar, SWT.PUSH);
	    printItem.setImage(Utility.getImage("print.png"));
	    printItem.setToolTipText(Messages
		    .getString("GUIText.PrintListItemButtonToolTip"));
	    printItem.setEnabled(false);

	    ToolItem exportItem = new ToolItem(adminToolBar, SWT.PUSH);
	    exportItem.setImage(Utility.getImage("export.png"));
	    exportItem.setToolTipText(Messages
		    .getString("GUIText.ExportListItemButtonToolTip"));
	    exportItem.setEnabled(false);

	    adminToolBar.pack();
	}

	toolBar
		.setLayoutData(Utility
			.getFormData(0, 0, null, 0, 0, 0, 100, -1));
    }

    /**
     * Creates for each db column ({@link #columns}) a corresponding GUI
     * column. <br>
     * <br>
     * In addition a SelectionListener is added to the GUI columns so that a
     * selection of a column header causes the sorting of the rows by the
     * according column. The current sort column is marked by an upside
     * respectively downside triangle.
     */
    protected void createTableBasics() {

	mainTable.setHeaderVisible(true);
	mainTable.setData(Utility.ATOSJ_COMPONENT_NAME_KEY, "MainTable");

	/* create and set up the GUI table columns */
	final TableColumn[] tableColumns = new TableColumn[columns.size()];

	for (int i = 0; i < tableColumns.length; i++) {
	    tableColumns[i] = new TableColumn(mainTable, SWT.LEFT);
	    tableColumns[i].setWidth(100);
	    tableColumns[i].setText(columns.get(i).getPublicColumnName());
	    tableColumns[i].addSelectionListener(new SelectionAdapter() {

		public void widgetSelected(SelectionEvent e) {

		    /*
		     * if a column head gets selected the list is sorted by this
		     * property and the column head is marked with an upside or
		     * downside triangle
		     */
		    TableColumn currentColumn = (TableColumn) e.widget;
		    TableColumn sortColumn = mainTable.getSortColumn();
		    int sortingDirection;

		    if (sortColumn == currentColumn) // the column head was
			// clicked twice
			sortingDirection = ((mainTable.getSortDirection() == SWT.UP) ? SWT.DOWN
				: SWT.UP);
		    else {
			sortingDirection = SWT.UP; // the column head was
			// clicked once
			mainTable.setSortColumn(currentColumn);
		    }
		    mainTable.setSortDirection(sortingDirection);

		    int columnNumber = currentColumn.getParent().indexOf(
			    currentColumn);
		    String internalColumnName = columns.get(columnNumber)
			    .getInternalColumnName();
		    String sortDirection = (sortingDirection == SWT.UP) ? "ASC"
			    : "DESC";

		    // creating sql string for the sorting
		    sortString = "ORDER BY " + internalColumnName + " "
			    + sortDirection;

		    update();
		}
	    });
	}

    }

    /**
     * Listener which reacts on changes of the database. It simply updates the
     * GUI table to changes of the database table.
     * 
     * @param type
     *                the wanted type of the DBTableChangedListener
     */
    protected void addUpdateListener(int type) {
	updateListener = new DBTableChangedListener(type) {
	    public void dBTableChanged() {
		update();
	    }
	};

	AbstractTable.addDBTableChangedListener(updateListener);
    }

    /**
     * Returns a vector of table class instances which will be displayed in the
     * GUI table of the list window. The filter and sorting preferences are also
     * consindered.
     * 
     * @return a vector filled with table class instances according to the
     *         current sorting and filter preferences.
     */
    protected abstract Object getTableInputFromDB();

    /**
     * Creates listeners for the toolbar buttons {@link #editItem},
     * {@link #newItem} and {@link #deleteItem} which provide the functionality
     * of the buttons. These listeners opens edit windows or deleting
     * dialogs and invoke the deleting or creating methods.
     */
    protected abstract void createSpecializedToolBarListeners();

    /**
     * Creates a {@link semorg.gui.util.DBTableChangedListener} and adds it to
     * the {@link #mainTableViewer}. <br>
     * <br>
     * This listener opens a editing window filled with the values of the
     * selected GUI table entry on doubleclicking the entry.
     */
    protected abstract void createSpecializedMainTableListener();

    /**
     * This method creates two listener and adds them to the main table viewer.
     * <br>
     * <br>
     * The first one is a {@link semorg.gui.util.DBTableChangedListener} which
     * reacts on changes of the database table. <br>
     * The second one is special DoubleClickListener according to the table
     * class (e.g. Lecturer, Client) of the displayed data. This listener gets
     * the selected entry and delivers its id to the
     * {@link semorg.gui.util.AssociationTabControl} instance which called the
     * list window by invoking the method
     * {@link semorg.gui.util.AssociationTabControl#addSelectedIds(semorg.sql.tables.SimpleIDKey[], boolean)}.
     * 
     * @param caller
     *                the {@link semorg.gui.util.AssociationTabControl} instance
     *                which called the actual list window
     */
    protected abstract void createSpecializedMainTableListener(
	    final AssociationTabControl caller);

    /**
     * This method creates two listener and adds them to the main table viewer.
     * <br>
     * <br>
     * The first one is a {@link semorg.gui.util.DBTableChangedListener} which
     * reacts on changes of the database table. <br>
     * The second one is special DoubleClickListener according to the table
     * class (e.g. Lecturer, Client) of the displayed data. This listener gets
     * the selected entry and delivers its id to the
     * {@link semorg.gui.util.ForeignKeyConstraintSelector} instance which
     * called the list window by invoking the method
     * {@link semorg.gui.util.ForeignKeyConstraintSelector#setSelectedId(int)}.
     * 
     * @param caller
     *                the {@link semorg.gui.util.ForeignKeyConstraintSelector}
     *                instance which called the actual list window
     */
    protected abstract void createSpecializedMainTableListener(
	    final ForeignKeyConstraintSelector caller);

    /**
     * Creates a SelectionListener for the choose button which passes the
     * SimpleIDKeys of the selected elements to the calling
     * AssociationTabControl by invoking the method addSelectedIds of the caller
     * instance.
     * 
     * @see semorg.gui.util.AssociationTabControl#addSelectedIds(semorg.sql.tables.SimpleIDKey[],
     *      boolean)
     * @param caller
     *                the AssociationTabControl instance which called the list
     *                window.
     */
    protected abstract void createChooseButtonListener(
	    final AssociationTabControl caller);

    /**
     * Creates a SelectionListener for the choose button which passes the
     * database id of the selected element to the calling
     * ForeignKeyConstraintSelector by invoking the method setSelectedId of the
     * caller instance.
     * 
     * @see semorg.gui.util.ForeignKeyConstraintSelector#setSelectedId(int)
     * @param caller
     *                the ForeignKeyConstraintSelector instance which called the
     *                list window.
     */
    protected abstract void createChooseButtonListener(
	    final ForeignKeyConstraintSelector caller);
}
