package usda.weru.util;

import java.awt.event.FocusEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
import javax.measure.Measurable;
import javax.measure.Measure;
import javax.measure.quantity.Quantity;
import javax.measure.unit.Unit;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.text.DefaultFormatterFactory;

/**
 *
 * @param <Q> Quantity to type the field to.
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class MeasurableField<Q extends Quantity> extends JFormattedTextField {

    private static final long serialVersionUID = 1L;

    /**
     *
     */
    public static final String PROP_UNITS_LABEL = "unitsLabel";

    /**
     *
     */
    public static final String PROP_UNITS = "units";

    /**
     *
     */
    public static final String PROP_DEFAULT_PATTERN = "defaultPattern";

    /**
     *
     */
    public static final String PROP_EDIT_PATTERN = "editPattern";

    /**
     *
     */
    public static final String PROP_DISPLAY_PATTERN = "displayPattern";

    /**
     *
     */
    public static final String PROP_VALUE = "value";

    /**
     *
     */
    protected JLabel c_unitsLabel;

    /**
     *
     */
    protected Unit<Q> c_units;

    /**
     *
     */
    protected final DefaultFormatterFactory c_formatterFactory;

    private boolean c_fireValueChanges = true;

    private String c_defaultPattern;
    private String c_editPattern;
    private String c_displayPattern;

    private final List<ValueListener<Q>> c_listeners;

    /**
     *
     */
    public MeasurableField() {
        c_listeners = new ArrayList<ValueListener<Q>>();
        c_formatterFactory = new DefaultFormatterFactory();
        setFormatterFactory(c_formatterFactory);
        setDefaultPattern("#0.00000");

        addPropertyChangeListener(PROP_VALUE, new PropertyChangeListener() {

            @Override
            @SuppressWarnings("unchecked")
            public void propertyChange(PropertyChangeEvent evt) {
                if (!c_fireValueChanges) {
                    return;
                }
                Measurable<Q> oldValue = (Measurable<Q>) evt.getOldValue();
                Measurable<Q> newValue = (Measurable<Q>) evt.getNewValue();
                Iterator<ValueListener<Q>> it = c_listeners.iterator();

                while (it.hasNext()) {
                    it.next().valueChanged(oldValue, newValue);
                }
            }
        });

    }

    /**
     *
     * @param label
     */
    public void setUnitsLabel(JLabel label) {
        JLabel old = c_unitsLabel;
        c_unitsLabel = label;
        updateUnitsLabel();
        firePropertyChange(PROP_UNITS_LABEL, old, c_unitsLabel);
    }

    /**
     *
     * @return
     */
    public JLabel getUnitsLabel() {
        return c_unitsLabel;
    }

    /**
     *
     * @param units
     */
    public void setUnits(Unit<Q> units) {
        Unit<Q> old = c_units;
        c_units = units;
        setValue(getValue());
        updateUnitsLabel();
        firePropertyChange(PROP_UNITS, old, c_units);
    }

    /**
     *
     * @return
     */
    public Unit<Q> getUnits() {
        return c_units;
    }

    private void updateUnitsLabel() {
        if (c_unitsLabel != null) {
            if (c_units != null) {
                c_unitsLabel.setText(c_units.toString());
            } else {
                c_unitsLabel.setText("?");
            }
        }
    }

    /**
     *
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public Measurable<Q> getValue() {
        if (super.getValue() instanceof Measurable) {
            return (Measurable<Q>) super.getValue();
        } else {
            return null;
        }
    }

    /**
     *
     * @param value
     * @deprecated
     */
    @Override
    @Deprecated
    @SuppressWarnings("unchecked")
    public void setValue(Object value) {
        //Although deprecated, we pass off valid values so the beans binding will work.
        if (value == null || value instanceof Measurable) {
            setValue((Measurable<Q>) value);
        } else {
            throw new IllegalArgumentException("Value must be a Measurable<Q> object.");
        }
    }

    /**
     *
     * @param value
     */
    public void setValue(Measurable<Q> value) {
        c_fireValueChanges = false;
        try {
            super.setValue(value);
        } finally {
            c_fireValueChanges = true;
        }
    }

    /**
     *
     * @param e
     */
    @Override
    protected void processFocusEvent(FocusEvent e) {

        if (FocusEvent.FOCUS_GAINED == e.getID()) {
            super.processFocusEvent(e);
            selectAll();
        } else if (FocusEvent.FOCUS_LOST == e.getID()) {
            try {
                super.commitEdit();
                super.processFocusEvent(e);
            } catch (ParseException pe) {
                JOptionPane.showMessageDialog(MeasurableField.this,
                        "Unable to parse value: " + getText(),
                        "Numeric Value", JOptionPane.WARNING_MESSAGE);
                setValue(getValue());
                MeasurableField.this.requestFocusInWindow();
            }

        }
    }

    /**
     *
     * @param pattern
     */
    public void setDefaultPattern(String pattern) {
        String old = c_defaultPattern;
        c_defaultPattern = pattern;
        c_formatterFactory.setDefaultFormatter(new InternalMeasurableFormatter(c_defaultPattern));
        firePropertyChange(PROP_DEFAULT_PATTERN, old, c_defaultPattern);
    }

    /**
     *
     * @return
     */
    public String getDefaultPattern() {
        return c_defaultPattern;
    }

    /**
     *
     * @param pattern
     */
    public void setEditPattern(String pattern) {
        String old = c_editPattern;
        c_editPattern = pattern;
        c_formatterFactory.setEditFormatter(new InternalMeasurableFormatter(c_editPattern));
        firePropertyChange(PROP_EDIT_PATTERN, old, c_editPattern);
    }

    /**
     *
     * @return
     */
    public String getEditPattern() {
        return c_editPattern;
    }

    /**
     *
     * @param pattern
     */
    public void setDisplayPattern(String pattern) {
        String old = c_displayPattern;
        c_displayPattern = pattern;
        c_formatterFactory.setDisplayFormatter(new InternalMeasurableFormatter(c_displayPattern));
        firePropertyChange(PROP_DISPLAY_PATTERN, old, c_displayPattern);
    }

    /**
     *
     * @return
     */
    public String getDisplayPattern() {
        return c_displayPattern;
    }

    private class InternalMeasurableFormatter extends AbstractFormatter {

        private static final long serialVersionUID = 1L;

        private final NumberFormat c_format;

        public InternalMeasurableFormatter(String pattern) {
            c_format = new DecimalFormat(pattern);
        }

        @Override
        public Object stringToValue(String text) throws ParseException {
            try {
                Number number = c_format.parse(text);
                Double value = number.doubleValue();
                return Measure.valueOf(value, c_units);
            } catch (ParseException e) {
                throw new ParseException(text, 0);
            }

        }

        @Override
        @SuppressWarnings("unchecked")
        public String valueToString(Object value) throws ParseException {
            try {
                if (value instanceof Measurable) {
                    Measurable<Q> measure = (Measurable<Q>) value;
                    double d = measure.doubleValue(c_units);
                    return c_format.format(d);
                } else {
                    return null;
                }
            } catch (Exception e) {
                throw new ParseException(null, 0);
            }
        }
    }

    /**
     *
     * @param listener
     */
    public void addValueListener(ValueListener<Q> listener) {
        c_listeners.add(listener);
    }

    /**
     *
     * @param listener
     */
    public void removeValueListener(ValueListener<Q> listener) {
        c_listeners.remove(listener);
    }

    /**
     *
     * @param <Q>
     */
    public interface ValueListener<Q extends Quantity> extends EventListener {

        /**
         *
         * @param oldValue
         * @param newValue
         */
        public void valueChanged(Measurable<Q> oldValue, Measurable<Q> newValue);
    }

}
