
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 CommandFileTableModel
    extends CommandTableModel
    implements HasCellLabels
{
    private FileGroup [] groups;
    private int size;
    private Numeric.Value [] actualData;
    private Numeric.Pattern [] expectedData;
    private boolean [] unapplied;
    private int [] files;
    private int [] addresses;
    private Numeric.Type type;
    private int radix;
    private BigValueFlags bigValueFlags;

    public CommandFileTableModel (BigValueFlags bigValueFlags,
        FileGroup [] groups,
        Numeric.Type type, int radix, boolean isTester,
        boolean isEditor, boolean isWrite)
    {
        super (isTester, isEditor, isWrite, true);
        Event.checkIsEventDispatchThread ();
        this.bigValueFlags = bigValueFlags;
        this.groups = groups;
        if (groups == null)
            throw new NullPointerException ();
        this.type = type;
        this.radix = radix;
        buildData ();
    }

    public void setGroups (FileGroup [] groups)
    {
        if (groups == null)
            throw new NullPointerException ();
        this.groups = groups;
        buildData ();
    }

    private void buildData ()
    {
        size = FileGroup.countValues (groups);
        if (isTester)
        {
            actualData = type.createUndefArray (size);
            expectedData = type.createEmptyPatternArray (size);
        }
        else
        {
            actualData = type.createZeroArray (size);
        }
        buildAddresses ();
        buildUnapplied ();
        fireTableDataChanged ();
    }

    public void reset ()
    {
        actualData = isTester ? type.createUndefArray (size) :
            type.createZeroArray (size);
        fireTableRowsUpdated (0, getRowCount () - 1);
    }

    public void resetExpected ()
    {
        assert isTester;
        expectedData = type.createEmptyPatternArray (size);
        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 < size ; i++)
            {
                try
                {
                    actualData [i] = type.createValue (actualData [i]);
                }
                catch (ValueException e)
                {
                    actualData [i] = type.zero;
                }

                if (isTester)
                {
                    try
                    {
                        expectedData [i] = type.createPattern (
                            expectedData [i].toString (radix), radix);
                    }
                    catch (ValueException e)
                    {
                        expectedData [i] = type.emptyPattern;
                    }
                }
            }

            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 ()
    {
        files = new int [size];
        addresses = new int [size];
        int inc = bigValueFlags.addressIncrement (type.getSize ());
        int index = 0;

        for (FileGroup group : groups)
        {
            for (int i = 0 ; i < group.nvalues ; i++)
            {
                files [index] = group.fileNum;
                addresses [index] = group.address + i * inc;
                ++index;
            }
        }

        assert index == size;
    }

    private void buildUnapplied ()
    {
        unapplied = new boolean [size];

        for (int i = 0 ; i < size ; ++i)
            unapplied [i] = true;
    }

    public int getRowCount ()
    {
        Event.checkIsEventDispatchThread ();
        return size;
    }

    public String getValueAt (int row, int col)
    {
        Event.checkIsEventDispatchThread ();
        if (col == COL_FILE)
            return row >= size ? "" : files [row] + "";
        if (col == COL_ADDRESS)
            return row >= size ? "" : addresses [row] + "";
        if (isTester && col == COL_EXPECTED)
        {
            return row >= size ? "" : expectedData [row].toString (radix);
        }
        return row >= size ? "" : actualData [row].toString (radix);
    }

    @Override
    public String getCellLabel (int row, int col)
    {
        if (row >= addresses.length)
            return "";
        return files [row] + ":" + addresses [row] + " " + getColumnName (col);
    }

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

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

    @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.Pattern [] getExpectedData ()
    {
        if (!isTester)
            throw new IllegalStateException ("Not tester");
        return expectedData;
    }

    public void setActualData (Numeric.Value [] actualData)
    {
        Event.checkIsEventDispatchThread ();

        for (int i = 0 ; i < size ; ++i)
        {
            this.actualData [i] = i < actualData.length ? actualData [i] :
                type.undef;
        }

        buildUnapplied ();
        fireTableDataChanged ();
    }

    public void setExpectedData (Numeric.Pattern [] expectedData)
    {
        Event.checkIsEventDispatchThread ();
        if (!isTester)
            throw new IllegalStateException ("Not tester");
        if (expectedData.length != size)
        {
            throw new IllegalArgumentException ("expectedData is wrong size: " +
                expectedData.length + " instead of " + size);
        }
        this.expectedData = expectedData.clone ();
        buildUnapplied ();
        fireTableDataChanged ();
    }

    public void setExpectedData (Numeric.Value [] expectedData)
    {
        setExpectedData (Numeric.createPatternArray (expectedData));
    }

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

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

    public boolean haveActualValuesChanged (Numeric.Value [] values)
    {
        boolean changed = false;

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

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

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

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

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

