
package uk.co.wingpath.modbusgui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
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 Command12Panel
    implements CommandPanel
{
    private static final Numeric.Type type = Numeric.Type.uint8;
    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 EventsModel model;
    private final CommandValuePanel valuePanel;
    private final WNumericField statusField;
    private final WNumericField expectedStatusField;
    private final WNumericField eventCountField;
    private final WNumericField expectedEventCountField;
    private final WNumericField msgCountField;
    private final WNumericField expectedMsgCountField;
    private final WComponent<Integer> slaveIdField;
    private final JLabel actualLabel;
    private final JLabel expectedLabel;
    private final JLabel eventsLabel;

    private Command12.Result actualResult;

    public Command12Panel (Frontend frontend, Settings settings,
        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 ();
        highlight = true;
        actualResult = null;

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

        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);
        GridBagConstraints constraints = panel.createConstraints ();
        panel.add (slaveIdField.getLabel (), constraints);
        constraints.gridx = 1;
        constraints.gridwidth = 2;
        panel.add (slaveIdField.getComponent (), constraints);

        statusField = new WNumericField (statusBar, "Status");
        statusField.setType (Numeric.Type.uint16);
        statusField.setEditable (false);
        statusField.setToolTipText ("Status received in response");
        statusField.setWidthChars (8);

        eventCountField = new WNumericField (statusBar, "Event Count");
        eventCountField.setType (Numeric.Type.uint16);
        eventCountField.setEditable (false);
        eventCountField.setToolTipText ("Event count received in response");
        eventCountField.setWidthChars (8);

        msgCountField = new WNumericField (statusBar, "Message Count");
        msgCountField.setType (Numeric.Type.uint16);
        msgCountField.setEditable (false);
        msgCountField.setToolTipText ("Message count received in response");
        msgCountField.setWidthChars (8);

        if (isTester)
        {
            actualLabel = new JLabel ("Actual Result");
            expectedLabel = new JLabel ("Expected Result");

            expectedStatusField = new WNumericField (statusBar, null, true);
            expectedStatusField.setValue (Numeric.Type.uint16.undef);
            expectedStatusField.setToolTipText (
                "Status expected in response");
            expectedStatusField.setWidthChars (8);
            expectedStatusField.setMirror (mirror, "Status");

            expectedEventCountField = new WNumericField (statusBar, null, true);
            expectedEventCountField.setValue (Numeric.Type.uint16.undef);
            expectedEventCountField.setToolTipText (
                "Event count expected in response");
            expectedEventCountField.setWidthChars (8);
            expectedEventCountField.setMirror (mirror, "Event Count");

            expectedMsgCountField = new WNumericField (statusBar, null, true);
            expectedMsgCountField.setValue (Numeric.Type.uint16.undef);
            expectedMsgCountField.setToolTipText (
                "Message count expected in response");
            expectedMsgCountField.setWidthChars (8);
            expectedMsgCountField.setMirror (mirror, "Message Count");

            constraints = panel.createConstraints ();
            constraints.gridx = 1;
            panel.add (expectedLabel, constraints);
            constraints.gridx = 2;
            panel.add (actualLabel, constraints);

            constraints = panel.createConstraints ();
            panel.add (statusField.getLabel (), constraints);
            constraints.gridx = 1;
            panel.add (expectedStatusField.getComponent (), constraints);
            constraints.gridx = 2;
            panel.add (statusField.getComponent (), constraints);

            constraints = panel.createConstraints ();
            panel.add (eventCountField.getLabel (), constraints);
            constraints.gridx = 1;
            panel.add (expectedEventCountField.getComponent (), constraints);
            constraints.gridx = 2;
            panel.add (eventCountField.getComponent (), constraints);

            constraints = panel.createConstraints ();
            panel.add (msgCountField.getLabel (), constraints);
            constraints.gridx = 1;
            panel.add (expectedMsgCountField.getComponent (), constraints);
            constraints.gridx = 2;
            panel.add (msgCountField.getComponent (), constraints);
        }
        else
        {
            actualLabel = null;
            expectedLabel = null;
            expectedStatusField = null;
            expectedEventCountField = null;
            expectedMsgCountField = null;

            panel.addComponent (statusField);
            panel.addComponent (eventCountField);
            panel.addComponent (msgCountField);
        }

        model = new EventsModel (isTester, isEditor);
        valuePanel = new CommandValuePanel (null, model, statusBar, mirror, 6,
            isEditor);
        eventsLabel = new JLabel ("Events:");
        constraints = panel.createConstraints ();
        panel.add (eventsLabel, constraints);
        constraints.gridx = 1;
        constraints.gridwidth = 2;
        panel.add (valuePanel, constraints);

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

        slaveIdField.addValueListener (listener);
        if (isTester)
        {
            expectedStatusField.addValueListener (listener);
            expectedEventCountField.addValueListener (listener);
            expectedMsgCountField.addValueListener (listener);
            valuePanel.addValueListener (listener);
        }
    }

    public JPanel getPanel ()
    {
        return panel;
    }

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

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

    public String getName ()
    {
        return "12 Get Comm Event Log";
    }

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

    public void initialize ()
    {
    }

    @Override
    public void setEnabled (boolean enabled, boolean enExpected)
    {
        slaveIdField.setEnabled (enabled);
        statusField.setEnabled (enabled);
        eventCountField.setEnabled (enabled);
        msgCountField.setEnabled (enabled);
        if (isTester)
        {
            actualLabel.setEnabled (enabled);
            expectedLabel.setEnabled (enabled && enExpected);
            expectedStatusField.setEnabled (enabled && enExpected);
            expectedEventCountField.setEnabled (enabled && enExpected);
            expectedMsgCountField.setEnabled (enabled && enExpected);
            model.enableExpected (enabled && enExpected);
            valuePanel.setEnabled (enabled && enExpected);
            eventsLabel.setEnabled (enabled && enExpected);
            if (!enabled || !enExpected)
                valuePanel.cancelEditing ();
        }
    }

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

    public boolean checkFields ()
    {
        Event.checkIsEventDispatchThread ();
        if (!slaveIdField.checkValue ())
            return false;
        if (isTester)
        {
            if (!expectedStatusField.checkValue ())
                return false;
            if (!expectedEventCountField.checkValue ())
                return false;
            if (!expectedMsgCountField.checkValue ())
                return false;
            if (!valuePanel.stopEditing ())
                return false;
        }
        return true;
    }

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

    private void highlightErrors ()
    {
        if (!isTester)
            return;
        Numeric.Value s = statusField.getValue ();
        Numeric.Value es = expectedStatusField.getValue ();
        statusField.setBackground (highlight && !s.matches (es) ?
            Gui.COLOUR_BACKGROUND_ERROR :
            Gui.COLOUR_BACKGROUND_INACTIVE);
        Numeric.Value c = eventCountField.getValue ();
        Numeric.Value ec = expectedEventCountField.getValue ();
        eventCountField.setBackground (highlight && !c.matches (ec) ?
            Gui.COLOUR_BACKGROUND_ERROR :
            Gui.COLOUR_BACKGROUND_INACTIVE);
        Numeric.Value m = msgCountField.getValue ();
        Numeric.Value em = expectedMsgCountField.getValue ();
        msgCountField.setBackground (highlight && !m.matches (em) ?
            Gui.COLOUR_BACKGROUND_ERROR :
            Gui.COLOUR_BACKGROUND_INACTIVE);
        valuePanel.highlightErrors (highlight);
    }

    public void setCommand (Command command)
    {
        if (command == null)
        {
            int slaveId = settings.getGeneral ().getSlaveId ().getValue ();
            slaveIdField.setValue (slaveId);
            setActualResult (null);
            setExpectedResult (null);
        }
        else
        {
            Command12 cmd = (Command12) command;
            slaveIdField.setValue (cmd.getSlaveId ());
            setActualResult (cmd.getActualResult ());
            setExpectedResult (cmd.getExpectedResult ());
        }
        highlightErrors ();
    }

    public void setActualResult (Command.Result result)
    {
        actualResult = (Command12.Result) result;
        if (result == null)
        {
            statusField.setValue (Numeric.Type.int16.undef);
            eventCountField.setValue (Numeric.Type.int16.undef);
            msgCountField.setValue (Numeric.Type.int16.undef);
            statusField.setBackground (Gui.COLOUR_BACKGROUND_INACTIVE);
            eventCountField.setBackground (Gui.COLOUR_BACKGROUND_INACTIVE);
            msgCountField.setBackground (Gui.COLOUR_BACKGROUND_INACTIVE);
            model.resetActual ();
        }
        else
        {
            statusField.setValue (actualResult.getStatus ());
            eventCountField.setValue (actualResult.getEventCount ());
            msgCountField.setValue (actualResult.getMsgCount ());
            model.setActualData (actualResult.getEvents ());
            ModbusException exception = actualResult.getException ();
            if (exception != null)
                statusBar.showException (exception);
        }
        highlightErrors ();
    }

    @Override
    public void expectActual ()
    {
        if (!isTester)
            return;
        expectedStatusField.setValue (statusField.getValue ());
        expectedEventCountField.setValue (eventCountField.getValue ());
        expectedMsgCountField.setValue (msgCountField.getValue ());
        model.setExpectedData (model.getActualData ());
        highlightErrors ();
    }

    private void setExpectedResult (Command.Result result)
    {
        if (!isTester)
            return;
        valuePanel.cancelEditing ();
        if (result == null)
        {
            expectedStatusField.setValue (Numeric.Type.int16.undef);
            expectedEventCountField.setValue (Numeric.Type.int16.undef);
            expectedMsgCountField.setValue (Numeric.Type.int16.undef);
            model.resetExpected ();
        }
        else
        {
            Command12.Result r = (Command12.Result) result;
            expectedStatusField.setValue (r.getStatus ());
            expectedEventCountField.setValue (r.getEventCount ());
            expectedMsgCountField.setValue (r.getMsgCount ());
            model.setExpectedData (r.getEvents ());
        }
        highlightErrors ();
    }

    public Command12 createCommand (String name, String description,
        ModbusException exception)
    {
        Command12 cmd;
        if (isTester)
        {
            cmd = new Command12 (
                name,
                description,
                slaveIdField.getValue (),
                exception,
                expectedStatusField.getValue (),
                expectedEventCountField.getValue (),
                expectedMsgCountField.getValue (),
                model.getExpectedData ());
        }
        else
        {
            cmd = new Command12 (name,
                description,
                slaveIdField.getValue ());
        }
        cmd.setActualResult (actualResult);
        return cmd;
    }

    public boolean haveValuesChanged (Command command)
    {
        Command12 cmd = (Command12) command;
        boolean changed = false;
        if (slaveIdField.hasValueChanged (cmd.getSlaveId ()))
            changed = true;
        if (isTester)
        {
            Command12.Result result = cmd.getExpectedResult ();
            if (expectedStatusField.hasValueChanged (result.getStatus ()))
                changed = true;
            if (expectedEventCountField.hasValueChanged (
                result.getEventCount ()))
            {
                changed = true;
            }
            if (expectedMsgCountField.hasValueChanged (result.getMsgCount ()))
                changed = true;
            if (valuePanel.haveExpectedValuesChanged (result.getEvents ()))
                changed = true;
        }
        return changed;
    }

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

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

    private class EventsModel
        extends CommandTableModel
        implements HasCellLabels
    {
        private Numeric.Value [] actualData;
        private Numeric.Value [] expectedData;
        private boolean [] unapplied;

        EventsModel (boolean isTester, boolean isEditor)
        {
            super (isTester, isEditor, false, false);
            Event.checkIsEventDispatchThread ();
            actualData = type.createUndefArray (0);
            expectedData = type.createUndefArray (64);
            unapplied = new boolean [64];
        }

        @Override
        public int getRowCount ()
        {
            Event.checkIsEventDispatchThread ();
            if (isTester)
                return Math.max (actualData.length, expectedData.length);
            return actualData.length;
        }

        @Override
        public String getValueAt (int row, int col)
        {
            Event.checkIsEventDispatchThread ();
            if (col == COL_ADDRESS)
                return row + "";
            if (isTester && col == COL_EXPECTED)
            {
                return row >= expectedData.length ? "" :
                    expectedData [row].toString (2);
            }
            return row >= actualData.length ? "" :
                actualData [row].toString (2);
        }

        @Override
        public String getCellLabel (int row, int col)
        {
            String label = "" + row;
            if (col == COL_EXPECTED)
                label += " Expected";
            else if (col == COL_ACTUAL)
                label += " Actual";
            return label;
        }

        @Override
        public int getColumnWidth (int col)
        {
            int width = super.getColumnWidth (col);
            int minWidth = Gui.getTextWidth (col == COL_ADDRESS ? 6 : 12);
            if (width < minWidth)
                width = minWidth;
            return width;
        }

        @Override
        public String fromString (String value, int row, int col)
            throws ValueException
        {
            Event.checkIsEventDispatchThread ();
            if (isTester && col == COL_EXPECTED)
            {
                expectedData [row] = type.fromString (value, 2);
                return expectedData [row].toString (2);
            }
            return value;
        }

        @Override
        public void setValueAt (Object value, int row, int col)
        {
            try
            {
                fromString ((String) value, row, col);
            }
            catch (ValueException e)
            {
                throw new IllegalStateException (e);
            }
        }

        public Numeric.Value [] getActualData ()
        {
            return actualData.clone ();
        }

        public Numeric.Value [] getExpectedData ()
        {
            if (!isTester)
                throw new IllegalStateException ("Not tester");
            return expectedData;
        }

        public void setActualData (Numeric.Value [] actualData)
        {
            Event.checkIsEventDispatchThread ();
            this.actualData = actualData.clone ();
            fireTableDataChanged ();
        }

        public void setExpectedData (Numeric.Value [] data)
        {
            if (!isTester)
                throw new IllegalStateException ("Not tester");
            expectedData = type.createUndefArray (64);
            
            for (int i = 0 ; i < 64 ; ++i)
            {
                if (i < data.length)
                    expectedData [i] = data [i];
            }
            fireTableDataChanged ();
        }

        public void resetActual ()
        {
            actualData = type.createUndefArray (0);
            fireTableDataChanged ();
        }

        public void resetExpected ()
        {
            Event.checkIsEventDispatchThread ();
            assert isTester;
            expectedData = type.createUndefArray (64);
        }

        public boolean isError (int row, int col)
        {
            if (isTester && col == COL_ACTUAL)
            {
                if (row >= expectedData.length)
                    return false;   // shouldn't happen
                if (row >= actualData.length)
                    return expectedData [row].isDefined ();
                if (!expectedData [row].matches (actualData [row]))
                    return true;
            }
            return false;
        }

        public boolean isUnapplied (int row, int col)
        {
            return isEditor && isCellEditable (row, col) &&
                row < unapplied.length && unapplied [row];
        }

        public boolean haveActualValuesChanged (Numeric.Value [] values)
        {
            return false;
        }

        public boolean haveExpectedValuesChanged (Numeric.PatVal [] values)
        {
            assert isTester;
            boolean changed = false;
            Numeric.Value [] vals = (Numeric.Value []) values;

            for (int i = 0 ; i < expectedData.length ; ++i)
            {
                boolean ch = i >= vals.length ||
                    !vals [i].equals (expectedData [i]);
                if (ch)
                    changed = true;
                unapplied [i] = ch;
            }

            fireTableRowsUpdated (0, getRowCount () - 1);
            return changed;
        }
    }
}


