
package uk.co.wingpath.modbusgui;

import java.io.*;
import java.util.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.util.*;
import uk.co.wingpath.xml.*;
import uk.co.wingpath.modbus.*;

public class Command23
    implements Command<Command23.Result>
{
    public static final String tag = "cmd_23";
    public static final String typeName =
        "23 Read/Write Multiple Holding Registers";

    private final String name;
    private final String description;

    private final int slaveId;
    private final int readAddress;
    private final int readCount;
    private final int writeAddress;
    private final Numeric.Value [] writeData;
    private final Numeric.Type type;
    private final int radix;

    private Result actualResult;
    private final Result expectedResult;

    private class Model
        extends ModbusModel
    {
        private Numeric.Value [] readData;
        private Numeric.Value [] writeData;
        private int readAddress;
        private int writeAddress;

        Model (int readAddress, Numeric.Value [] readData,
            int writeAddress, Numeric.Value [] writeData,
            BigValueFlags bigValueFlags)
        {
            super (bigValueFlags, null);
            this.readAddress = readAddress;
            this.readData = readData;
            this.writeAddress = writeAddress;
            this.writeData = writeData;
        }

        @Override
        public int getValueSize (int address)
        {
            return type.getSize ();
        }

        private int findAddress (int addr, int base, int count)
            throws ModbusException
        {
            int i = addr - base;
            int inc = getBigValueFlags ().addressIncrement (type.getSize ());
            // Address errors should not occur, so probably ought to
            // throw an IllegalArgumentException
            if (i % inc != 0)
                Modbus.addressError (null, "No register at address " + addr);
            i /= inc;
            // Address errors should not occur, so probably ought to
            // throw an IllegalArgumentException
            if (i < 0 || i >= count)
                Modbus.addressError (null, "No register at address " + addr);
            return i;
        }

        @Override
        public boolean isFloat (int address)
        {
            return type.isFloat ();
        }

        @Override
        public boolean isSigned (int address)
            throws ModbusException
        {
            return type.isSigned ();
        }

        @Override
        public int getRadix (int address)
            throws ModbusException
        {
            return radix;
        }

        @Override
        public void setValue (int address, long val)
            throws ModbusException
        {
            int row = findAddress (address, readAddress, readData.length);
            try
            {
                readData [row] = type.createValue (val);
            }
            catch (ValueException e)
            {
            }
        }

        @Override
        public void setValue (int address, double val)
            throws ModbusException
        {
            int row = findAddress (address, readAddress, readData.length);
            try
            {
                readData [row] = type.createValue (val);
            }
            catch (ValueException e)
            {
            }
        }

        @Override
        public long getLongValue (int address)
            throws ModbusException
        {
            int row = findAddress (address, writeAddress, writeData.length);
            Numeric.Value d = writeData [row];
            return d.getLongValue ();
        }

        @Override
        public double getDoubleValue (int address)
            throws ModbusException
        {
            int row = findAddress (address, writeAddress, writeData.length);
            Numeric.Value d = writeData [row];
            return d.getDoubleValue ();
        }
    }

    public class Result
        implements Command.Result
    {
        private final Numeric.PatVal [] readData;
        private final ModbusException exception;
        private final long responseTime;

        Result (Numeric.PatVal [] readData, ModbusException exception,
            long responseTime)
        {
            this.readData = readData.clone ();
            this.exception = exception;
            this.responseTime = responseTime;
        }

        @Override
        public ModbusException getException ()
        {
            return exception;
        }

        @Override
        public long getResponseTime ()
        {
            return responseTime;
        }

        public Numeric.Value [] getActualReadData ()
        {
            return (Numeric.Value []) readData;
        }

        public Numeric.Pattern [] getExpectedReadData ()
        {
            return (Numeric.Pattern []) readData;
        }

        @Override
        public boolean matches (Command.Result obj)
        {
            Command23.Result r = (Command23.Result) obj;
            if (exception != null)
                return exception.matches (r.exception);
            if (r.exception != null)
                return false;
            return Numeric.matches ((Numeric.Pattern []) readData,
                (Numeric.Value []) r.readData);
        }

        @Override
        public boolean equals (Object obj)
        {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (obj.getClass () != getClass ())
                return false;
            Command23.Result r = (Command23.Result) obj;
            if (r.exception != null)
                return r.exception.equals (exception);
            if (exception != null)
                return false;
            if (!Arrays.equals (r.readData, readData))
                return false;
            return true;
        }

        @Override
        public int hashCode ()
        {
            return 0;
        }
    }

    Command23 (String name, String description, int slaveId, int readAddress,
        int readCount, int writeAddress, Numeric.Value [] writeData,
        Numeric.Type type, int radix,
        ModbusException exception, Numeric.Pattern [] readData)
    {
        this.name = name;
        this.description = description;
        this.slaveId = slaveId;
        this.readAddress = readAddress;
        this.readCount = readCount;
        this.writeAddress = writeAddress;
        this.writeData = writeData.clone ();
        this.type = type;
        this.radix = radix;
        expectedResult = new Result (readData, exception, 0);
        actualResult = null;
    }

    Command23 (String name, String description, int slaveId, int readAddress,
        int readCount, int writeAddress, Numeric.Value [] writeData,
        Numeric.Type type, int radix)
    {
        this.name = name;
        this.description = description;
        this.slaveId = slaveId;
        this.readAddress = readAddress;
        this.readCount = readCount;
        this.writeAddress = writeAddress;
        this.writeData = writeData.clone ();
        this.type = type;
        this.radix = radix;
        expectedResult = null;
        actualResult = null;
    }

    public int getSlaveId ()
    {
        return slaveId;
    }

    public int getReadAddress ()
    {
        return readAddress;
    }

    public int getReadCount ()
    {
        return readCount;
    }

    public int getWriteAddress ()
    {
        return writeAddress;
    }

    public Numeric.Value [] getWriteData ()
    {
        return writeData;
    }

    public Numeric.Type getType ()
    {
        return type;
    }

    public int getRadix ()
    {
        return radix;
    }

    public Result getActualResult ()
    {
        return actualResult;
    }

    public void setActualResult (Result result)
    {
        actualResult = result;
    }

    public Result getExpectedResult ()
    {
        return expectedResult;
    }

    @Override
    public boolean equals (Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (obj.getClass () != getClass ())
            return false;
        Command23 cmd = (Command23) obj;
        if (!cmd.name.equals (name))
            return false;
        if (!cmd.description.equals (description))
            return false;
        if (cmd.slaveId != slaveId)
            return false;
        if (cmd.readAddress != readAddress)
            return false;
        if (cmd.readCount != readCount)
            return false;
        if (cmd.writeAddress != writeAddress)
            return false;
        if (cmd.type != type)
            return false;
        if (cmd.radix != radix)
            return false;
        if (!Arrays.equals (cmd.writeData, writeData))
            return false;
        if (expectedResult == null)
            return cmd.expectedResult == null;
        return expectedResult.equals (cmd.expectedResult);
    }

    @Override
    public int hashCode ()
    {
        int hash = 3;
        hash = 29 * hash + name.hashCode ();
        hash = 29 * hash + description.hashCode ();
        hash = 29 * hash + slaveId;
        hash = 29 * hash + readAddress;
        hash = 29 * hash + readCount;
        hash = 29 * hash + writeAddress;
        hash = 29 * hash + type.hashCode ();
        hash = 29 * hash + radix;
        hash = 29 * hash + Arrays.hashCode (writeData);
        return hash;
    }

    @Override
    public String toString ()
    {
        return name;
    }

    public String getDescription ()
    {
        return description;
    }

    public String getTypeName ()
    {
        return typeName;
    }

    public String getTag ()
    {
        return tag;
    }

    public void send (ModbusClient client, Settings settings)
        throws InterruptedException, IOException, ValueException
    {
        Numeric.Value [] readData = type.createUndefArray (readCount);
        try
        {
            BigValueFlags bigValueFlags = settings.getBigValue ().getValue ();
            GeneralSettings gs = settings.getGeneral ();
            int maxPdu = gs.getMaxPdu ().getValue ();
            boolean checkCountLimits =
                gs.getCheckCountLimits ().getValue ();
            boolean strictChecking = gs.getStrictChecking ().getValue ();
            boolean allowLongMessages = gs.getAllowLongMessages ().getValue ();
            ModbusMaster master = new ModbusMaster (
                new Model (
                    readAddress, readData,
                    writeAddress, writeData, bigValueFlags),
                client, slaveId, maxPdu, checkCountLimits,
                strictChecking, allowLongMessages, false);
            master.readWriteRegisters (
                readAddress, readCount,
                writeAddress, writeData.length);
            actualResult = new Result (readData, null,
                master.getResponseTime ());
        }
        catch (ModbusException e)
        {
            actualResult = new Result (readData, e, 0);
        }
    }

    public void save (Xml.Saver saver)
        throws IOException
    {
        saver.saveValue ("name", name);
        saver.saveValue ("description", description);
        saver.saveValue ("slaveid", slaveId);
        saver.saveValue ("readaddress", readAddress);
        saver.saveValue ("readcount", readCount);
        saver.saveValue ("writeaddress", writeAddress);
        saver.saveValue ("type", type.toString ());
        saver.saveValue ("radix", RadixSelector.convertRadix (radix));
        saver.saveValue ("dwritedata", writeData, 10);
        if (expectedResult != null)
        {
            saver.startTag ("result");
            saver.saveValue ("preaddata",
                (Numeric.Pattern []) expectedResult.readData, 10);
            if (expectedResult.exception != null)
                CommandResult.saveException (saver, expectedResult.exception);
            saver.endTag ("result");
        }
    }

    public static Xml.Loader getXmlLoader (Xml.Receiver<Command> receiver,
        boolean isTester)
    {
        return new XmlLoader (receiver, isTester);
    }

    private static class XmlLoader
        extends Xml.AbstractLoader
    {
        private final Xml.Receiver<Command> receiver;
        private final boolean isTester;
        private String name;
        private String description;
        private int slaveId;
        private int readAddress;
        private int readCount;
        private int writeAddress;
        private Numeric.Value [] writeData;
        private Numeric.Type type;
        private int radix;
        private boolean compareData;
        private Numeric.Pattern [] readData;
        private ModbusException exception;

        XmlLoader (Xml.Receiver<Command> receiver, boolean isTester)
        {
            this.receiver = receiver;
            this.isTester = isTester;
            name = "";
            description = "";
            slaveId = 1;
            type = Numeric.Type.int16;
            radix = 10;
            compareData = true;
            readAddress = 0;
            readCount = 1;
            writeAddress = 0;
            writeData = null;
            readData = null;
            exception = null;
        }

        @Override
        public Xml.Loader startChild (String tag)
        {
            if (tag.equalsIgnoreCase ("name"))
            {
                return new Xml.StringLoader (new Xml.Receiver<String> ()
                {
                    public void receive (String value)
                        throws ValueException
                    {
                        if (value.equals (""))
                            throw new ValueException ("Name missing");
                        name = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("description"))
            {
                return new Xml.StringLoader (new Xml.Receiver<String> ()
                {
                    public void receive (String value)
                        throws ValueException
                    {
                        description = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("slaveid"))
            {
                return new Xml.IntegerLoader (0, 255,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            slaveId = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("readaddress"))
            {
                return new Xml.IntegerLoader (0, 65535,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            readAddress = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("readcount"))
            {
                return new Xml.IntegerLoader (0, 65535,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            readCount = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("writeaddress"))
            {
                return new Xml.IntegerLoader (0, 65535,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            writeAddress = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("type"))
            {
                return new Xml.StringLoader (new Xml.Receiver<String> ()
                {
                    public void receive (String value)
                        throws ValueException
                    {
                        try
                        {
                            if (value.equals ("discrete"))
                                type = Numeric.Type.int1;
                            else
                                type = Numeric.Type.valueOf (value);
                        }
                        catch (IllegalArgumentException e)
                        {
                            throw new ValueException (e.getMessage ());
                        }
                    }
                });
            }

            if (tag.equalsIgnoreCase ("radix"))
            {
                return new Xml.StringLoader (new Xml.Receiver<String> ()
                {
                    public void receive (String value)
                        throws ValueException
                    {
                        if (!value.equals ("10"))
                        {
                            if (value.equals ("10U"))
                                value = "10";
                            type = type.unsigned ();
                        }
                        radix = RadixSelector.convertRadix (value);
                    }
                });
            }

            if (tag.equalsIgnoreCase ("dwritedata"))
            {
                return new Xml.NumericArrayLoader (type, 10, false,
                    new Xml.Receiver<Numeric.Value []> ()
                {
                    public void receive (Numeric.Value [] value)
                        throws ValueException
                    {
                        if (value.length > 65535)
                        {
                            throw new ValueException (
                                "Too many values (> 65535)");
                        }
                        writeData = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("xwritedata"))
            {
                return new Xml.NumericArrayLoader (type, 16, false,
                    new Xml.Receiver<Numeric.Value []> ()
                {
                    public void receive (Numeric.Value [] value)
                        throws ValueException
                    {
                        if (value.length > 65535)
                        {
                            throw new ValueException (
                                "Too many values (> 65535)");
                        }
                        writeData = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("writedata"))
            {
                return new Xml.NumericArrayLoader (type, radix, false,
                    new Xml.Receiver<Numeric.Value []> ()
                {
                    public void receive (Numeric.Value [] value)
                        throws ValueException
                    {
                        if (value.length > 65535)
                        {
                            throw new ValueException (
                                "Too many values (> 65535)");
                        }
                        writeData = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("comparevalues"))
            {
                return new Xml.BooleanLoader (new Xml.Receiver<Boolean> ()
                {
                    public void receive (Boolean value)
                        throws ValueException
                    {
                        compareData = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("result"))
            {
                return new Xml.AbstractLoader ()
                {
                    @Override
                    public Xml.Loader startChild (String tag)
                    {
                        if (tag.equalsIgnoreCase ("preaddata"))
                        {
                            return new Xml.NumericPatternArrayLoader (type, 10,
                                new Xml.Receiver<Numeric.Pattern []> ()
                            {
                                public void receive (Numeric.Pattern [] value)
                                    throws ValueException
                                {
                                    if (value.length > 65535)
                                    {
                                        throw new ValueException (
                                            "Too many values (> 65535)");
                                    }
                                    readData = value;
                                }
                            });
                        }

                        if (tag.equalsIgnoreCase ("dreaddata"))
                        {
                            return new Xml.NumericArrayLoader (type, 10, true,
                                new Xml.Receiver<Numeric.Value []> ()
                            {
                                public void receive (Numeric.Value [] value)
                                    throws ValueException
                                {
                                    if (value.length > 65535)
                                    {
                                        throw new ValueException (
                                            "Too many values (> 65535)");
                                    }
                                    readData =
                                        Numeric.createPatternArray (value);
                                }
                            });
                        }

                        if (tag.equalsIgnoreCase ("xreaddata"))
                        {
                            return new Xml.NumericArrayLoader (type, 16, true,
                                new Xml.Receiver<Numeric.Value []> ()
                            {
                                public void receive (Numeric.Value [] value)
                                    throws ValueException
                                {
                                    if (value.length > 65535)
                                    {
                                        throw new ValueException (
                                            "Too many values (> 65535)");
                                    }
                                    readData =
                                        Numeric.createPatternArray (value);
                                }
                            });
                        }

                        if (tag.equalsIgnoreCase ("readdata"))
                        {
                            return new Xml.NumericArrayLoader (type, radix,
                                true, new Xml.Receiver<Numeric.Value []> ()
                            {
                                public void receive (Numeric.Value [] value)
                                    throws ValueException
                                {
                                    if (value.length > 65535)
                                    {
                                        throw new ValueException (
                                            "Too many values (> 65535)");
                                    }
                                    readData =
                                        Numeric.createPatternArray (value);
                                }
                            });
                        }

                        if (tag.equalsIgnoreCase ("exception"))
                        {
                            return CommandResult.getExceptionLoader (
                                new Xml.Receiver<ModbusException> ()
                                {
                                    public void receive (ModbusException e)
                                    {
                                        exception = e;
                                    }
                                });
                        }

                        return null;
                    }
                };
            }

            return null;
        }

        public void end (String value)
            throws ValueException
        {
            if (!value.equals (""))
                throw new ValueException ("Value not allowed");
            if (name.equals (""))
                throw new ValueException ("<name> missing");
            if (writeData == null)
                throw new ValueException ("<writedata> missing");
            if (isTester)
            {
                if (readData == null || exception != null || !compareData)
                {
                    readData = type.createEmptyPatternArray (readCount);
                }
                else
                {
                    if (readData.length != readCount)
                    {
                        throw new ValueException (
                            "Inconsistent read count and data length");
                    }
                }
                Command23 command = new Command23 (name, description, slaveId,
                    readAddress, readCount, writeAddress, writeData, type,
                    radix, exception, readData);
                receiver.receive (command);
            }
            else
            {
                Command23 command = new Command23 (name, description, slaveId,
                    readAddress, readCount, writeAddress, writeData, type,
                    radix);
                receiver.receive (command);
            }
        }
    }
}

