/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with Icy. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package icy.gui.sequence.tools;

import icy.gui.component.RangeComponent;
import icy.gui.component.sequence.SequencePreviewPanel;
import icy.sequence.DimensionId;
import icy.sequence.SequenceModel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.EventListener;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SequenceDimensionAdjustPanel extends JPanel
{
    /**
     * 
     */
    private static final long serialVersionUID = 5670426025630944193L;

    public interface RangeChangeListener extends EventListener
    {
        public void rangeChanged();
    }

    protected DimensionId dim;

    // GUI
    protected SequencePreviewPanel sequencePreviewPanel;
    protected RangeComponent range;
    private JPanel extractionRulePanel;
    protected JSpinner extractSpinner;
    protected JSpinner loopSpinner;

    // internal
    double oldLow;
    double oldHigh;

    /**
     * Create the panel.
     */
    public SequenceDimensionAdjustPanel(DimensionId dim)
    {
        super();

        if ((dim != DimensionId.Z) && (dim != DimensionId.T))
            throw new IllegalArgumentException("Only Z or T dimension allowed");

        this.dim = dim;

        initialize();

        if (dim == DimensionId.Z)
        {
            sequencePreviewPanel.getMainPanel().setBorder(
                    new TitledBorder(null, "Select Z interval", TitledBorder.LEADING, TitledBorder.TOP, null, null));
            range = new RangeComponent(SwingConstants.VERTICAL);
            range.setToolTipText("Select Z interval");
        }
        else
        {
            sequencePreviewPanel.getMainPanel().setBorder(
                    new TitledBorder(null, "Select T interval", TitledBorder.LEADING, TitledBorder.TOP, null, null));
            range = new RangeComponent(SwingConstants.HORIZONTAL);
            range.setToolTipText("Select T interval");
        }

        range.setMinMaxStep(0, 0, 1);
        range.setVisible(false);
        range.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                final double low = range.getLow();
                final double high = range.getHigh();

                if (oldLow != low)
                {
                    // low value changed
                    if (SequenceDimensionAdjustPanel.this.dim == DimensionId.Z)
                        sequencePreviewPanel.setPositionZ((int) low);
                    else
                        sequencePreviewPanel.setPositionT((int) low);
                }
                else if (oldHigh != high)
                {
                    // high value changed
                    if (SequenceDimensionAdjustPanel.this.dim == DimensionId.Z)
                        sequencePreviewPanel.setPositionZ((int) high);
                    else
                        sequencePreviewPanel.setPositionT((int) high);
                }

                oldLow = low;
                oldHigh = high;

                fireRangeChangedEvent();
            }
        });

        if (dim == DimensionId.Z)
        {
            // replace z slider by z range component
            final JPanel panel = sequencePreviewPanel.getZPanel();
            panel.removeAll();
            panel.setLayout(new BorderLayout());
            panel.add(range, BorderLayout.CENTER);
        }
        else
        {
            // replace t slider by t range component
            final JPanel panel = sequencePreviewPanel.getTPanel();
            panel.removeAll();
            panel.setLayout(new BorderLayout());
            panel.add(range, BorderLayout.CENTER);
        }

        extractSpinner.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                final SpinnerNumberModel model = (SpinnerNumberModel) loopSpinner.getModel();
                final int currentValue = ((Integer) model.getValue()).intValue();
                final int min = getExtractValue();

                // adjust minimum value
                loopSpinner.setModel(new SpinnerNumberModel(Math.max(min, currentValue), min, (int) range.getMax(), 1));

                fireRangeChangedEvent();
            }
        });
        loopSpinner.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                fireRangeChangedEvent();
            }
        });
    }

    private void initialize()
    {
        setLayout(new BorderLayout(0, 0));

        sequencePreviewPanel = new SequencePreviewPanel();
        add(sequencePreviewPanel, BorderLayout.CENTER);

        extractionRulePanel = new JPanel();
        extractionRulePanel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Extraction rule",
                TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
        add(extractionRulePanel, BorderLayout.SOUTH);
        GridBagLayout gbl_extractionRulePanel = new GridBagLayout();
        gbl_extractionRulePanel.columnWidths = new int[] {0, 0, 0, 0, 0, 0};
        gbl_extractionRulePanel.rowHeights = new int[] {20, 0};
        gbl_extractionRulePanel.columnWeights = new double[] {0.0, 1.0, 0.0, 1.0, 2.0, Double.MIN_VALUE};
        gbl_extractionRulePanel.rowWeights = new double[] {0.0, Double.MIN_VALUE};
        extractionRulePanel.setLayout(gbl_extractionRulePanel);

        JLabel lblKeep = new JLabel("Keep");
        GridBagConstraints gbc_lblKeep = new GridBagConstraints();
        gbc_lblKeep.anchor = GridBagConstraints.EAST;
        gbc_lblKeep.insets = new Insets(0, 0, 0, 5);
        gbc_lblKeep.gridx = 0;
        gbc_lblKeep.gridy = 0;
        extractionRulePanel.add(lblKeep, gbc_lblKeep);

        extractSpinner = new JSpinner();
        extractSpinner.setModel(new SpinnerNumberModel(new Integer(1), new Integer(1), null, new Integer(1)));
        extractSpinner
                .setToolTipText("Number of frames to keep every N frames. 'Keep 1 every 2' means keep every other frame.");
        GridBagConstraints gbc_extractSpinner = new GridBagConstraints();
        gbc_extractSpinner.fill = GridBagConstraints.HORIZONTAL;
        gbc_extractSpinner.anchor = GridBagConstraints.NORTH;
        gbc_extractSpinner.insets = new Insets(0, 0, 0, 5);
        gbc_extractSpinner.gridx = 1;
        gbc_extractSpinner.gridy = 0;
        extractionRulePanel.add(extractSpinner, gbc_extractSpinner);

        JLabel lblEvery = new JLabel("every");
        GridBagConstraints gbc_lblEvery = new GridBagConstraints();
        gbc_lblEvery.anchor = GridBagConstraints.EAST;
        gbc_lblEvery.insets = new Insets(0, 0, 0, 5);
        gbc_lblEvery.gridx = 2;
        gbc_lblEvery.gridy = 0;
        extractionRulePanel.add(lblEvery, gbc_lblEvery);

        loopSpinner = new JSpinner();
        loopSpinner.setModel(new SpinnerNumberModel(new Integer(1), new Integer(1), null, new Integer(1)));
        loopSpinner.setToolTipText("Size for the extraction rule loop.");
        GridBagConstraints gbc_loopSpinner = new GridBagConstraints();
        gbc_loopSpinner.fill = GridBagConstraints.HORIZONTAL;
        gbc_loopSpinner.insets = new Insets(0, 0, 0, 5);
        gbc_loopSpinner.anchor = GridBagConstraints.NORTH;
        gbc_loopSpinner.gridx = 3;
        gbc_loopSpinner.gridy = 0;
        extractionRulePanel.add(loopSpinner, gbc_loopSpinner);
    }

    public DimensionId getDimensionId()
    {
        return dim;
    }

    public boolean isPreviewVisible()
    {
        return sequencePreviewPanel.isVisible();
    }

    public void setPreviewVisible(boolean value)
    {
        sequencePreviewPanel.setVisible(value);
    }

    private void setMax(int value)
    {
        range.setMinMaxStep(0, value, 1);
        range.setLowHigh(0, value);
        range.setVisible(value > 0);

        extractSpinner.setModel(new SpinnerNumberModel(1, 1, Math.max(value, 1), 1));
        loopSpinner.setModel(new SpinnerNumberModel(1, 1, Math.max(value, 1), 1));
    }

    public SequenceModel getModel()
    {
        return sequencePreviewPanel.getModel();

    }

    public void setModel(SequenceModel model)
    {
        if (getModel() != model)
        {
            sequencePreviewPanel.setModel(model);
            dimensionChangedInternal();
        }
    }

    public void dimensionChanged()
    {
        dimensionChangedInternal();
        sequencePreviewPanel.dimensionChanged();
    }

    private void dimensionChangedInternal()
    {
        final SequenceModel model = getModel();

        if (model != null)
        {
            if (dim == DimensionId.Z)
                setMax(Math.max(0, model.getSizeZ() - 1));
            else
                setMax(Math.max(0, model.getSizeT() - 1));
        }
        else
            setMax(0);
    }

    public void imageChanged()
    {
        sequencePreviewPanel.imageChanged();
    }

    public int getRangeLow()
    {
        return (int) range.getLow();
    }

    public int getRangeHigh()
    {
        return (int) range.getHigh();
    }

    public boolean isIndexSelected(int index)
    {
        if (index < getRangeLow())
            return false;
        if (index > getRangeHigh())
            return false;

        return ((index - getRangeLow()) % getLoopValue()) < getExtractValue();
    }

    public int getExtractValue()
    {
        return ((Integer) extractSpinner.getValue()).intValue();
    }

    public int getLoopValue()
    {
        return ((Integer) loopSpinner.getValue()).intValue();
    }

    protected void fireRangeChangedEvent()
    {
        for (RangeChangeListener listener : getListeners(RangeChangeListener.class))
            listener.rangeChanged();
    }

    public void addRangeChangeListener(RangeChangeListener listener)
    {
        listenerList.add(RangeChangeListener.class, listener);
    }

    public void removeRangeChangeListener(RangeChangeListener listener)
    {
        listenerList.remove(RangeChangeListener.class, listener);
    }
}