package adecWatt.view;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import javax.swing.AbstractButton;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import misc.Bundle;
import misc.Util;

import adecWatt.model.Editable;
import adecWatt.model.Permanent;
import adecWatt.model.Prop;
import adecWatt.model.Unit;

@SuppressWarnings ("serial") public class JDefPropTable extends JPanel implements ActionListener {
    // ========================================
    static public final String
	actionUp	= "Up",
	actionDown	= "Down",
	actionSetEnum	= "SetEnum",
	actionAdd	= "Add",
	actionRemove	= "Remove";

    static public final List<String>
	leftDefPropActionsNames		= Arrays.asList (actionUp, actionDown, actionSetEnum, actionAdd),
	rightDefPropActionsNames	= Arrays.asList (actionRemove);

    @SuppressWarnings ("unchecked")
    static public final Hashtable<String, Method> actionsMethod =
	Util.collectMethod (JDefPropTable.class,
			    leftDefPropActionsNames, rightDefPropActionsNames);

    public void  actionPerformed (ActionEvent e) {
	Util.actionPerformed (actionsMethod, e, this);
    }

    // ========================================
    public class PropShadow {
	String propName;
	Prop prop;
	Collection<String> oldModifiers;
	Collection<String> parentModifiers;
	ArrayList<JCheckBox> jModifiers;
	JComboBox<String> jType;
	JStringSet jStringSet;
	JDefLabel jDefLabel;

	public PropShadow (String propName, Prop prop, Collection<String> parentModifiers) {
	    this.propName = propName;
	    this.prop = prop;
	    this.oldModifiers = prop == null ? null : prop.getModifiers ();
	    if (parentModifiers != null && parentModifiers.size () > 0)
		this.parentModifiers = parentModifiers;
	    jType = Util.newEnum (Prop.PropTypeEnum.class, Prop.PropTypeEnum.Text);
	    if (prop != null) {
		oldModifiers = prop.getModifiers ();
		jType.setSelectedIndex (prop.getTypeToken ().ordinal ());
	    } else {
		try {
		    Unit<?> parent = jTransform.getPermanent ().getDirectUnit ();
		    oldModifiers = parent.getPropModifiers (propName);
		    jType.setSelectedIndex (parent.getProp (propName).getTypeToken ().ordinal ());
		} catch (Exception e) {
		}
	    }
	    jModifiers = JTransform.getJModifiers (modifiersSet, oldModifiers, jTransform.isLock (propName) && !admin);
	    jStringSet = new JStringSet (prop == null ? null : prop.enumChoice);
	    jDefLabel = new JDefLabel (prop == null ? null : prop.multiLabel);
	}
	public Collection<String> getModifiers () {
	    Collection<String> modifiers = JTransform.getModifiers (jModifiers, oldModifiers, admin, Permanent.PropHiddenModifiersSet);
	    if (modifiers == null || parentModifiers == null)
		return modifiers;
	    if (Permanent.getModifiersString (parentModifiers).equals (Permanent.getModifiersString (modifiers)))
		return null;
	    return modifiers;
	}
    }

    // ========================================
    private String[] tabExtraNames = { Bundle.getLabel ("Type"), Bundle.getLabel ("Property") };
    private JTransform jTransform;
    private Vector<PropShadow> objectData = new Vector<PropShadow> ();
    private JTable jTableObject;
    private JScrollPane jScrollPane;
    private JButton setEnum;
    public JTextField jNewPropName = new JTextField (16);
    private boolean admin;
    private Collection<String> modifiersSet;
    private int modifiersSetSize;
    private TableObjectModel dataObjectModel;

    public Vector<PropShadow> getObjectData () { return objectData; }
    //public TableObjectModel getDataObjectModel () { return dataObjectModel; }

    // ========================================
    public JDefPropTable (JTransform jTransform, Editable<?, ?, ?, ?> editable) {
	super (new BorderLayout ());
	this.jTransform = jTransform;
	admin = jTransform.isAdmin ();
	modifiersSet = admin ? Permanent.PropModifiersSet : Permanent.PropNotHiddenModifiersSet;
	modifiersSetSize = modifiersSet.size ();
	dataObjectModel = new TableObjectModel ();

	for (Prop prop : editable.getOrderedOwnProps ()) {
	    if (editable.getParentProp (prop.getName ()) != null &&
		prop.enumChoice == null && (prop.getModifiers () == null ||
					    prop.getModifiersString ().equals (Prop.getModifiersString
									       (editable.getParentPropModifiers (prop.getName ())))))
		continue;
	    // XXX 2 fois editable.getParentPropModifiers (prop.getName ()
	    Collection<String> parentModifiers = editable.getParentPropModifiers (prop.getName ());
	    objectData.add (new PropShadow (prop.getName (), prop, parentModifiers));
	}
	jTableObject = new JTable (dataObjectModel) {
		public void valueChanged (ListSelectionEvent e) {
		    super.valueChanged (e);
		    int idx = jTableObject.getSelectedRow ();
		    updateSetEnum (idx < 0 ? null : objectData.elementAt (idx));
		}
	    };
	jTableObject.getTableHeader ().setReorderingAllowed (true);
	jTableObject.setShowHorizontalLines (false);
	jTableObject.setShowVerticalLines (false);
	jTableObject.setRowSelectionAllowed (true);
	jTableObject.setColumnSelectionAllowed (false);
	// jTableObject.setIntercellSpacing(new Dimension(spacing, spacing));
	jTableObject.setRowHeight (24);
	jTableObject.setSelectionMode (0);
	//jTableObject.setAutoResizeMode (0);
	int columnIdx = 0;
	for (String modifier : modifiersSet) {
	    CheckIconEditorRenderer editor = new CheckIconEditorRenderer (modifier);
	    TableColumn tableColumn = jTableObject.getColumnModel ().getColumn (columnIdx);
	    tableColumn.setCellRenderer (editor);
	    tableColumn.setCellEditor (editor);
	    tableColumn.setMaxWidth (24);
	    columnIdx++;
	}
	EnumEditorRenderer enumEditor = new EnumEditorRenderer (Prop.PropTypeEnum.class);
	TableColumn tableColumn = jTableObject.getColumnModel ().getColumn (columnIdx);
	tableColumn.setCellRenderer (enumEditor);
	tableColumn.setCellEditor (enumEditor);

	add (jScrollPane = Util.getJScrollPane (jTableObject), BorderLayout.CENTER);
	dataObjectModel.revalidate (-1);

	// command panel
	JPanel propCmdPanel = new JPanel (new BorderLayout ());
	JPanel leftPropCmdPanel = new JPanel ();
	Util.addIconButton (leftDefPropActionsNames, this, leftPropCmdPanel);
	propCmdPanel.add (leftPropCmdPanel, BorderLayout.WEST);
	propCmdPanel.add (jNewPropName, BorderLayout.CENTER);
	propCmdPanel.add (Util.newIconButton (actionRemove, this), BorderLayout.EAST);
	add (propCmdPanel, BorderLayout.SOUTH);

	Hashtable<String, AbstractButton> buttons = Util.collectButtons (null, leftPropCmdPanel);
	Util.collectButtons (buttons, propCmdPanel);
	for (AbstractButton button : buttons.values ())
	    Util.unBoxButton (button);
	setEnum = (JButton) buttons.get (actionSetEnum);
	updateSetEnum (null);
    }

    // ========================================
    public class TableObjectModel extends AbstractTableModel {
	public int getColumnCount () {
	    return modifiersSetSize+tabExtraNames.length;
	}
	public int getRowCount () {
	    return objectData.size ();
	}
	public Object getValueAt (int row, int column) {
	    if (row >= objectData.size ())
		return null;
	    PropShadow prop = objectData.elementAt (row);
	    if (column < modifiersSetSize)
		return prop.jModifiers.get (column);
	    switch (column-modifiersSetSize) {
	    case 0: return prop.jType;
	    case 1: return prop.propName;
	    }
	    return null;
	}
	public String getColumnName (int column) {
	    if (column < modifiersSetSize)
		return ""; //modifiersSet.get (column);
	    return tabExtraNames[column-modifiersSetSize];
	}
	public Class<?> getColumnClass (int column) {
	    if (column < modifiersSetSize)
		return JCheckBox.class;
	    switch (column-modifiersSetSize) {
	    case 0: return JComboBox.class;
	    case 1: return String.class;
	    }
	    throw new IllegalArgumentException ("Column 0 >= "+column+" > "+(modifiersSetSize+2));
	}
	public boolean isCellEditable (int row, int column) {
	    PropShadow prop = objectData.elementAt (row);
	    if (prop == null)
		return false;
	    return !jTransform.isLock (prop.propName) || admin;
	}
	public void setValueAt (Object aValue, int row, int column) {
	    PropShadow prop = objectData.elementAt (row);
	    if (column < modifiersSetSize) {
		prop.jModifiers.get (column).setSelected ((Boolean) aValue);
		return;
	    }
	    switch (column-modifiersSetSize) {
	    case 0:
		prop.jType.setSelectedItem ((String) aValue);
		updateSetEnum (prop);
		return;
	    case 1:
		prop.propName = (String) aValue;
		return;
	    }
	}
	public void revalidate (int idx) {
	    if (jTableObject == null)
		return;
	    jTableObject.revalidate ();
	    jTableObject.getSelectionModel ().clearSelection ();
	    Dimension preferredSize = new Dimension (jTableObject.getPreferredSize ());
	    preferredSize.height += 40;
	    jScrollPane.setPreferredSize (preferredSize);
	    Util.packWindow (jScrollPane);
	    if (idx < 0)
		return;
	    jTableObject.getSelectionModel ().setLeadSelectionIndex (idx);
	}
	public boolean contains (String propName) {
	    for (PropShadow prop : objectData)
		if (prop.propName.equals (propName))
		    return true;
	    return false;
	}
	public void addRow (String propName) {
	    objectData.add (new PropShadow (propName, null, null));
	    revalidate (objectData.size ()-1);
	}
	public void removeRow () {
	    int idx = jTableObject.getSelectedRow ();
	    if (idx < 0)
		return;
	    Prop prop = objectData.get (idx).prop;
	    if (prop != null && prop.isLock () && !admin)
		return;
	    objectData.remove (idx);
	    if (idx == objectData.size ())
		idx--;
	    revalidate (idx);
	}
	public void move (int delta) {
	    int idx = jTableObject.getSelectedRow ();
	    if (idx < 0)
		return;
	    int mod = objectData.size ();
	    PropShadow old =  objectData.remove (idx);
	    idx = (idx+delta+mod) % mod;
	    if (idx == objectData.size ())
		objectData.add (old);
	    else
		objectData.add (idx, old);
	    revalidate (idx);
	}
	public void setEnum () {
	    int idx = jTableObject.getSelectedRow ();
	    if (idx < 0)
		return;
	    int nbLabel = 0;
	    switch (Prop.PropTypeEnum.values () [objectData.elementAt (idx).jType.getSelectedIndex ()]) {
	    case Enum:
		JStringSet jStringSet = objectData.elementAt (idx).jStringSet;
		jStringSet.init ();
		if (JOptionPane.showConfirmDialog (JDefPropTable.this,
						   jStringSet,
						   null, JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION)
		    return;
		jStringSet.confirm ();
		return;
	    case Square:
	    case Geo:
		nbLabel = 2;
		break;
	    case Cube:
		nbLabel = 3;
		break;
	    default:
		return;
	    }
	    JDefLabel jDefLabel = objectData.elementAt (idx).jDefLabel;
	    jDefLabel.initLabels (nbLabel);
	    if (JOptionPane.showConfirmDialog (JDefPropTable.this,
					       jDefLabel,
					       null, JOptionPane.YES_NO_OPTION)	!= JOptionPane.YES_OPTION)
		return;
	    jDefLabel.confirmLabels ();
	}
    }

    // ========================================
    public class EnumEditorRenderer extends DefaultCellEditor implements TableCellRenderer {
	public EnumEditorRenderer (Class<? extends Enum<?>> enumClass) {
	    super (Util.newEnum (enumClass, null));
	}
	@Override
	public Component getTableCellEditorComponent (JTable table, Object value, boolean isSelected, int row, int column) {
	    ((JComboBox<?>)editorComponent).setSelectedIndex (((JComboBox<?>) value).getSelectedIndex ());
	    return editorComponent;
	}
	public Component getTableCellRendererComponent (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
	    JComboBox<?> jCombobox = objectData.elementAt (row).jType;
	    jCombobox.setBackground (isSelected ? table.getSelectionBackground () : table.getBackground ());
	    jCombobox.setForeground (isSelected ? table.getSelectionForeground () : table.getForeground ());
	    jCombobox.setSelectedItem (((JComboBox<?>) value).getSelectedIndex ());
	    return jCombobox;
	}
    };

    // ========================================
    public class CheckIconEditorRenderer extends DefaultCellEditor implements TableCellRenderer {
	public CheckIconEditorRenderer (String action) {
	    super (Util.newCheckIcon (Util.toCapital (action), null));
	}
	@Override
	public Component getTableCellEditorComponent (JTable table, Object value, boolean isSelected, int row, int column) {
	    ((JCheckBox)editorComponent).setSelected (((JCheckBox) value).isSelected ());
	    return editorComponent;
	}
	public Component getTableCellRendererComponent (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
	    PropShadow prop = objectData.elementAt (row);
	    JCheckBox jCheckBox = prop.jModifiers.get (Util.viewToModel (table, column));
	    if (jCheckBox == null)
		return null;
	    jCheckBox.setBackground (isSelected ? table.getSelectionBackground () : table.getBackground ());
	    jCheckBox.setForeground (isSelected ? table.getSelectionForeground () : table.getForeground ());
	    jCheckBox.setSelected (((JCheckBox) value).isSelected ());
	    return jCheckBox;
	}
    };

    // ========================================
    public void actionUp () {
	dataObjectModel.move (-1);
    }
    public void actionDown () {
	dataObjectModel.move (+1);
    }
    public void actionSetEnum () {
	dataObjectModel.setEnum ();
    }
    public void actionAdd () {
	String propName = jNewPropName.getText ();
	if (dataObjectModel.contains (propName))
	    return;
	dataObjectModel.addRow (propName);
	jNewPropName.setText ("");
    }

    // ========================================
    public void actionRemove () {
	dataObjectModel.removeRow ();
    }
    public void updateSetEnum (PropShadow prop) {
	boolean enable = false;
	try {
	    switch (Prop.PropTypeEnum.values () [prop.jType.getSelectedIndex ()]) {
	    case Enum:
		enable = true;
		break;
	    case Square:
	    case Geo:
	    case Cube:
		// XXX sauf si lock et pas admin
		enable = true;
	    }
	} catch (Exception e) {
	}
	setEnum.setEnabled (enable);
    }

    // ========================================
}
