package org.freehep.util.parameterdatabase;

import java.lang.reflect.Constructor;

import java.awt.BorderLayout;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.util.Iterator;
import java.util.LinkedList;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

import org.freehep.swing.CellOrientatedTable;
import org.freehep.util.parameterdatabase.selector.*;
import org.freehep.util.parameterdatabase.editor.*;
import org.freehep.util.parameterdatabase.types.*;

public class DatabaseTable 
    implements TableModel,
               DatabaseListener {

    private JComponent table;

    /**
     * The object used as the key into the database.  Actually here this will
     * be used to generate a ClassIterator which will be used to search the
     * database. */
    private Object key = null;

    /**
     * The flag indicating whether or not the local column is shown.  The two
     * forms of the table are obtained with a trick--we just add one to the
     * column index if there is no local column. */
    private boolean local;

    /**
     * The ClassIterator derived from the key object. */
    private ClassIterator classIterator = null;

    /**
     * An array containing a sorted list of names. */
    private String[] names = null;

    /**
     * The database which will be searched for parameters. */
    ParameterDatabase database; 

    /**
     * The list of TableModelListeners. */
    protected LinkedList tableModelListeners = new LinkedList();

    /** 
     * Create a new table to view the parameters in the database. */
    public DatabaseTable(ParameterDatabase database) {
        this(database,true);
    }

    /** 
     * Create a new table to view the parameters in the database.  If local is
     * true, then this table will show a "local variable" column.  If it is
     * false, then the "local variable" column is omitted. */
    public DatabaseTable(ParameterDatabase database, boolean local) {

        if (database==null) throw new IllegalArgumentException();
        this.local = local;
        this.database = database;
        table = makeForm();
    }

    /**
     * Return a reference to the JComponent which contains the table. */
    public JComponent getForm() {
        return table;
    }

    /**
     * Set the object used to search the parameter database. */
    public void setKeyObject(Object key) {
        if (this.key!=key) {
            this.key = key;

            if (key!=null) {

                // Make the ClassIterator for this object.
                classIterator = new ClassIterator(key);
                
                // Now we must remake the list of names.
                names = database.getCurrentParameterSet(classIterator);
            } else {
                classIterator = null;
                names = null;
            }
            fireTableChanged();
        }
    }

    /**
     * This is called when any parameter of the database is changed.  It is
     * important when a parameter is updated programatically so that the user
     * interface reflects the current state of the database. */
    public void databaseUpdated() {
        fireTableChanged();
    }

    public void fireTableChanged() {

        // Create the table change event.
        TableModelEvent event = new TableModelEvent(this);

        // Notify all of the listeners.
        Iterator iterator = tableModelListeners.iterator();
        while (iterator.hasNext()) {
            TableModelListener listener = 
                (TableModelListener) iterator.next();
            listener.tableChanged(event);
        }
    }

    private JComponent makeForm() {

        // Make an overall JPanel to hold everything.
        JPanel main = new JPanel();
        main.setLayout(new BorderLayout());

        // Make a table with this as its table model.
        JTable table = new CellOrientatedTable(this);
        table.setDefaultEditor(Selector.class, new SelectorEditor());
        table.setDefaultEditor(DoubleRange.class, new DoubleRangeEditor());

        JScrollPane scroll = new JScrollPane(table);
        main.add(scroll,BorderLayout.CENTER);

        return main;
    }

    public void addTableModelListener(TableModelListener listener) {
        if (listener!=null) tableModelListeners.add(listener);
    }

    public void removeTableModelListener(TableModelListener listener) {
        tableModelListeners.remove(listener);
    }

    public Class getColumnClass(int columnIndex) {

        if (!local) columnIndex++;

        Class columnClass;
        switch (columnIndex) {
        case (0): 
            columnClass = Boolean.class;
            break; 
        case (1): 
            columnClass = String.class;
            break;
        case (2): 
            columnClass = Object.class;
            break;
        default: 
            columnClass = null;
            break;
        }
        return columnClass;
    }

    public int getColumnCount() {
        return (local) ? 3 : 2;
    }

    public String getColumnName(int columnIndex) {

        if (!local) columnIndex++;

        String columnName;
        switch (columnIndex) {
        case (0): 
            columnName = "Local";
            break; 
        case (1): 
            columnName = "Parameter";
            break;
        case (2): 
            columnName = "Value";
            break;
        default: 
            columnName = null;
            break;
        }
        return columnName;
    }

    public boolean isCellEditable(int rowIndex, int columnIndex) {
        if (local) {
            return (columnIndex==0 || columnIndex==2) ? true : false;
        } else {
            return (columnIndex==1);
        }
    }

    public int getRowCount() {

        // The number of rows is just equal to the number of parameter names
        // in the list.
        return (names!=null) ? names.length : 0;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {

        if (!local) columnIndex++;

        if (columnIndex==0) {
            classIterator.reset();
            String name = names[rowIndex];
            if (database.isParameterLocal(name,classIterator)) {
                return Boolean.TRUE;
            } else {
                return Boolean.FALSE;
            }
        } else if (columnIndex==1) {
            return names[rowIndex];
        } else if (columnIndex==2) {
            classIterator.reset();
            String name = names[rowIndex];
            return database.getParameter(name,classIterator);
        } else {
            return null;
        }
    }

    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {

        // We need to be a bit careful with this "local" column trick here. We
        // reuse the column index to pass back into getValueAt(), so we need
        // to keep a local copy of columnIndex.
        int cIndex = columnIndex;
        if (!local) cIndex++;

        if (cIndex==0 && (aValue instanceof Boolean)) {
            boolean makeLocal = ((Boolean) aValue).booleanValue();

            if (makeLocal) {
                String name = names[rowIndex];
                Object value = getValueAt(rowIndex,2);
                
                if (value!=null) {
                    
                    Object firstObject = null;
                    classIterator.reset();
                    if (classIterator.hasNext()) {
                        firstObject = classIterator.next();
                    }
                    
                    PropertyChangeListener listener = null;
                    if (firstObject instanceof PropertyChangeListener) {
                        listener = (PropertyChangeListener) firstObject;
                    }
                    
                    classIterator.reset();
                    database.addParameter(name,value,classIterator,listener);
                }
            } else {
                String name = names[rowIndex];
                classIterator.reset();
                database.removeParameter(name,classIterator);
            }
        }

        if (cIndex==2) {

            String name = names[rowIndex];
            Object object = getValueAt(rowIndex, columnIndex);
            Class c = object.getClass();
            if (c.equals(aValue.getClass())) {

                // Check to see if the new value is of the same type as the
                // old value.  If so, just set the parameter in the database.
                classIterator.reset();
                database.setParameter(name,aValue,classIterator);
                
            } else if (aValue instanceof String) {

                // Here we try to reconstruct the object itself from a
                // constructor which takes a String.
                
                String value = (String) aValue;
                
                if (object!=null) {
                    
                    Object[] parameters = new Object[1];
                    parameters[0] = value;
                    Class[] parameterTypes = new Class[1];
                    parameterTypes[0] = String.class;
                    
                    try {
                        Class objectClass = object.getClass();
                        Constructor constructor = 
                            objectClass.getConstructor(parameterTypes);
                        
                        Object newValue = constructor.newInstance(parameters);
                        
                        classIterator.reset();
                        database.setParameter(name,newValue,classIterator);
                        
                    } catch (NoSuchMethodException nsme) {
                        nsme.printStackTrace();
                    } catch (InstantiationException ie) {
                        ie.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
