/*
 * 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.component.sequence;

import icy.common.listener.AcceptListener;
import icy.gui.main.GlobalSequenceListener;
import icy.gui.util.ComponentUtil;
import icy.main.Icy;
import icy.sequence.Sequence;
import icy.util.StringUtil;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;

/**
 * The sequence chooser is a component derived from JComboBox. <br>
 * The combo auto refresh its content regarding to the sequence opened in ICY.<br>
 * You can get it with getSequenceSelected()
 * 
 * @author Fabrice de Chaumont & Stephane<br>
 */

public class SequenceChooser extends JComboBox implements GlobalSequenceListener
{
    public interface SequenceChooserListener
    {
        /**
         * Called when the sequence chooser selection changed for specified sequence.
         */
        public void sequenceChanged(Sequence sequence);
    }

    private class SequenceComboModel extends DefaultComboBoxModel
    {
        /**
         * 
         */
        private static final long serialVersionUID = -1402261337279171323L;

        /**
         * cached items list
         */
        final List<WeakReference<Sequence>> cachedList;

        public SequenceComboModel()
        {
            super();

            cachedList = new ArrayList<WeakReference<Sequence>>();
            updateList();
        }

        public void updateList()
        {
            // save selected item
            final Object selected = getSelectedItem();

            final int oldSize = cachedList.size();

            cachedList.clear();

            // add null entry at first position
            if (nullEntryName != null)
                cachedList.add(new WeakReference<Sequence>(null));

            final List<Sequence> sequences = Icy.getMainInterface().getSequences();

            // add active sequence entry at second position
            if ((sequences.size() > 0) && (activeSequence != null))
                cachedList.add(new WeakReference<Sequence>(activeSequence));

            // add others sequence
            for (Sequence seq : sequences)
                if ((filter == null) || filter.accept(seq))
                    cachedList.add(new WeakReference<Sequence>(seq));

            final int newSize = cachedList.size();

            // some elements has been removed
            if (newSize < oldSize)
                fireIntervalRemoved(this, newSize, oldSize - 1);
            // some elements has been added
            else if (newSize > oldSize)
                fireIntervalAdded(this, oldSize, newSize - 1);

            // and some elements changed
            fireContentsChanged(this, 0, newSize - 1);

            // restore selected item
            setSelectedItem(selected);
        }

        @Override
        public Object getElementAt(int index)
        {
            return cachedList.get(index).get();
        }

        @Override
        public int getSize()
        {
            return cachedList.size();
        }
    }

    private static final long serialVersionUID = -6108163762809540675L;

    public static final String SEQUENCE_SELECT_CMD = "sequence_select";

    /**
     * var
     */
    AcceptListener filter;
    final String nullEntryName;

    /**
     * listeners
     */
    protected final List<SequenceChooserListener> listeners;

    /**
     * internals
     */
    protected WeakReference<Sequence> previousSelectedSequence;
    protected final SequenceComboModel model;
    protected final Sequence activeSequence;

    /**
     * Create a new Sequence chooser component (JComboBox for sequence selection).
     * 
     * @param activeSequenceEntry
     *        If true the combobox will display an <i>Active Sequence</i> entry so when we select it
     *        the {@link #getSelectedSequence()} method returns the current active sequence.
     * @param nullEntryName
     *        If this parameter is not <code>null</code> the combobox will display an extra entry
     *        with the given string to define <code>null</code> sequence selection so when this
     *        entry will be selected the {@link #getSelectedSequence()} will return <code>null</code>.
     * @param nameMaxLength
     *        Maximum authorized length for the sequence name display in the combobox (extra
     *        characters are truncated).<br>
     *        That prevent the combobox to be resized to very large width.
     */
    public SequenceChooser(final boolean activeSequenceEntry, final String nullEntryName, final int nameMaxLength)
    {
        super();

        this.nullEntryName = nullEntryName;

        if (activeSequenceEntry)
            activeSequence = new Sequence("active sequence");
        else
            activeSequence = null;

        model = new SequenceComboModel();
        setModel(model);
        setRenderer(new ListCellRenderer()
        {
            @Override
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                    boolean cellHasFocus)
            {
                final JLabel result = new JLabel();

                if (value instanceof Sequence)
                {
                    final String name = ((Sequence) value).getName();

                    result.setText(StringUtil.limit(name, nameMaxLength));
                    result.setToolTipText(name);
                }
                else if (value == null)
                    result.setText(nullEntryName);

                return result;
            }
        });

        addActionListener(this);

        // default
        listeners = new ArrayList<SequenceChooserListener>();
        setActionCommand(SEQUENCE_SELECT_CMD);
        previousSelectedSequence = new WeakReference<Sequence>(null);
        setSelectedItem(null);

