/*
 * Copyright (C) 2011 Peransin Nicolas.
 * Use is subject to license terms.
 */
package org.mypsycho.swing.table;

import java.awt.Color;
import java.awt.Component;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;

/**
 * The Editor does not rely on TableModel to fetch the class of the cell.
 * The TableModel can only provide 1 class for 1 column.
 * 
 * The expected class is provided by a constructor of class or a static getter.
 * 
 * @author Peransin Nicolas
 */
public class GenericEditor extends DefaultCellEditor {

    private static final long serialVersionUID = 3668933684480176108L;
    
    private static final Class<?>[] argTypes = new Class[]{ String.class };
    Constructor<?> constructor = null;
    Method         getter = null;
    Object         value;

    public GenericEditor(Class<?> valueClass) {
        this();
        try {
            if (constructor.getDeclaringClass() == String.class) {
                constructor = valueClass.getConstructor(argTypes);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public GenericEditor(Class<?> valueClass, String staticMethod) {
        this();
        try {
            getter = valueClass.getMethod(staticMethod, argTypes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }        
    }
    
    private GenericEditor() {
        super(new JTextField());
        getComponent().setName("Table.editor");
    }
    
    public boolean stopCellEditing() {
        String s = (String)super.getCellEditorValue();
        // Here we are dealing with the case where a user
        // has deleted the string value in a cell, possibly
        // after a failed validation. Return null, so that
        // they have the option to replace the value with
        // null or use escape to restore the original.
        // For Strings, return "" for backward compatibility.
        if ("".equals(s)) {
            if ((constructor == null) && (getter == null)) {
                value = s;
            }
            super.stopCellEditing();
        }

        try {
            if (constructor != null) {
                value = constructor.newInstance(s);
            } else {
                value = getter.invoke(null, s);
            }
        } catch (Exception e) {
            ((JComponent)getComponent()).setBorder(new LineBorder(Color.red));
            return false;
        }
        return super.stopCellEditing();
    }

    public Component getTableCellEditorComponent(JTable table, Object value,
                boolean isSelected,
                int row, int column) {
        this.value = null;
        ((JComponent)getComponent()).setBorder(new LineBorder(Color.black));
        return super.getTableCellEditorComponent(table, value, isSelected, row, column);
    }

    public Object getCellEditorValue() {
        return value;
    }

    public static class NumberEditor extends GenericEditor {
        private static final long serialVersionUID = GenericEditor.serialVersionUID;

        public NumberEditor(Class<? extends Number> numberClass) {
            super(numberClass);
            ((JTextField)getComponent()).setHorizontalAlignment(JTextField.RIGHT);
        }
    }

    
    public static class BooleanEditor extends DefaultCellEditor {
        private static final long serialVersionUID = GenericEditor.serialVersionUID;

        public BooleanEditor() {
            super(new JCheckBox());
            JCheckBox checkBox = (JCheckBox)getComponent();
            checkBox.setHorizontalAlignment(JCheckBox.CENTER);
        }
    }
}