
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 Command24Panel
    implements CommandPanel
{
    private final Frontend frontend;
    private final Settings settings;
    private final ValueEventSource listeners;
    private final boolean isTester;
    private final boolean isEditor;

    private final StatusBar statusBar;
    private final GridBagPanel panel;
    private final ValueModel model;
    private final CommandValuePanel valuePanel;
    private final RadixSelector radixSelector;
    private final WComponent<Integer> addressField;
    private final TypeSelector typeSelector;
    private final WComponent<Integer> slaveIdField;

    private Command24.Result actualResult;

    public Command24Panel (Frontend frontend, final 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 ();
        actualResult = null;

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

        GridBagPanel leftPanel = new GridBagPanel ();
        GridBagConstraints constraints = panel.createConstraints ();
        constraints.insets = new Insets (2, 0, 2, 5);
        constraints.gridwidth = 2;
        panel.add (leftPanel, 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);
        leftPanel.addComponent (slaveIdField);

        addressField = new WIntegerField (statusBar, "Address", 0, 65535, 0);
        addressField.setToolTipText ("Address of first register");
        addressField.setWidthChars (8);
        addressField.setMnemonic (KeyEvent.VK_A);
        addressField.setMirror (mirror);
        leftPanel.addComponent (addressField);

        typeSelector = TypeSelector.create (true, true);
        leftPanel.addComponent (typeSelector);

        radixSelector = new RadixSelector (true);
        radixSelector.setRadix (10);
        leftPanel.addComponent (radixSelector);

        model = new ValueModel (
            settings.getBigValue ().getValue (),
            addressField.getValue (),
            typeSelector.getValue (),
            radixSelector.getValue (),
            isTester, isEditor);
        valuePanel = new CommandValuePanel ("Received Data", model,
            statusBar, mirror, 10, isEditor);
        constraints.gridx = 2;
        constraints.gridwidth = 1;
        constraints.insets = new Insets (2, 5, 2, 5);
        panel.add (valuePanel, constraints);

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

        slaveIdField.addValueListener (listener);
        valuePanel.addValueListener (listener);

        typeSelector.addValueListener (new ValueListener ()
        {
            public void valueChanged (ValueEvent e)
            {
                if (!e.isChanging ())
                {
                    Numeric.Type type = typeSelector.getValue ();
                    model.setType (type);
                    setEnabled (true, true);
                }
                listeners.fireValueChanged (e);
            }
        });

        radixSelector.addValueListener (new ValueListener ()
        {
            public void valueChanged (ValueEvent e)
            {
                if (!e.isChanging ())
                    model.setRadix (radixSelector.getValue ());
                listeners.fireValueChanged (e);
            }
        });

        addressField.addValueListener (new ValueListener ()
        {
            public void valueChanged (ValueEvent e)
            {
                if (!e.isChanging ())
                    model.setAddress (addressField.getValue ());
                listeners.fireValueChanged (e);
            }
        });

        settings.getBigValue ().addValueListener (new ValueListener ()
        {
            public void valueChanged (ValueEvent e)
            {
                if (!e.isChanging ())
                {
                    BigValueFlags bvf = settings.getBigValue ().getValue ();
                    model.setBigValueFlags (bvf);
                }
                listeners.fireValueChanged (e);
            }
        });
    }

    public JPanel getPanel ()
    {
        return panel;
    }

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

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

    public String getName ()
    {
        return "24 Read FIFO Queue";
    }

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

    public void initialize ()
    {
    }

    @Override
    public void setEnabled (boolean enabled, boolean enExpected)
    {
        slaveIdField.setEnabled (enabled);
        typeSelector.setEnabled (enabled);
        Numeric.Type type = typeSelector.getValue ();
        if (type.isFloat () || type == Numeric.Type.int1)
        {
            radixSelector.setEnabled (false);
            radixSelector.setRadix (10);
            model.setRadix (10);
        }
        else
        {
            radixSelector.setEnabled (enabled);
        }
        addressField.setEnabled (enabled);
        valuePanel.setEnabled (enabled);
        if (!enabled || !enExpected)
            valuePanel.cancelEditing ();
        model.enableExpected (enExpected);
    }

    @Override
    public void highlightErrors (boolean highlight)
    {
        valuePanel.highlightErrors (highlight);
    }

    public boolean checkFields ()
    {
        assert EventQueue.isDispatchThread ();
        if (!slaveIdField.checkValue ())
            return false;
        if (!typeSelector.checkValue ())
            return false;
        if (!radixSelector.checkValue ())
            return false;
        if (!addressField.checkValue ())
            return false;
        if (!valuePanel.stopEditing ())
            return false;
        return true;
    }

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

    public void setCommand (Command command)
    {
        if (command == null)
        {
            int slaveId = settings.getGeneral ().getSlaveId ().getValue ();
            slaveIdField.setValue (slaveId);
            addressField.setValue (0);
            final int count = 1;
            final Numeric.Type type = Numeric.Type.int16;
            typeSelector.setValue (type);
            final int radix = 10;
            radixSelector.setValue (radix);
            model.setAddress (0);
            model.setType (type);
            model.setRadix (radix);
            setActualResult (null);
            setExpectedResult (null);
        }
        else
        {
            Command24 cmd = (Command24) command;
            slaveIdField.setValue (cmd.getSlaveId ());
            addressField.setValue (cmd.getAddress ());
            typeSelector.setValue (cmd.getType ());
            radixSelector.setValue (cmd.getRadix ());
            model.setAddress (cmd.getAddress ());
            model.setType (cmd.getType ());
            model.setRadix (cmd.getRadix ());
            setActualResult (cmd.getActualResult ());
            setExpectedResult (cmd.getExpectedResult ());
        }
    }

    public void setActualResult (Command.Result result)
    {
        actualResult = (Command24.Result) result;
        if (result == null)
        {
            model.resetActual ();
        }
        else
        {
            model.setActualData (actualResult.getData ());
            ModbusException exception = actualResult.getException ();
            if (exception != null)
                statusBar.showException (exception);
        }
    }

    private void setExpectedResult (Command.Result result)
    {
        if (isTester)
        {
            valuePanel.cancelEditing ();
            if (result == null)
            {
                model.resetExpected ();
            }
            else
            {
                Command24.Result r = (Command24.Result) result;
                model.setExpectedData (r.getData ());
            }
        }
    }

    @Override
    public void expectActual ()
    {
        if (!isTester)
            return;
        model.setExpectedData (model.getActualData ());
    }

    public Command24 createCommand (String name, String description,
        ModbusException exception)
    {
        Command24 cmd;
        if (isTester)
        {
            cmd = new Command24 (name,
                description,
                slaveIdField.getValue (),
                addressField.getValue (),
                typeSelector.getValue (),
                radixSelector.getValue (),
                exception,
                model.getExpectedData ());
        }
        else
        {
            cmd = new Command24 (name,
                description,
                slaveIdField.getValue (),
                addressField.getValue (),
                typeSelector.getValue (),
                radixSelector.getValue ());
        }
        cmd.setActualResult (actualResult);
        return cmd;
    }

    public boolean haveValuesChanged (Command command)
    {
        boolean changed = false;
        Command24 cmd = (Command24) command;
        if (slaveIdField.hasValueChanged (cmd.getSlaveId ()))
            changed = true;
        if (addressField.hasValueChanged (cmd.getAddress ()))
            changed = true;
        if (typeSelector.hasValueChanged (cmd.getType ()))
            changed = true;
        if (radixSelector.hasValueChanged (cmd.getRadix ()))
            changed = true;
        if (isTester)
        {
            Command24.Result result = cmd.getExpectedResult ();
            if (valuePanel.haveExpectedValuesChanged (result.getData ()))
                changed = true;
        }
        return changed;
    }

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

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

    private class ValueModel
        extends CommandTableModel
        implements HasCellLabels
    {
        private Numeric.Value [] actualData;
        private Numeric.Value [] expectedData;
        private boolean [] unapplied;
        private int [] addresses;
        private int address;
        private Numeric.Type type;
        private int radix;
        private BigValueFlags bigValueFlags;

        ValueModel (BigValueFlags bigValueFlags,
            int address, Numeric.Type type, int radix, boolean isTester,
            boolean isEditor)
        {
            super (isTester, isEditor, false, false);
            Event.checkIsEventDispatchThread ();
            this.bigValueFlags = bigValueFlags;
            this.address = address;
            this.type = type;
            this.radix = radix;
            actualData = type.createUndefArray (1);
            expectedData = type.createUndefArray (1);
            unapplied = new boolean [expectedData.length];
            buildAddresses ();
        }

        @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 >= addresses.length ? "" : addresses [row] + "";
            if (isTester && col == COL_EXPECTED)
            {
                return row >= expectedData.length ? "" :
                    expectedData [row].toString (radix);
            }
            return row >= actualData.length ? "" :
                actualData [row].toString (radix);
        }

        @Override
        public String getCellLabel (int row, int col)
        {
            if (row >= addresses.length)
                return "";
            String label = "" + addresses [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)
            {
                Numeric.Value v = type.fromString (value, radix);
                if (row == 0)
                {
                    if (!v.equals (expectedData [0]))
                    {
                        int n = 0;
                        if (v.isDefined ())
                        {
                            n = (int) v.getLongValue ();
                            if (n < 0 || n > 31)
                            {
                                throw new ValueException (
                                    "Value must be in range 0 to 31");
                            }
                        }
                        Numeric.Value [] ed = new Numeric.Value [n + 1];
                        ed [0] = v;

                        for (int i = 1 ; i < ed.length ; ++i)
                        {
                            if (i < expectedData.length)
                                ed [i] = expectedData [i];
                            else
                                ed [i] = type.undef;
                        }

                        setExpectedData (ed);
                    }
                }
                else
                {
                    expectedData [row] = v;
                }
                return expectedData [row].toString (radix);
            }
            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 void setAddress (int address)
        {
            Event.checkIsEventDispatchThread ();
            this.address = address;
            buildAddresses ();
            fireTableRowsUpdated (0, getRowCount () - 1);
        }

        public void setRadix (int radix)
        {
            Event.checkIsEventDispatchThread ();
            this.radix = radix;
            fireTableRowsUpdated (0, getRowCount () - 1);
        }

        public void setType (Numeric.Type type)
        {
            Event.checkIsEventDispatchThread ();
            if (type != this.type)
            {
                this.type = type;

                for (int i = 0 ; i < actualData.length ; i++)
                {
                    try
                    {
                        actualData [i] = type.createValue (actualData [i]);
                    }
                    catch (ValueException e)
                    {
                        actualData [i] = type.undef;
                    }
                }

                for (int i = 0 ; i < expectedData.length ; i++)
                {
                    try
                    {
                        expectedData [i] = type.createValue (expectedData [i]);
                    }
                    catch (ValueException e)
                    {
                        expectedData [i] = type.undef;
                    }
                }

                buildAddresses ();
                fireTableRowsUpdated (0, getRowCount () - 1);
            }
        }

        public void setBigValueFlags (BigValueFlags bigValueFlags)
        {
            Event.checkIsEventDispatchThread ();
            if (!this.bigValueFlags.equals (bigValueFlags))
            {
                this.bigValueFlags = bigValueFlags;
                buildAddresses ();
                fireTableRowsUpdated (0, getRowCount () - 1);
            }
        }

        private void buildAddresses ()
        {
            addresses = new int [getRowCount ()];
            int addr = address;
            int inc = bigValueFlags.addressIncrement (type.getSize ());

            for (int i = 0 ; i < addresses.length ; i++)
            {
                addresses [i] = addr;
                addr += inc;
            }
        }

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

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

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

        public void setExpectedData (Numeric.Value [] data)
        {
            if (!isTester)
                throw new IllegalStateException ("Not tester");
            assert data.length > 0 && data.length <= 32 : data.length;
            expectedData = data.clone ();
            boolean [] ua = new boolean [expectedData.length];

            for (int i = 0 ; i < ua.length ; ++i)
            {
                ua [i] = i < unapplied.length ? unapplied [i] : false;
            }

            unapplied = ua;
            buildAddresses ();
            fireTableDataChanged ();
        }

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

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

        public boolean isError (int row, int col)
        {
            if (isTester && col == COL_ACTUAL)
            {
                if (row >= expectedData.length || row >= actualData.length)
                    return true;
                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;
        }
    }
}


