
package uk.co.wingpath.modbusgui;

import java.io.*;
import java.util.*;
import java.util.List;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.event.Event;
import uk.co.wingpath.util.*;
import uk.co.wingpath.gui.*;
import uk.co.wingpath.xml.*;

final class CommandList
    implements Xml.Savable
{
    private final ArrayList<Command> commands;
    private final Map<String,Command> nameMap;
    private final Map<Command,Integer> indexMap;
    private final Variable<Command> selectedCommand;
    private final ComboModel comboModel;
    private int lastSelectedIndex;
    private final ValueEventSource valueListeners;
    private final ListEventSource listListeners;

    CommandList ()
    {
        commands = new ArrayList<Command> ();
        nameMap = new TreeMap<String,Command> ();
        indexMap = new IdentityHashMap<Command,Integer> ();
        selectedCommand = new SimpleVariable<Command> (null);
        comboModel = new ComboModel ();
        valueListeners = new ValueEventSource ();
        listListeners = new ListEventSource ();
        lastSelectedIndex = -1;

        selectedCommand.addValueListener (new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    Command command = selectedCommand.getValue ();
                    if (command != null)
                        lastSelectedIndex = getIndex (command);
                }
            });
    }

    Command getCommand (String name)
    {
        return nameMap.get (name);
    }

    public Command getCommandAt (int index)
    {
        return index >= commands.size () ? null : commands.get (index);
    }

    public int getIndex (Command command)
    {
        return indexMap.get (command);
    }

    public int getSize ()
    {
        return commands.size ();
    }

    private void ensureCommandSelected ()
    {
        int size = commands.size ();
        if (selectedCommand.getValue () == null && size != 0)
        {
            if (lastSelectedIndex < 0)
                lastSelectedIndex = 0;
            else if (lastSelectedIndex >= size)
                lastSelectedIndex = size - 1;
            Command command = getCommandAt (lastSelectedIndex);
            selectedCommand.setValue (command);
        }
    }

    private void rebuildIndexMap ()
    {
        indexMap.clear ();

        for (int i = 0 ; i < commands.size () ; i++)
            indexMap.put (commands.get (i), i);
    }

    public void replaceCommand (Command oldCommand, Command newCommand)
    {
        int index = getIndex (oldCommand);
        commands.set (index, newCommand);
        nameMap.remove (oldCommand.toString ());
        nameMap.put (newCommand.toString (), newCommand);
        Integer i = indexMap.remove (oldCommand);
        assert i != null;
        assert i == index;
        indexMap.put (newCommand, index);
        if (selectedCommand.getValue () == oldCommand)
            selectedCommand.setValue (newCommand);
        listListeners.fireChanged (this, index, index);
    }

    public void addCommand (final Command command)
    {
        Event.checkIsEventDispatchThread ();
        String name = command.toString ();
        assert nameMap.get (name) == null;
        int index = commands.size ();
        commands.add (command);
        nameMap.put (name, command);
        indexMap.put (command, index);
        listListeners.fireInserted (this, index, index);
        ensureCommandSelected ();
    }

    public void moveCommands (int firstIndex, int lastIndex, int targetIndex)
    {
        assert firstIndex <= lastIndex : firstIndex + " " + lastIndex;
        assert firstIndex >= 0 : firstIndex;
        assert lastIndex < commands.size () :
            lastIndex + " " + commands.size ();
        assert targetIndex >= 0 : targetIndex;
        assert targetIndex <= commands.size () :
            targetIndex + " " + commands.size ();

        Command [] cmds = new Command [lastIndex - firstIndex + 1];

        for (int i = 0 ; i < cmds.length ; i++)
            cmds [i] = commands.remove (firstIndex);

        rebuildIndexMap ();
        listListeners.fireRemoved (this, firstIndex, lastIndex);
        if (targetIndex >= firstIndex)
            targetIndex -= cmds.length;

        for (int i = 0 ; i < cmds.length ; i++)
            commands.add (targetIndex + i, cmds [i]);

        rebuildIndexMap ();
        listListeners.fireInserted (this,
            targetIndex, targetIndex + cmds.length - 1);
    }

    public void deleteCommand (Command command)
    {
        Event.checkIsEventDispatchThread ();
        selectedCommand.setValue (null);
        int index = getIndex (command);
        Command cmd = commands.remove (index);
        assert cmd != null;
        cmd = nameMap.remove (command.toString ());
        assert cmd != null;
        rebuildIndexMap ();
        listListeners.fireRemoved (this, index, index);
        ensureCommandSelected ();
    }

    public void deleteAll ()
    {
        Event.checkIsEventDispatchThread ();
        int size = commands.size ();
        if (size > 0)
        {
            selectedCommand.setValue (null);
            commands.clear ();
            nameMap.clear ();
            indexMap.clear ();
            listListeners.fireRemoved (this, 0, size - 1);
        }
    }

    public void addValueListener (ValueListener l)
    {
        valueListeners.addListener (l);
    }

    public void removeValueListener (ValueListener l)
    {
        valueListeners.removeListener (l);
    }

    public void fireValueChanged ()
    {
        valueListeners.fireValueChanged (this);
    }

    public void addListListener (ListDataListener l)
    {
        listListeners.addListener (l);
    }

    public void removeListListener (ListDataListener l)
    {
        listListeners.removeListener (l);
    }

    public Variable<Command> getSelectedCommand ()
    {
        return selectedCommand;
    }

    public Selector createSelector ()
    {
        return new Selector ();
    }

    public class Selector
        extends WAbstractComponent<Command>
    {
        private JComboBox combo;

        private Selector ()
        {
            combo = new JComboBox (comboModel);
            initialize (combo, "Command");
            Dimension prefSize = combo.getPreferredSize ();
            combo.setPreferredSize (
                new Dimension (Gui.getTextWidth (20), prefSize.height));
            final ListCellRenderer renderer = combo.getRenderer ();
            combo.setRenderer (new ListCellRenderer ()
            {
                public Component getListCellRendererComponent (JList list,
                    Object value, int index, boolean isSelected,
                    boolean cellHasFocus)
                {
                    JComponent comp =
                        (JComponent) renderer.getListCellRendererComponent (
                            list, value, index, isSelected, cellHasFocus);
                    if (index >= 0 && index < commands.size ())
                    {
                        comp.setToolTipText (
                            commands.get (index).getTypeName ());
                    }
                    return comp;
                }
            });
            setToolTipText ("Select saved Modbus command");
            setMnemonic (KeyEvent.VK_C);
        }

        @Override
        public void addValueListener (ValueListener l)
        {
            selectedCommand.addValueListener (l);
        }

        @Override
        public void removeValueListener (ValueListener l)
        {
            selectedCommand.removeValueListener (l);
        }

        public void setValue (Command value)
        {
            selectedCommand.setValue (value);
        }

        public Command getValue ()
        {
            return selectedCommand.getValue ();
        }
    }

    private class ComboModel
        implements ComboBoxModel
    {
        private ComboModel ()
        {
            selectedCommand.addValueListener (new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        // Swing Tutorial: "combo box models ... must fire
                        // a list data event (a CONTENTS_CHANGED event) when
                        // the selection changes".
                        // JComboBox doesn't seem to care about the interval,
                        // but other listeners might.
                        listListeners.fireChanged (CommandList.this, -1, -1);
                    }
                });
        }

        public Command getElementAt (int index)
        {
            if (index < 0 || index >= commands.size ())
                return null;
            return commands.get (index);
        }

        public int getSize ()
        {
            return commands.size ();
        }

        public void setSelectedItem (Object obj)
        {
            selectedCommand.setValue ((Command) obj);
        }

        public Object getSelectedItem ()
        {
            return selectedCommand.getValue ();
        }

        public void addListDataListener (ListDataListener l)
        {
            listListeners.addListener (l);
        }

        public void removeListDataListener (ListDataListener l)
        {
            listListeners.removeListener (l);
        }
    }

    public void save (Xml.Saver saver)
        throws IOException
    {
        for (Command command : commands)
            saver.saveValue (command.getTag (), command);
    }

    public Xml.Loader getXmlLoader (boolean isTester)
    {
        return new XmlLoader (isTester);
    }

    private class XmlLoader
        extends Xml.AbstractLoader
    {
        private final Xml.Receiver<Command> receiver;
        private final boolean isTester;

        private XmlLoader (boolean isTester)
        {
            this.isTester = isTester;
            receiver = new Xml.Receiver<Command> ()
            {
                public void receive (Command command)
                    throws ValueException
                {
                    String name = command.toString ();
                    Command cmd = getCommand (name);
                    if (cmd != null)
                        replaceCommand (cmd, command);
                    else
                        addCommand (command);
                }
            };
        }

        @Override
        public Xml.Loader startChild (String tag)
            throws ValueException
        {
            if (tag.equalsIgnoreCase (CommandCustom.tag))
                return CommandCustom.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (CommandRaw.tag))
                return CommandRaw.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command1.tag))
                return Command1.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command2.tag))
                return Command2.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command3.tag))
                return Command3.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command4.tag))
                return Command4.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command5.tag))
                return Command5.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command6.tag))
                return Command6.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command7.tag))
                return Command7.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command8.tag))
                return Command8.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command11.tag))
                return Command11.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command12.tag))
                return Command12.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command15.tag))
                return Command15.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command16.tag))
                return Command16.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command17.tag))
                return Command17.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command20.tag))
                return Command20.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command21.tag))
                return Command21.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command22.tag))
                return Command22.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command23.tag))
                return Command23.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command24.tag))
                return Command24.getXmlLoader (receiver, isTester);
            if (tag.equalsIgnoreCase (Command43.tag))
                return Command43.getXmlLoader (receiver, isTester);
            return null;
        }

        @Override
        public void cleanup ()
        {
            listListeners.fireChanged (CommandList.this, -1, 0);
            ensureCommandSelected ();
        }
    }
}


