/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.gui.panel.edit;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;

import net.sf.jaxodraw.gui.panel.JaxoColorChooser;
import net.sf.jaxodraw.gui.panel.button.JaxoColorButton;
import net.sf.jaxodraw.util.JaxoPrefs;
import net.sf.jaxodraw.util.JaxoColor;
import net.sf.jaxodraw.util.JaxoLog;


/**
 * A listener for edit panels.
 *
 * @since 2.0
 */
public class JaxoOptionsPanelListener implements JaxoEditPanelListener {
    private final PropertyChangeListener theObject;
    private ChangeListener changeListener;
    private ChangeEvent changeEvent;

    /**
     * Constructor: sets the object that gets changed
     * by actions on the edit panel.
     *
     * @param object The object to receive ChangeEvents from the edit panel.
     */
    public JaxoOptionsPanelListener(final PropertyChangeListener object) {
        this(object, null);
    }

    /**
     * Constructor: sets the object that gets changed
     * by actions on the edit panel.
     *
     * @param object The object to receive ChangeEvents from the edit panel.
     * @param l ChangeListener to notify after each change to 'object'. May be null.
     */
    public JaxoOptionsPanelListener(final PropertyChangeListener object, final ChangeListener l) {
        this.theObject = object;
        this.changeListener = l;

        if (l != null) {
            changeEvent = new ChangeEvent(this);
        }
    }

    private void fireStateChanged() {
        if (changeListener != null) {
            changeListener.stateChanged(changeEvent);
        }
    }

    private void setParameter(final String name, final Object value) {
        theObject.propertyChange(
                new PropertyChangeEvent(this, name, null, value));
    }

    /**
     * Transmits the changes from an edit panel to the object.
     * @param evt The ChangeEvent to transmit.
     */
    public void stateChanged(final ChangeEvent evt) {
        final Component component = (Component) evt.getSource();
        final String name = component.getName();

        if ("spxin".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("xPosition", (Integer) model.getNumber());
        } else if ("spyin".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("yPosition", (Integer) model.getNumber());
        } else if ("spx".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("x", (Integer) model.getNumber());
        } else if ("spy".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("y", (Integer) model.getNumber());
        } else if ("spxfin".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("x2", (Integer) model.getNumber());
        } else if ("spyfin".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("y2", (Integer) model.getNumber());
        } else if ("spwidth".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("relativeWidth", (Integer) model.getNumber());
        } else if ("spheight".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("relativeHeight", (Integer) model.getNumber());
        } else if ("sprad".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("radius", Float.valueOf(model.getNumber().floatValue()));
        } else if ("stroke".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("strokeWidth", Float.valueOf(model.getNumber().floatValue()));
        } else if ("arrowpos".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("arrowPosition", Float.valueOf(Math.abs(model.getNumber().floatValue())));
        } else if ("rotation".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("rotationAngle", (Integer) model.getNumber());
        } else if ("spx4".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("x4", (Integer) model.getNumber());
        } else if ("spx3".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("x3", (Integer) model.getNumber());
        } else if ("spx2".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("x2", (Integer) model.getNumber());
        } else if ("spy4".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("y4", (Integer) model.getNumber());
        } else if ("spy3".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("y3", (Integer) model.getNumber());
        } else if ("spy2".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("y2", (Integer) model.getNumber());
        } else if ("dlsep".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("DLSeparation", Float.valueOf(model.getNumber().floatValue()));
        } else if ("dash".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("dash", Float.valueOf(model.getNumber().floatValue()));
        } else if ("amp".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("amp", (Integer) model.getNumber());
        } else if ("wiggle".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("frequencyFromWiggles", (Integer) model.getNumber());
        } else if ("scale".equals(name)) {
            final SpinnerNumberModel model =
                (SpinnerNumberModel) ((JSpinner) component).getModel();
            setParameter("newScale", Float.valueOf(Math.abs(model.getNumber().floatValue())));
            model.setValue(Double.valueOf(1.d));
        }

        fireStateChanged();
    }

    /**
     * Transmits the changes from an edit panel to the object.
     * Currently only used for the color buttons to bring up
     * the color chooser panel.
     * @param evt The ActionEvent to transmit.
     */
    public void actionPerformed(final ActionEvent evt) {
        final AbstractButton pressedButton = (AbstractButton) evt.getSource();
        final JaxoColorButton colorButton = pressedButton instanceof JaxoColorButton
            ? (JaxoColorButton) pressedButton : null;

        final JaxoColorChooser colorChooser = new JaxoColorChooser(pressedButton);
        final int colorMode = JaxoPrefs.getIntPref(JaxoPrefs.PREF_COLORSPACE);

        if ("Fill_color".equals(pressedButton.getActionCommand())) {
            colorChooser.setMode(colorMode == JaxoColor.ALL_COLORS_MODE
            ? JaxoColor.ALL_COLORS_MODE : JaxoColor.JAXO_COLORS_MODE);
            colorChooser.setOptional(false);
            colorChooser.setColor(colorButton.getColor());
            colorChooser.show();

            final Color c = colorChooser.getColor();
            colorButton.setColor(c);
            // need to set line color _before_ fill color
            if (JaxoColor.isGrayScale(c)) {
                setParameter("color", JaxoColor.BLACK);
            }
            setParameter("fillColor", c);
        } else if ("Filled".equals(pressedButton.getActionCommand())) {
            final JCheckBox fillcb = (JCheckBox) evt.getSource();
            setParameter("filled", Boolean.valueOf(fillcb.isSelected()));
        } else {
            int mode = JaxoColor.JAXO_COLORS_MODE;

            if ("Line_color".equals(pressedButton.getActionCommand())) {
                mode = JaxoColor.JAXO_COLORS_NO_GRAYSCALES_MODE;
            }

            colorChooser.setMode(colorMode == JaxoColor.ALL_COLORS_MODE
                    ? JaxoColor.ALL_COLORS_MODE : mode);

            colorChooser.setOptionalColor(colorButton.getColor());
            colorChooser.show();

            final Color c = colorChooser.getColor();
            colorButton.setColor(c);
            setParameter("color", c);
        }

        fireStateChanged();
    }

