/**
 *  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.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.lang.ref.WeakReference;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import net.sf.jaxodraw.object.arrow.JaxoArrow;
import net.sf.jaxodraw.object.JaxoObject;
import net.sf.jaxodraw.object.JaxoObjectEditPanel;

import net.sf.jaxodraw.util.JaxoColor;
import net.sf.jaxodraw.util.JaxoLanguage;
import net.sf.jaxodraw.util.JaxoPrefs;
import net.sf.jaxodraw.util.JaxoUtils;
import net.sf.jaxodraw.util.Location;


/** A parent class for all option panels.
 * @since 2.0
 */
public class JaxoOptionsPanel extends JPanel implements JaxoObjectEditPanel {
    private static List<Integer> rotationAngles;

    private static final long serialVersionUID = 7526471155622776147L;

    static {
        rotationAngles = new ArrayList<Integer>(4);
        rotationAngles.add(Integer.valueOf(-135));
        rotationAngles.add(Integer.valueOf(-90));
        rotationAngles.add(Integer.valueOf(-45));
        rotationAngles.add(Integer.valueOf(0));
        rotationAngles.add(Integer.valueOf(45));
        rotationAngles.add(Integer.valueOf(90));
        rotationAngles.add(Integer.valueOf(135));
        rotationAngles.add(Integer.valueOf(180));

        rotationAngles = Collections.unmodifiableList(rotationAngles);
    }

    private final GridBagConstraints panelConstraints;
    private JaxoEditPanelListener internalListener;
    private JaxoObject object;
    private JaxoObject backup;
    private String title;
    private Icon icon;
    private ChangeEvent event;

    /**
     * Constructor: initializes the panel.
     *
     * @param ob The object to be edited by this panel.
     */
    public JaxoOptionsPanel(final JaxoObject ob) {
        super(new GridBagLayout());
        panelConstraints = new GridBagConstraints();

        panelConstraints.insets = new Insets(5, 5, 5, 5);

        setObject(ob);

//      Not yet: The panels have to be checked whether they support it properly
//        panelConstraints.weightx = 1;
//        panelConstraints.weighty = 1;
//        panelConstraints.fill = GridBagConstraints.BOTH;
    }

    /** List of numbers of default rotation angles to display to the user.
     * @return List
     */
    public static List<Integer> getRotationAngleDefaults() {
        return rotationAngles;
    }

    /** {@inheritDoc} */
    public void addChangeListener(final ChangeListener l) {
        listenerList.add(ChangeListener.class, l);
    }

    /**
     * Removes a change listener.
     * @param l The listener to remove.
     */
    public void removeChangeListener(final ChangeListener l) {
        listenerList.remove(ChangeListener.class, l);
    }

    /**
     * Notifies all listeners of a state change.
     */
    protected void fireStateChanged() {
        final Object[] pairs = listenerList.getListenerList();

        for (int i = pairs.length - 2; i >= 0; i -= 2) {
            if (pairs[i] == ChangeListener.class) {
                if (event == null) {
                    event = new ChangeEvent(this);
                }
                ((ChangeListener) pairs[i + 1]).stateChanged(event);
            }
        }
    }

    /** Set the object that is being edited.
     * @param value the object that is being edited.
     */
    public final void setObject(final JaxoObject value) {
        if (value != object) {
            object = value;
            backup = object.copy();

            internalListener =
                new JaxoOptionsPanelListener(object,
                    new ChangeListener() {
                        public void stateChanged(final ChangeEvent e) {
                            fireStateChanged();
                        }
                    });

            updatePanels();
        }
    }

    /** The object that is being edited.
     * @return the currently edited object.
     */
    public final JaxoObject getObject() {
        return object;
    }

    /** {@inheritDoc} */
    public final void add3PointsPanel(final Point[] points, final int gridx, final int gridy) {
        add3PointsPanel(points, gridx, gridy, 1);
    }

    /** {@inheritDoc} */
    public final void add3PointsPanel(final Point[] points, final int gridx, final int gridy,
        final int gridwidth) {
        final Jaxo3PointsEditPanel panel = new Jaxo3PointsEditPanel(points);
        addPanel(panel, gridx, gridy, gridwidth);
    }

    /** {@inheritDoc} */
    public final void add4PointsPanel(final Point[] points, final int gridx, final int gridy) {
        add4PointsPanel(points, gridx, gridy, 1);
    }

