
package uk.co.wingpath.modbusgui;

import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.awt.event.*;
import uk.co.wingpath.util.*;
import uk.co.wingpath.modbus.*;
import uk.co.wingpath.gui.*;
import uk.co.wingpath.event.*;

public class CommandDiscreteTableModel
    extends CommandTableModel
    implements HasCellLabels
{
    private Numeric.Value [] actualData;
    private Numeric.Value [] expectedData;
    private boolean [] unapplied;
    private int base;
    private int size;

    public CommandDiscreteTableModel (int base, int size, boolean isTester,
        boolean isEditor, boolean isWrite)
    {
        super (isTester, isEditor, isWrite, false);
        this.base = base;
        this.size = size;
        if (isTester)
        {
            actualData = Numeric.Type.int1.createUndefArray (size);
            expectedData = Numeric.Type.int1.createUndefArray (size);
        }
        else
        {
            actualData = Numeric.Type.int1.createZeroArray (size);
        }
        unapplied = new boolean [size];
        buildUnapplied ();
    }

    private void buildUnapplied ()
    {
        int length = isTester ? expectedData.length : actualData.length;
        boolean [] u = new boolean [length];

        for (int i = 0 ; i < length ; ++i)
        {
            if (i < unapplied.length)
                u [i] = unapplied [i];
            else
                u [i] = true;
        }

        unapplied = u;
    }

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

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

    @Override
    public String getCellLabel (int row, int col)
    {
        String label = "" + (base + 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 : 2);
        if (width < minWidth)
            width = minWidth;
        return width;
    }

    @Override
    public String fromString (String value, int row, int col)
        throws ValueException
    {
        Event.checkIsEventDispatchThread ();
        if (col == COL_ADDRESS)
            return value;         // shouldn't happen
        if (isTester && col == COL_EXPECTED)
        {
            expectedData [row] = Numeric.Type.int1.fromString (value, 2);
            return expectedData [row].toString (2);
        }
        if (value.equals (""))
            throw new ValueException ("Value missing");
        actualData [row] = Numeric.Type.int1.fromString (value, 2);
        return actualData [row].toString (2);
    }

    @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 ()
    {
        return expectedData.clone ();
    }

    public void reset ()
    {
        actualData = isTester ?
            Numeric.Type.int1.createUndefArray (actualData.length) :
            Numeric.Type.int1.createZeroArray (actualData.length);
        fireTableRowsUpdated (0, getRowCount () - 1);
    }

    public void resetExpected ()
    {
        assert isTester;
        expectedData =
            Numeric.Type.int1.createUndefArray (actualData.length);
        fireTableRowsUpdated (0, getRowCount () - 1);
    }

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

    public void setSize (int size)
    {
        Event.checkIsEventDispatchThread ();
        if (size != actualData.length)
        {
            {
                Numeric.Value [] d =
                    isTester ? Numeric.Type.int1.createUndefArray (size) :
                        Numeric.Type.int1.createZeroArray (size);

                for (int i = 0 ; i < size && i < actualData.length ; i++)
                    d [i] = actualData [i];

                actualData = d;
            }

            if (isTester)
            {
                Numeric.Value [] d = Numeric.Type.int1.createUndefArray (size);

                for (int i = 0 ; i < size && i < expectedData.length ; i++)
                    d [i] = expectedData [i];

                expectedData = d;
            }

            buildUnapplied ();
            fireTableDataChanged ();
        }
    }

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

    public void setExpectedData (Numeric.Value [] expectedData)
    {
        if (!isTester)
            throw new IllegalStateException ("Not tester");
        Event.checkIsEventDispatchThread ();
        this.expectedData = expectedData.clone ();
        buildUnapplied ();
        fireTableDataChanged ();
    }

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

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

    @Override
    public boolean haveActualValuesChanged (Numeric.Value [] values)
    {
        assert actualData.length == unapplied.length :
            actualData.length + " " + unapplied.length;
        boolean changed = false;

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

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

    @Override
    public boolean haveExpectedValuesChanged (Numeric.PatVal [] values)
    {
        assert isTester;
        assert expectedData.length == unapplied.length :
            expectedData.length + " " + unapplied.length;
        Numeric.Value [] vals = (Numeric.Value []) values;
        boolean changed = false;

        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;
    }
}