    /** Checks for ItemEvents that have ocurred on the panel,
     *  (check boxes, comboboxes and radiobuttons).
     * @param evt  The ItemEvent that has ocurred.
     */
    public final void itemStateChanged(final ItemEvent evt) {
        final Component component = (Component) evt.getSource();
        final String name = component.getName();

        if ("arrow".equals(name)) {
            final JCheckBox arrowcb = (JCheckBox) component;
            setParameter("paintArrow", Boolean.valueOf(arrowcb.isSelected()));
        } else if ("flip".equals(name)) {
            final JCheckBox flipcb = (JCheckBox) component;
            setParameter("flip", Boolean.valueOf(flipcb.isSelected()));
        } else if ("middle".equals(name)) {
            setParameter("arrowPosition", Float.valueOf(0.5f));
        } else if ("end".equals(name)) {
            setParameter("arrowPosition", Float.valueOf(1.f));
        } else if ("dLine".equals(name)) {
            final JCheckBox dlcb = (JCheckBox) component;
            setParameter("doubleLine", Boolean.valueOf(dlcb.isSelected()));
        } else if ("symm".equals(name)) {
            final JCheckBox symmcb = (JCheckBox) component;
            setParameter("symmetric", Boolean.valueOf(symmcb.isSelected()));
        } else if ("stretch".equals(name)) {
            final JCheckBox stretchcb = (JCheckBox) component;
            setParameter("noFreqStretching", Boolean.valueOf(stretchcb.isSelected()));
        } else if ("psfontname".equals(name)) {
            if (evt.getStateChange() != ItemEvent.SELECTED) {
                return;
            }
            final JComboBox cb = (JComboBox) component;
            setParameter("fontName", (String) cb.getSelectedItem());
        } else if ("psfontsize".equals(name)) {
            if (evt.getStateChange() != ItemEvent.SELECTED) {
                return;
            }
            final JComboBox cb = (JComboBox) component;
            setParameter("fontSize", (Integer) cb.getSelectedItem());
        } else if ("psfontstyle".equals(name)) {
            if (evt.getStateChange() != ItemEvent.SELECTED) {
                return;
            }
            final JComboBox cb = (JComboBox) component;
            setParameter("fontStyle", (Integer) cb.getSelectedItem());
        } else if ("latexalign".equals(name)) {
            if (evt.getStateChange() != ItemEvent.SELECTED) {
                return;
            }
            final JComboBox cb = (JComboBox) component;
            setParameter("allign", Integer.valueOf(cb.getSelectedIndex()));
        } else if ("latexfontsize".equals(name)) {
            if (evt.getStateChange() != ItemEvent.SELECTED) {
                return;
            }
            final JComboBox cb = (JComboBox) component;
            setParameter("latexTextSize", Integer.valueOf(cb.getSelectedIndex()));
        }

        fireStateChanged();
    }

    /** Checks for DocumentEvents that have ocurred on the panel,
     * (used for text fields).
     * This method is called after an insert into the document.
     * @param evt  The DocumentEvent that has ocurred.
     */
    public void insertUpdate(final DocumentEvent evt) {
        try {
            final String s = evt.getDocument().getText(0, evt.getDocument().getLength());
            setParameter("textString", s);
        } catch (BadLocationException e) {
            JaxoLog.debug(e);
        }

        fireStateChanged();
    }

    /** Checks for DocumentEvents that have ocurred on the panel,
     * (used for text fields).
     * This method is called after a removal from the document.
     * @param evt  The DocumentEvent that has ocurred.
     */
    public void removeUpdate(final DocumentEvent evt) {
        try {
            final String s = evt.getDocument().getText(0, evt.getDocument().getLength());
            setParameter("textString", s);
        } catch (BadLocationException e) {
            JaxoLog.debug(e);
        }

        fireStateChanged();
    }

    /** Checks for DocumentEvents that have ocurred on the panel,
     * (used for text fields).
     * This method is called after one or more attributes have changed.
     * This method is not called when characters are inserted with attributes.
     * @param evt  The DocumentEvent that has ocurred.
     */
    public void changedUpdate(final DocumentEvent evt) {
        // we have no text attributes
    }
}
