
package uk.co.wingpath.modbusgui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
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 Command43Panel
    implements CommandPanel
{
    private class ObjectIdVerifier
        implements Verifier
    {
        @Override
        public String verify (String value, boolean isChanging)
        {
            if (isChanging)
            {
                statusBar.clear ();
                return value;
            }
            try
            {
                int id = Integer.parseInt (value.trim (), 16);
                if (id >= 0 && id <= 255)
                {
                    statusBar.clear ();
                    return DeviceIdPanel.idToString (id);
                }
            }
            catch (NumberFormatException e)
            {
            }
            statusBar.showError (
                "ID must be hex integer in range 00..ff");
            return null;
        }
    }

    private final Frontend frontend;
    private final Settings settings;
    private final ValueEventSource listeners;
    private final boolean isTester;
    private final boolean isEditor;
    private boolean highlight;

    private final StatusBar statusBar;
    private final GridBagPanel panel;
    private final WComponent<Integer> codeField;
    private final WTextField objectIdField;
    private final WNumericField actualConformityField;
    private final WNumericField expectedConformityField;
    private final JLabel conformityLabel;
    private final DeviceIdPanel deviceIdPanel;
    private final DeviceIdTestPanel deviceIdTestPanel;
    private final WComponent<Integer> slaveIdField;
    private final JButton addButton;
    private final JButton deleteButton;
    private final WEditableComboBox objectIdCombo;
    private int selectedId;

    private Command43.Result actualResult;

    public Command43Panel (Frontend frontend, Settings settings,
        final StatusBar statusBar, MirrorField mirror, boolean isTester, boolean isEditor)
    {
        Event.checkIsEventDispatchThread ();
        this.frontend = frontend;
        this.settings = settings;
        this.statusBar = statusBar;
        this.isTester = isTester;
        this.isEditor = isEditor;
        listeners = new ValueEventSource ();
        selectedId = -1;
        highlight = true;
        actualResult = null;

        panel = new GridBagPanel ();
        panel.addTitle (Command43.typeName, 3);

        GridBagPanel requestPanel = new GridBagPanel ();
        GridBagConstraints constraints = panel.createConstraints ();
        constraints.insets = new Insets (2, 0, 2, 5);
        constraints.gridwidth = 2;
        panel.add (requestPanel, constraints);

        GridBagPanel editPanel = new GridBagPanel ();
        constraints.gridx = 2;
        constraints.gridwidth = 1;
        constraints.insets = new Insets (2, 5, 2, 5);
        panel.add (editPanel, constraints);

        int slaveId = settings.getGeneral ().getSlaveId ().getValue ();
        slaveIdField = new WIntegerField (statusBar, "Slave ID",
            0, 255, slaveId);
        slaveIdField.setToolTipText ("Slave identifier");
        slaveIdField.setWidthChars (4);
        slaveIdField.setMnemonic (KeyEvent.VK_I);
        slaveIdField.setMirror (mirror);
        requestPanel.addComponent (slaveIdField);

        codeField = new WComboBox<Integer> ("Code",
            new Integer [] { 0x01, 0x02, 0x03, 0x04 },
            new String [] {
                "01 Basic",
                "02 Regular",
                "03 Extended",
                "04 Specific",
            });
        codeField.setToolTipText ("Read Device ID code");
        codeField.setToolTipText (new String []
            {
                "Get basic device identification (stream access)",
                "Get regular device identification (stream access)",
                "Get extended device identification (stream access)",
                "Get one specific identification object (individual access)"
            });
        codeField.setMnemonic (KeyEvent.VK_D);
        requestPanel.addComponent (codeField);

        objectIdField = new WTextField (statusBar, "First Object ID");
        objectIdField.setValue ("0");
        objectIdField.setToolTipText ("Hex ID of first object to obtain");
        objectIdField.setWidthChars (4);
        objectIdField.setMnemonic (KeyEvent.VK_F);
        objectIdField.setVerifier (new ObjectIdVerifier ());
        objectIdField.setMirror (mirror);
        requestPanel.addComponent (objectIdField);

        GridBagPanel conformityPanel = new GridBagPanel ();

        if (isTester)
        {
            ButtonPanel editButtonPanel = ButtonPanel.createVertical ();

            addButton = editButtonPanel.addButton ("Add", null,
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        if (!objectIdCombo.checkValue ())
                            return;
                        if (selectedId >= 0 &&
                            !deviceIdTestPanel.isExpected (selectedId))
                        {
                            deviceIdTestPanel.addId (selectedId);
                            deviceIdTestPanel.setSelectedId (selectedId);
                            setComboItems ();
                            enableAddRemoveButtons (false);
                        }
                    }
                });
            addButton.setMnemonic (KeyEvent.VK_A);
            addButton.setToolTipText ("Add expected object");

            deleteButton = editButtonPanel.addButton ("Delete", null,
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        if (!objectIdCombo.checkValue ())
                            return;
                        int id = deviceIdTestPanel.getSelectedId ();
                        if (deviceIdTestPanel.isExpected (id))
                        {
                            deviceIdTestPanel.removeId (id);
                            setComboItems ();
                            enableAddRemoveButtons (false);
                        }
                    }
                });
            deleteButton.setMnemonic (KeyEvent.VK_D);
            deleteButton.setToolTipText ("Delete expected object");

            objectIdCombo = new WEditableComboBox (statusBar, "Object ID");
            objectIdCombo.setToolTipText (
                "Hex ID of expected object to be added/deleted");
            objectIdCombo.setMnemonic (KeyEvent.VK_O);
            objectIdCombo.setVerifier (new ObjectIdVerifier ());

            constraints = editPanel.createConstraints ();
            editPanel.add (objectIdCombo.getLabel (), constraints);
            constraints.gridx = 1;
            editPanel.add (objectIdCombo.getComponent (), constraints);
            constraints.gridx = 2;
            editPanel.add (editButtonPanel, constraints);

            deviceIdTestPanel = new DeviceIdTestPanel ("Received Data",
                new DeviceId (), new DeviceId (), statusBar, mirror,
                isEditor, 7);
            constraints = panel.createConstraints ();
            constraints.anchor = GridBagConstraints.CENTER;
            constraints.gridwidth = 3;
            panel.add (deviceIdTestPanel, constraints);

            deviceIdPanel = null;

            constraints = panel.createConstraints ();
            constraints.anchor = GridBagConstraints.CENTER;
            constraints.gridwidth = 3;
            panel.add (conformityPanel, constraints);
            conformityLabel = new JLabel ("Conformity Level:");
            expectedConformityField = new WNumericField (statusBar,
                "Expected", true);
            expectedConformityField.setRadix (16);
            expectedConformityField.setValue (Numeric.Type.uint8.undef);
            expectedConformityField.setToolTipText (
                "Expected hex conformity level");
            expectedConformityField.setWidthChars (4);
            expectedConformityField.setMirror (mirror);
            actualConformityField = new WNumericField (statusBar, "Actual");
            actualConformityField.setRadix (16);
            actualConformityField.setValue (Numeric.Type.uint8.zero);
            actualConformityField.setToolTipText (
                "Actual hex conformity level");
            actualConformityField.setWidthChars (4);
            actualConformityField.setEditable (false);
            constraints = conformityPanel.createConstraints ();
            conformityPanel.add (conformityLabel, constraints);
            constraints.gridx = 1;
            conformityPanel.add (expectedConformityField.getLabel (),
                constraints);
            constraints.gridx = 2;
            conformityPanel.add (expectedConformityField.getComponent (),
                constraints);
            constraints.gridx = 3;
            conformityPanel.add (actualConformityField.getLabel (),
                constraints);
            constraints.gridx = 4;
            conformityPanel.add (actualConformityField.getComponent (),
                constraints);
        }
        else
        {
            objectIdCombo = null;
            addButton = null;
            deleteButton = null;

            deviceIdPanel = new DeviceIdPanel ("Received Data",
                new DeviceId (), isEditor, false, statusBar, mirror, 7);
            constraints = panel.createConstraints ();
            // constraints.fill = GridBagConstraints.BOTH;
            constraints.anchor = GridBagConstraints.CENTER;
            // constraints.weightx = 1.0;
            // constraints.weighty = 1.0;
            // constraints.insets = new Insets (10, 5, 2, 5);
            constraints.gridwidth = 3;
            panel.add (deviceIdPanel, constraints);

            deviceIdTestPanel = null;

            constraints = panel.createConstraints ();
            constraints.anchor = GridBagConstraints.CENTER;
            constraints.gridwidth = 3;
            panel.add (conformityPanel, constraints);
            actualConformityField =
                new WNumericField (statusBar, "Conformity Level");
            actualConformityField.setRadix (16);
            actualConformityField.setValue (Numeric.Type.uint8.zero);
            actualConformityField.setToolTipText ("Hex conformity level");
            actualConformityField.setWidthChars (4);
            actualConformityField.setEditable (false);
            conformityPanel.addComponent (actualConformityField);

            conformityLabel = null;
            expectedConformityField = null;
        }

        if (isTester)
        {
            objectIdCombo.addValueListener (new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        if (!e.isChanging ())
                        {
                            String value = objectIdCombo.getValue ();
                            int id = value.equals ("") ? -1 :
                                Integer.parseInt (value.trim (), 16);
                            if (id != selectedId)
                            {
                                selectedId = id;
                                deviceIdTestPanel.setSelectedId (id);
                            }
                        }
                        else
                        {
                            statusBar.clear ("DI3");
                        }
                        enableAddRemoveButtons (e.isChanging ());
                    }
                });

            deviceIdTestPanel.addValueListener (new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        if (e.isChanging ())
                            statusBar.clear ("DI4");
                        int id = deviceIdTestPanel.getSelectedId ();
                        if (id >= 0 && id != selectedId)
                        {
                            selectedId = id;
                            objectIdCombo.setValue (
                                DeviceIdTestPanel.idToString (id));
                        }
                        enableAddRemoveButtons (false);
                    }
                });

            enableAddRemoveButtons (false);
        }

        ValueListener listener = new ValueListener ()
        {
            public void valueChanged (ValueEvent e)
            {
                Event.checkIsEventDispatchThread ();
                listeners.fireValueChanged (e);
            }
        };

        slaveIdField.addValueListener (listener);
        codeField.addValueListener (listener);
        objectIdField.addValueListener (listener);
        if (isTester)
        {
            expectedConformityField.addValueListener (listener);
            deviceIdTestPanel.addValueListener (listener);
        }
    }

    public JPanel getPanel ()
    {
        return panel;
    }

    public String getTitle ()
    {
        return Command43.typeName;
    }

    public String getTag ()
    {
        return Command43.tag;
    }

    public String getName ()
    {
        return "43/14 Read Device ID";
    }

    public Action getHelpAction ()
    {
        return frontend.getHelpAction ("cmd_43");
    }

    public void initialize ()
    {
    }

    @Override
    public void setEnabled (boolean enabled, boolean enExpected)
    {
        slaveIdField.setEnabled (enabled);
        codeField.setEnabled (enabled);
        objectIdField.setEnabled (enabled);
        if (isTester)
        {
            deviceIdTestPanel.setEnabled (enabled, enExpected);
            conformityLabel.setEnabled (enabled);
            expectedConformityField.setEnabled (enabled && enExpected);
            actualConformityField.setEnabled (enabled);
            objectIdCombo.setEnabled (enabled && enExpected);
            boolean expected = deviceIdTestPanel.isExpected (selectedId);
            addButton.setEnabled (enabled && enExpected &&
                selectedId >= 0 && !expected);
            deleteButton.setEnabled (enabled && enExpected &&
                selectedId >= 0 && expected);
        }
        else
        {
            deviceIdPanel.setEnabled (enabled);
            actualConformityField.setEnabled (enabled);
        }
    }

    @Override
    public void highlightErrors (boolean highlight)
    {
        this.highlight = highlight;
        deviceIdTestPanel.highlightErrors (highlight);
        highlightErrors ();
    }

    private void enableAddRemoveButtons (boolean isChanging)
    {
        if (!isTester)
            return;
        boolean expected = deviceIdTestPanel.isExpected (selectedId);
        addButton.setEnabled (isChanging || (selectedId >= 0 && !expected));
        deleteButton.setEnabled (isChanging || (selectedId >= 0 && expected));
    }

    public boolean checkFields ()
    {
        Event.checkIsEventDispatchThread ();
        if (!slaveIdField.checkValue ())
            return false;
        if (!codeField.checkValue ())
            return false;
        if (!objectIdField.checkValue ())
            return false;
        if (isTester)
        {
            if (!expectedConformityField.checkValue ())
                return false;
            if (!deviceIdTestPanel.stopEditing ())
                return false;
        }
        return true;
    }

    public void setSlaveId (int slaveId)
    {
        slaveIdField.setValue (slaveId);
    }

    private void setComboItems ()
    {
        if (!isTester)
            return;
        ArrayList<String> ids = new ArrayList<String> ();

        for (int id : deviceIdTestPanel.getIds ())
            ids.add (DeviceIdTestPanel.idToString (id));

        objectIdCombo.setItems (ids);
        objectIdCombo.setValue (DeviceIdTestPanel.idToString (selectedId));
    }

    private void highlightErrors ()
    {
        if (!isTester)
            return;
        Numeric.Value expected = expectedConformityField.getValue ();
        Numeric.Value actual = actualConformityField.getValue ();
        boolean error = !actual.matches (expected);
        actualConformityField.setBackground (highlight && error ?
            Gui.COLOUR_BACKGROUND_ERROR :
            Gui.COLOUR_BACKGROUND_INACTIVE);
    }

    public void setCommand (Command command)
    {
        if (command == null)
        {
            int slaveId = settings.getGeneral ().getSlaveId ().getValue ();
            slaveIdField.setValue (slaveId);
            codeField.setValue (0x01);
            objectIdField.setValue ("0");
            setActualResult (null);
            setExpectedResult (null);
        }
        else
        {
            Command43 cmd = (Command43) command;
            slaveIdField.setValue (cmd.getSlaveId ());
            codeField.setValue (cmd.getCode ());
            objectIdField.setValue (Integer.toString (cmd.getObjectId (), 16));
            setActualResult (cmd.getActualResult ());
            setExpectedResult (cmd.getExpectedResult ());
        }
        highlightErrors ();
        setComboItems ();
    }

    public void setActualResult (Command.Result result)
    {
        actualResult = (Command43.Result) result;
        if (result == null)
        {
            if (isTester)
            {
                deviceIdTestPanel.setActualDeviceId (new DeviceId ());
            }
            else
            {
                deviceIdPanel.setDeviceId (new DeviceId ());
            }
            actualConformityField.setValue (Numeric.Type.uint8.zero);
        }
        else
        {
            DeviceId devId = actualResult.getDeviceId ();
            if (isTester)
            {
                deviceIdTestPanel.setActualDeviceId (devId);
            }
            else
            {
                deviceIdPanel.setDeviceId (devId);
            }
            int conformity = devId.getConformityLevel ();
            try
            {
                actualConformityField.setValue (
                    Numeric.Type.uint8.createValue (conformity));
            }
            catch (ValueException e)
            {
            }
            ModbusException exception = actualResult.getException ();
            if (exception != null)
                statusBar.showException (exception);
        }
        highlightErrors ();
        setComboItems ();
    }

    private void setExpectedResult (Command.Result result)
    {
        if (!isTester)
            return;
        if (result == null)
        {
            deviceIdTestPanel.setExpectedDeviceId (new DeviceId ());
            expectedConformityField.setValue (
                Numeric.Type.uint8.undef);
        }
        else
        {
            Command43.Result r = (Command43.Result) result;
            DeviceId devId = r.getDeviceId ();
            deviceIdTestPanel.setExpectedDeviceId (devId);
            int conformity = devId.getConformityLevel ();
            try
            {
                expectedConformityField.setValue (
                    Numeric.Type.uint8.createValue (conformity));
            }
            catch (ValueException e)
            {
            }
        }
        selectedId = 0;
        setComboItems ();
        deviceIdTestPanel.setSelectedId (selectedId);
        highlightErrors ();
        setComboItems ();
    }

    @Override
    public void expectActual ()
    {
        if (!isTester)
            return;
        deviceIdTestPanel.setExpectedDeviceId (
            deviceIdTestPanel.getActualDeviceId ().clone ());
        expectedConformityField.setValue (actualConformityField.getValue ());
        highlightErrors ();
        setComboItems ();
    }

    public Command43 createCommand (String name, String description,
        ModbusException exception)
    {
        Command43 cmd;
        if (isTester)
        {
            DeviceId id = deviceIdTestPanel.getExpectedDeviceId ();
            int conformity =
                (int) expectedConformityField.getValue ().getLongValue ();
            id.setConformityLevel (conformity);
            cmd = new Command43 (name,
                description,
                slaveIdField.getValue (),
                codeField.getValue (),
                Integer.parseInt (objectIdField.getValue ().trim (), 16),
                exception,
                id);
        }
        else
        {
            cmd = new Command43 (name,
                description,
                slaveIdField.getValue (),
                codeField.getValue (),
                Integer.parseInt (objectIdField.getValue ().trim (), 16));
        }
        cmd.setActualResult (actualResult);
        return cmd;
    }

    public boolean haveValuesChanged (Command command)
    {
        boolean changed = false;
        Command43 cmd = (Command43) command;
        if (slaveIdField.hasValueChanged (cmd.getSlaveId ()))
            changed = true;
        if (codeField.hasValueChanged (cmd.getCode ()))
            changed = true;
        if (objectIdField.hasValueChanged (
            Integer.toString (cmd.getObjectId (), 16)))
        {
            changed = true;
        }
        if (isTester)
        {
            Command43.Result result = cmd.getExpectedResult ();
            DeviceId id = result.getDeviceId ();
            try
            {
                if (expectedConformityField.hasValueChanged (
                    Numeric.Type.uint8.createValue (id.getConformityLevel ())))
                {
                    changed = true;
                }
            }
            catch (ValueException e)
            {
            }
            if (deviceIdTestPanel.haveValuesChanged (id))
                changed = true;
        }
        return changed;
    }

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

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