    /** {@inheritDoc} */
    public final void add4PointsPanel(final Point[] points, final int gridx,
        final int gridy, final int gridwidth) {
        final Jaxo4PointsEditPanel panel = new Jaxo4PointsEditPanel(points);
        addPanel(panel, gridx, gridy, gridwidth);
    }

    /** {@inheritDoc} */
    public final void addPositionPanel(final int x, final int y, final int gridx, final int gridy) {
        final JaxoPositionEditPanel panel = new JaxoPositionEditPanel(x, y);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void add2PointsPanel(final Point[] points, final int gridx, final int gridy) {
        final Jaxo2PointsEditPanel panel = new Jaxo2PointsEditPanel(points);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addXYRPanel(final int x, final int y, final int r, final int gridx, final int gridy) {
        final JaxoXYREditPanel panel = new JaxoXYREditPanel(x, y, r);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addCenterSizePanel(final int x, final int y, final int w, final int h, final int gridx,
        final int gridy) {
        final JaxoCenterSizeEditPanel panel = new JaxoCenterSizeEditPanel(x, y, w, h);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addDashPanel(final float dash, final int gridx, final int gridy) {
        final JaxoDashEditPanel panel = new JaxoDashEditPanel(dash);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addStrokePanel(final float width, final int gridx, final int gridy) {
        final JaxoStrokeEditPanel panel = new JaxoStrokeEditPanel(width);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addReScalePanel(final int gridx, final int gridy) {
        final JaxoScaleEditPanel finalScalePanel = new JaxoScaleEditPanel();
        addPanel(finalScalePanel, gridx, gridy);
    }

    /** {@inheritDoc} */
   public final void addWigglePanel(final int amp, final int wiggles, final int gridx, final int gridy) {
        final JaxoWiggleEditPanel panel = new JaxoWiggleEditPanel(amp, wiggles);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addWigglePanel(final int amp, final int gridx, final int gridy) {
        final JaxoWiggleEditPanel wigglePanel = new JaxoWiggleEditPanel(amp);
        addPanel(wigglePanel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addSymmPanel(final boolean symm, final int gridx, final int gridy) {
        final JaxoSymmEditPanel symPanel = new JaxoSymmEditPanel(symm);
        addPanel(symPanel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addStretchingPanel(final boolean noFreqStretching, final int gridx, final int gridy) {
        final JaxoStretchingEditPanel panel = new JaxoStretchingEditPanel(noFreqStretching);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addRotationPanel(final int angle, final int gridx, final int gridy) {
        final JaxoRotationEditPanel panel = new JaxoRotationEditPanel(angle);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addArrowPanel(final boolean arrow, final boolean flip, final float oldPosition,
        final int gridx, final int gridy) {
        final JaxoArrowEditPanel arrowPanel = new JaxoArrowEditPanel(arrow, flip, oldPosition);
        addPanel(arrowPanel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addArrowOptionsEditPanel(final JaxoArrow arrow, final int gridx,
        final int gridy) {
        addPanel(arrow.getEditPanel(), gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addDoubleLinePanel(final boolean dLine, final float dlSep, final int gridx,
        final int gridy) {
        final JaxoDLEditPanel dlPanel = new JaxoDLEditPanel(dLine, dlSep);
        addPanel(dlPanel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addLineColorPanel(final Color color, final int gridx, final int gridy) {
        final int type = TYPE_LINE_COLOR;
        final JaxoColorEditPanel panel = new JaxoColorEditPanel(type, false, color);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addFillLineColorPanels(final Color color, final Color fillColor,
        final boolean isFilled, final int fillX, final int fillY, final int lineX, final int lineY) {
        final boolean disable = JaxoColor.isGrayScale(fillColor);

        final JaxoColorEditPanel linePanel =
            new JaxoColorEditPanel(TYPE_LINE_COLOR, disable, color);

        addPanel(linePanel, lineX, lineY);

        final JaxoColorEditPanel fillPanel =
            new JaxoColorEditPanel(false, fillColor, isFilled);

        addPanel(fillPanel, fillX, fillY);

        final WeakReference<JaxoColorEditPanel> weakLinePanel = new WeakReference<JaxoColorEditPanel>(linePanel);
        final WeakReference<JaxoColorEditPanel> weakFillPanel = new WeakReference<JaxoColorEditPanel>(fillPanel);

        addChangeListener(new ChangeListener() {
            public void stateChanged(final ChangeEvent e) {
                final JaxoColorEditPanel l = weakLinePanel.get();
                final JaxoColorEditPanel f = weakFillPanel.get();

                if (l == null || f == null) {
                    removeChangeListener(this);
                } else {
                    if (JaxoPrefs.getIntPref(JaxoPrefs.PREF_COLORSPACE)
                    == JaxoColor.ALL_COLORS_MODE) {
                        l.setEnabled(true);
                    } else {
                        final boolean isGrayScale = JaxoColor.isGrayScale(f.getColor());

                        if (isGrayScale) {
                            l.setColor(JaxoColor.BLACK);
                        }
                        l.setEnabled(!isGrayScale);
                    }
                }
            }
        });
    }

    /** {@inheritDoc} */
    public final void addColorPanel(final Color color, final int type, final int gridx, final int gridy) {
        final JaxoColorEditPanel colorPanel =
                new JaxoColorEditPanel(type, false, color);
        addPanel(colorPanel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addTextPanel(final String text, final int gridx, final int gridy) {
        final JaxoTextEditPanel textPanel = new JaxoTextEditPanel(text);
        addPanel(textPanel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addPSFontPanel(final Font font, final int gridx, final  int gridy,
        final int gridwidth) {
        final JaxoPSFontPanel fontPanel = new JaxoPSFontPanel(font);
        addPanel(fontPanel, gridx, gridy, gridwidth);
    }

    /** {@inheritDoc} */
    public final void addLatexAlignPanel(final int allign, final int gridx, final int gridy) {
        final JaxoLatexAlignPanel panel = new JaxoLatexAlignPanel(allign);
        addPanel(panel, gridx, gridy);
    }

    /** {@inheritDoc} */
    public final void addLatexFontSizePanel(final int size, final int gridx, final int gridy) {
        final JaxoLatexFontSizePanel panel = new JaxoLatexFontSizePanel(size);
        addPanel(panel, gridx, gridy);
    }

/////////////////////////////////////////////////////////

    /** Adds the current listeners to the given subpanel
     * and adds it at the given position.
     * @param panel The panel to add.
     * @param gridx The x position of the subpanel.
     * @param gridy The y position of the subpanel.
     */
    private void addPanel(final JaxoEditPanel panel, final int gridx, final int gridy) {
        addPanel(panel, gridx, gridy, 1);
    }

    /**
     * Adds the current listeners to the given subpanel
     * and adds it at the given position. If panel is null, does nothing.
     *
     * @param panel The panel to add.
     * @param gridx The x position of the subpanel.
     * @param gridy The y position of the subpanel.
     * @param gridwidth The width of the subpanel.
     */
    private void addPanel(final JaxoEditPanel panel, final int gridx, final int gridy,
        final int gridwidth) {

        if (panel == null) {
            return;
        }

        panel.addEditPanelListener(internalListener);

        panelConstraints.gridx = gridx;
        panelConstraints.gridy = gridy;
        panelConstraints.gridwidth = gridwidth;
        add(panel, panelConstraints);
    }

    /** {@inheritDoc} */
    public final void show(final Component parent, final Location l) {
        showDialog(parent, l);
    }

    /** {@inheritDoc} */
    public boolean hasChanged() {
        return (backup != null) && !backup.isCopy(object);
    }

    /** {@inheritDoc} */
    public void setTitleAndIcon(final String newTitle, final String newIcon) {
        this.title = JaxoLanguage.translate(newTitle);
        this.icon = JaxoUtils.newImageIcon(newIcon);
    }

    /** Reset 'object' (and GUI) to initial value. */
    protected void resetObject() {
        object.setState(backup);
        updatePanels();
    }

    private void updatePanels() {
        removeAll();
        object.prepareEditPanel(this);
        revalidate();
        repaint();
        fireStateChanged();
    }

    /**
     * Show a dialog for this panel.
     * The current implementation blocks until the dialog is closed again.
     *
     * @param parent the parent component.
     * @param l the location.
     */
    protected final void showDialog(final Component parent, final Location l) {
        final JButton reset = new JButton(JaxoLanguage.translate("Reset"));

        reset.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    resetObject();
                }
            });

        final Object[] optionButtons = {
            JaxoLanguage.translate("Accept"),
            reset,
            JaxoLanguage.translate("Cancel")
        };

        final JOptionPane p =
            new JOptionPane(this, JOptionPane.PLAIN_MESSAGE,
                JOptionPane.OK_CANCEL_OPTION, icon, optionButtons,
                optionButtons[0]);

        final JDialog d = p.createDialog(parent, title);

        l.setLocation(d);
        d.setVisible(true);
        d.dispose();

        if (!optionButtons[0].equals(p.getValue())) {
            object.setState(backup);
            fireStateChanged();
        }
    }
}