        // fix height
        ComponentUtil.setFixedHeight(this, 26);
    }

    /**
     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
     */
    @SuppressWarnings("unused")
    @Deprecated
    public SequenceChooser(final int sequenceNameMaxLength, final boolean nullEntry, final boolean autoSelectIfNull,
            final String nullEntryName)
    {
        this(false, nullEntry ? nullEntryName : null, sequenceNameMaxLength);
    }

    /**
     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
     */
    @SuppressWarnings("unused")
    @Deprecated
    public SequenceChooser(int maxLength, boolean nullEntry, boolean autoSelectIfNull)
    {
        this(false, nullEntry ? "no sequence" : null, maxLength);
    }

    /**
     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
     */
    @Deprecated
    public SequenceChooser(int maxLength, boolean nullEntry)
    {
        this(false, nullEntry ? "no sequence" : null, maxLength);
    }

    /**
     * Create a new Sequence chooser component (JComboBox for sequence selection).
     * 
     * @param activeSequenceEntry
     *        If true the combobox will display an <i>Active Sequence</i> entry so when we select it
     *        the {@link #getSelectedSequence()} method returns the current active sequence.
     * @param nullEntryName
     *        If this parameter is not <code>null</code> the combobox will display an extra entry
     *        with the given string to define <code>null</code> sequence selection so when this
     *        entry will be selected the {@link #getSelectedSequence()} will return <code>null</code>.
     */
    public SequenceChooser(boolean activeSequenceEntry, String nullEntryName)
    {
        this(activeSequenceEntry, nullEntryName, 64);
    }

    /**
     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
     */
    @Deprecated
    public SequenceChooser(int nameMaxLength)
    {
        this(false, "no sequence", nameMaxLength);
    }

    /**
     * Create a new Sequence chooser component (JComboBox for sequence selection).
     */
    public SequenceChooser()
    {
        this(true, null, 64);
    }

    @Override
    public void addNotify()
    {
        super.addNotify();

        Icy.getMainInterface().addGlobalSequenceListener(this);
    }

    @Override
    public void removeNotify()
    {
        Icy.getMainInterface().removeGlobalSequenceListener(this);

        super.removeNotify();
    }

    /**
     * @return the filter
     */
    public AcceptListener getFilter()
    {
        return filter;
    }

    /**
     * Set a filter for sequence display.<br>
     * Only Sequence accepted by the filter will appear in the combobox.
     */
    public void setFilter(AcceptListener filter)
    {
        if (this.filter != filter)
        {
            this.filter = filter;
            model.updateList();
        }
    }

    /**
     * @return current selected sequence.
     */
    public Sequence getSelectedSequence()
    {
        final Sequence result = (Sequence) getSelectedItem();

        // special case for active sequence
        if (result == activeSequence)
            return Icy.getMainInterface().getActiveSequence();

        return result;
    }

    /**
     * Select the <i>Active sequence</i> entry if enable.
     */
    public void setActiveSequenceSelected()
    {
        if (activeSequence != null)
            setSelectedItem(activeSequence);
    }

    /**
     * @param sequence
     *        The sequence to select in the combo box
     */
    public void setSelectedSequence(Sequence sequence)
    {
        if (sequence != getSelectedSequence())
            setSelectedItem(sequence);
    }

    /**
     * @deprecated
     *             use {@link #setSelectedSequence(Sequence)} instead
     */
    @Deprecated
    public void setSequenceSelected(Sequence sequence)
    {
        setSelectedSequence(sequence);
    }

    // called when sequence selection has changed
    private void sequenceChanged(Sequence sequence)
    {
        fireSequenceChanged(sequence);
    }

    private void fireSequenceChanged(Sequence sequence)
    {
        for (SequenceChooserListener listener : getListeners())
            listener.sequenceChanged(sequence);
    }

    public ArrayList<SequenceChooserListener> getListeners()
    {
        return new ArrayList<SequenceChooserListener>(listeners);
    }

    public void addListener(SequenceChooserListener listener)
    {
        if (!listeners.contains(listener))
            listeners.add(listener);
    }

    public void removeListener(SequenceChooserListener listener)
    {
        listeners.remove(listener);
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        final Sequence selected = getSelectedSequence();

        if (previousSelectedSequence.get() != selected)
        {
            previousSelectedSequence = new WeakReference<Sequence>(selected);
            // sequence changed
            sequenceChanged(selected);
        }
    }

    @Override
    public void sequenceOpened(Sequence sequence)
    {
        model.updateList();
    }

    @Override
    public void sequenceClosed(Sequence sequence)
    {
        model.updateList();
    }
}