
package uk.co.wingpath.modbusgui;

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

public class CommandDefine
    implements TreeCard
{
    private final Frontend frontend;
    private final boolean isTester;
    private final Backend backend;
    private final Settings settings;
    private final Variable<Integer> slaveId;
    private final Reporter reporterNSB;
    private final StatusBar statusBar;
    private final MirrorField mirror;
    private final JPanel outerPanel;
    private final JPanel cardPanel;
    private final WTextField nameField;
    private final WTextArea descriptionField;
    private final JButton sendButton;
    private final JButton saveButton;
    private final JButton resetButton;
    private final JButton expectActualButton;
    private final WComboBox<CommandPanel> commandSelector;
    private final ExceptionSelector expectedExceptionSelector;
    private final ExceptionField actualExceptionField;
    private final Map<String,CommandPanel> panelMap;
    private CommandPanel commandPanel;
    private final CommandList commands;
    private final ValueEventSource statusListeners;

    public CommandDefine (final Frontend frontend, final Backend backend,
        Settings settings, Reporter reporterNSB)
    {
        Event.checkIsEventDispatchThread ();
        this.frontend = frontend;
        this.backend = backend;
        this.settings = settings;
        this.reporterNSB = reporterNSB;
        isTester = frontend.getProduct ().isTester ();
        slaveId = settings.getGeneral ().getSlaveId ();
        statusListeners = new ValueEventSource ();
        commandPanel = null;

        statusBar = new StatusBar ("comdef", frontend.getHelpViewer ());
        statusBar.addStatusListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    fireStatusChanged ();
                }
            });

        mirror = new MirrorField ();

        commands = settings.getCommands ();

        List<CommandPanel> commandPanels = new ArrayList<CommandPanel> ();
        List<CommandPanel> items = new ArrayList<CommandPanel> ();
        List<Object> names = new ArrayList<Object> ();
        List<String> tooltips = new ArrayList<String> ();
        CommandPanel cp;
        CommandPanel initialCp = null;

        cp = new CommandCustomPanel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        names.add (new JSeparator ());
        items.add (null);
        tooltips.add (null);

        cp = new CommandRawPanel (frontend, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        items.add (cp);
        names.add (cp.getName ());
        tooltips.add (cp.getTitle ());

        names.add (new JSeparator ());
        items.add (null);
        tooltips.add (null);

        names.add ("Standard commands");
        items.add (null);
        tooltips.add (null);

        cp = new Command1Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command2Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command3Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());
        initialCp = cp;

        cp = new Command4Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command5Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command6Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command7Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command8Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command11Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command12Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command15Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command16Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command17Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command20Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command21Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command22Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command23Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command24Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        cp = new Command43Panel (frontend, settings, statusBar, mirror, isTester, false);
        commandPanels.add (cp);
        names.add (" " + cp.getName ());
        items.add (cp);
        tooltips.add (cp.getTitle ());

        panelMap = new HashMap<String,CommandPanel> ();

        for (CommandPanel c : commandPanels)
            panelMap.put (c.getTag (), c);

        outerPanel = new JPanel ();
        outerPanel.setLayout (new BorderLayout ());
        JLabel heading = Gui.createDialogHeading ("Define Command");
        outerPanel.add (heading, BorderLayout.NORTH);
        // outerPanel.add (statusBar, BorderLayout.SOUTH);

        JPanel mainPanel = new JPanel ();
        outerPanel.add (mainPanel, BorderLayout.CENTER);
        mainPanel.setLayout (new BoxLayout (mainPanel, BoxLayout.Y_AXIS));

        GridBagPanel topPanel = new GridBagPanel ();
        mainPanel.add (topPanel);

        commandSelector = new WComboBox<CommandPanel> ("Command Type",
            items.toArray (new CommandPanel [items.size ()]),
            names.toArray (new Object [names.size ()]));
        topPanel.addComponent (commandSelector);
        commandSelector.setMaximumRowCount (0);
        commandSelector.setToolTipText ("Select Modbus command type");
        commandSelector.setToolTipText (
            tooltips.toArray (new String [tooltips.size ()]));
        commandSelector.setMnemonic (KeyEvent.VK_C);

        commandSelector.addValueListener (new ValueListener ()
        {
            public void valueChanged (ValueEvent e)
            {
                CommandPanel cp = commandSelector.getValue ();
                if (cp != null)
                    setPanel (cp);
            }
        });

        nameField = new WTextField (statusBar, "Name");
        topPanel.addComponent (nameField);
        nameField.setToolTipText ("Name for saved command");
        nameField.setMnemonic (KeyEvent.VK_N);
        nameField.setWidthChars (20);
        nameField.setMirror (mirror);
        nameField.setVerifier (new Verifier ()
        {
            public String verify (String name, boolean isChanging)
            {
                setEnabled (false, name);
                if (isChanging)
                {
                    statusBar.clear ();
                    return name;
                }
                Command command = commands.getCommand (name);
                if (command != null)
                {
                    statusBar.showError ("Command '" + name +
                        "' is already defined");
                    return null;
                }
                return name;
            }
        });

        descriptionField = new WTextArea (statusBar, "Description");
        descriptionField.setToolTipText ("Description");
        descriptionField.setMnemonic (KeyEvent.VK_T);
        descriptionField.setRows (3);
        descriptionField.setWidthChars (40);
        topPanel.addComponent (descriptionField);

        cardPanel = new JPanel ();
        cardPanel.setLayout (new CardLayout ());
        mainPanel.add (cardPanel);

        for (CommandPanel c : commandPanels)
            cardPanel.add (c.getPanel (), c.getTag ());

        if (isTester)
        {
            GridBagPanel bottomPanel = new GridBagPanel ();
            mainPanel.add (bottomPanel);

            expectedExceptionSelector = new ExceptionSelector (
                "Expected Outcome");
            GridBagConstraints constraints = bottomPanel.createConstraints ();
            bottomPanel.add (expectedExceptionSelector.getLabel (),
                constraints);
            constraints.gridx = 1;
            constraints.weightx = 1.0;
            constraints.fill = GridBagConstraints.HORIZONTAL;
            bottomPanel.add (expectedExceptionSelector.getComponent (),
                constraints);

            actualExceptionField = new ExceptionField (statusBar,
                "Actual Outcome", frontend.getHelpViewer ());
            actualExceptionField.setWidthChars (20);
            constraints = bottomPanel.createConstraints ();
            bottomPanel.add (actualExceptionField.getLabel (), constraints);
            constraints.gridx = 1;
            constraints.weightx = 1.0;
            constraints.fill = GridBagConstraints.HORIZONTAL;
            bottomPanel.add (actualExceptionField.getComponent (), constraints);
        }
        else
        {
            expectedExceptionSelector = null;
            actualExceptionField = null;
        }

        ButtonPanel buttonPanel = new ButtonPanel ();
        mainPanel.add (buttonPanel);
        mainPanel.add (mirror.getComponent ());
        mainPanel.add (statusBar);

        saveButton = buttonPanel.addButton ("Save", null,
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    Event.checkIsEventDispatchThread ();
                    if (!commandPanel.checkFields ())
                        return;
                    if (!nameField.checkValue ())
                        return;
                    if (!descriptionField.checkValue ())
                        return;
                    String name = nameField.getValue ();
                    if (name.equals (""))
                    {
                        statusBar.showError ("Name missing");
                        return;
                    }
                    Command command = commands.getCommand (name);
                    if (command != null)
                    {
                        statusBar.showError ("Command '" + name +
                            "' is already defined");
                        return;
                    }
                    ModbusException exception = null;
                    if (isTester)
                    {
                        exception = expectedExceptionSelector.getValue ();
                        if (exception != null &&
                            exception.getErrorCode () == Modbus.ERROR_NONE)
                        {
                            exception = null;
                        }
                    }
                    command = commandPanel.createCommand (name,
                        descriptionField.getValue (), exception);
                    commands.addCommand (command);
                    commands.getSelectedCommand ().setValue (command);
                    nameField.setValue ("");
                    setEnabled ();
                }
            });
        saveButton.setMnemonic (KeyEvent.VK_V);
        saveButton.setToolTipText ("Add command definition to command table");
        saveButton.setEnabled (false);

        resetButton = buttonPanel.addButton ("Reset", null,
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    statusBar.clear ();
                    setCommand (null);
                }
            });
        resetButton.setMnemonic (KeyEvent.VK_R);
        resetButton.setToolTipText ("Reset fields to default values");

        sendButton = buttonPanel.addButton (isTester ? "Test" : "Send", null,
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    Event.checkIsEventDispatchThread ();
                    if (!commandPanel.checkFields ())
                        return;
                    Command command = commandPanel.createCommand (null, "",
                        isTester ? expectedExceptionSelector.getValue () :
                        null);
                    sendCommand (command);
                }
            });
        sendButton.setMnemonic (isTester? KeyEvent.VK_T : KeyEvent.VK_S);
        sendButton.setToolTipText ("Send command to slave");

        if (isTester)
        {
            expectActualButton = buttonPanel.addButton ("Expect actual", null,
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        statusBar.clear ();
                        ModbusException exception =
                            actualExceptionField.getValue ();
                        expectedExceptionSelector.setValue (exception);
                        // Clear any exception error highlighting.
                        actualExceptionField.setValue (exception, false);
                        commandPanel.expectActual ();
                        setEnabled ();
                    }
                });
            expectActualButton.setMnemonic (KeyEvent.VK_E);
            expectActualButton.setToolTipText ("Use actual values as expected");
            Gui.setEnabled (expectActualButton, false);
        }
        else
        {
            expectActualButton = null;
        }

        buttonPanel.addButton (getHelpAction ());

        frontend.addBackendStateListener (new BackendState.Listener ()
        {
            public void stateChanged (BackendState state)
            {
                setEnabled ();
            }
        });

        ValueListener guiListener = new ValueListener ()
        {
            public void valueChanged (ValueEvent e)
            {
                statusBar.clear ();
                setEnabled (e.isChanging (), nameField.getValue ());
            }
        };

        descriptionField.addValueListener (guiListener);

        for (CommandPanel c : commandPanels)
            c.addValueListener (guiListener);

        if (isTester)
        {
            expectedExceptionSelector.addValueListener (guiListener);
        }

        slaveId.addValueListener (new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    commandPanel.setSlaveId (slaveId.getValue ());
                }
            });

        setPanel (initialCp);
        setEnabled ();
    }

    private void setPanel (CommandPanel cp)
    {
        if (cp != commandPanel)
        {
            commandPanel = cp;
            commandSelector.setValue (cp);
            CardLayout layout = (CardLayout) cardPanel.getLayout ();
            layout.show (cardPanel, commandPanel.getTag ());
            statusBar.clear ();
            commandPanel.initialize ();
            commandPanel.setSlaveId (slaveId.getValue ());
            if (isTester)
            {
                commandPanel.setActualResult (null);
                actualExceptionField.setValue (null);
            }
            setEnabled ();
        }
    }

    public void setCommand (Command command)
    {
        if (command != null)
            setPanel (panelMap.get (command.getTag ()));
        commandPanel.setCommand (command);
        if (isTester)
        {
            commandPanel.setActualResult (null);
            actualExceptionField.setValue (null);
        }
        setEnabled ();
    }

    public JComponent getPanel ()
    {
        return outerPanel;
    }

    public String getTag ()
    {
        return "command.define";
    }

    public String getName ()
    {
        return "Define Command";
    }

    public JButton getDefaultButton ()
    {
        return saveButton;
    }

    public Action getHelpAction ()
    {
        if (commandPanel != null)
            return commandPanel.getHelpAction ();
        return frontend.getHelpAction ("command_define");
    }

    public String getToolTipText ()
    {
        return null;
    }

    @Override
    public void selected ()
    {
    }

    @Override
    public void reset ()
    {
    }

    public boolean hasUnappliedChanges ()
    {
        return false;
    }

    public boolean hasError ()
    {
        return statusBar.hasError ();
    }

    private boolean checkFields ()
    {
        assert EventQueue.isDispatchThread ();
        statusBar.clear ();
        return commandPanel.checkFields ();
    }

    private boolean nameOk (String name)
    {
        if (name.equals (""))
            return false;
        Command cmd = commands.getCommand (name);
        if (cmd != null)
            return false;
        return true;
    }

    private void setEnabled (boolean isChanging, String name)
    {
        Gui.setEnabled (saveButton, nameOk (name));
        Gui.setEnabled (sendButton, 
            frontend.isRunning () && frontend.isMaster ());
        if (isTester)
        {
            boolean haveActual = actualExceptionField.getValue () != null;
            Gui.setEnabled (expectActualButton, haveActual);
            if (commandPanel != null)
            {
                commandPanel.highlightErrors (haveActual);
                boolean expectOK =
                    expectedExceptionSelector.getValue () == null;
                commandPanel.setEnabled (true, expectOK);
            }
        }
    }

    private void setEnabled ()
    {
        nameField.checkValue ();
        setEnabled (false, nameField.getValue ());
    }

    private void sendCommand (final Command command)
    {
        final ModbusClient cl = backend.getClient ();
        if (cl == null)
        {
            statusBar.showError ("I100", "Connection closed");
            return;
        }

        statusBar.clear ();
        statusBar.showMessage ("Sending command ...");

        backend.execute (new Backend.Task ()
        {
            public void run ()
                throws InterruptedException, IOException, ValueException
            {
                command.send (cl, settings);
            }

            public void done ()
            {
                statusBar.clear ();
                Command.Result actual = command.getActualResult ();
                ModbusException actualException =
                    actual == null ? null : actual.getException ();
                if (actualException != null)
                {
                    reporterNSB.error (Exceptions.getHelpId (actualException),
                        Exceptions.getMessage (actualException));
                }
                commandPanel.setActualResult (actual);
                if (isTester)
                {
                    Gui.setEnabled (expectActualButton, true);
                    Command.Result expected = command.getExpectedResult ();
                    ModbusException expectedException =
                        expected == null ? null : expected.getException ();
                    boolean exceptionError =
                        actual == null ? false :
                        expectedException == null ? actualException != null :
                        !expectedException.matches (actualException);
                    actualExceptionField.setValue (
                        actual == null ? null :
                            actualException == null ? ModbusException.NO_ERROR :
                            actualException,
                        exceptionError);
                    if (actual != null && expected != null)
                    {
                        if (expected.matches (actual))
                            statusBar.showMessage ("Test passed");
                        else
                            statusBar.showError ("Test failed");
                    }
                    setEnabled ();
                }
                else
                {
                    if (actual != null)
                    {
                        statusBar.showMessage ("OK - response time " +
                            Metric.formatNanoTime (actual.getResponseTime ()));
                    }
                }
            }

            public void exceptionOccurred (Exception ex)
            {
                statusBar.showException (ex);
                reporterNSB.error (Exceptions.getHelpId (ex),
                    Exceptions.getMessage (ex));
                if (ex instanceof IOException)
                    backend.stop ();
            }
        });
    }

    private void fireStatusChanged ()
    {
        statusListeners.fireValueChanged (this);
    }

    public void addStatusListener (ValueListener l)
    {
        statusListeners.addListener (l);
    }

    public void removeStatusListener (ValueListener l)
    {
        statusListeners.removeListener (l);
    }
}


