
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 Command8
    implements Command<Command8.Result>
{
    public static final String tag = "cmd_8";
    public static final String typeName = "8 Diagnostics";

    private final String name;
    private final String description;

    private final int slaveId;
    private final int subFunction;
    private final int radix;
    private final int sendData;

    private Result actualResult;
    private final Result expectedResult;

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

        Result (Numeric.PatVal rcvdData, ModbusException exception,
            long responseTime)
        {
            assert rcvdData.getType () == Numeric.Type.uint16;
            this.rcvdData = rcvdData;
            this.exception = exception;
            this.responseTime = responseTime;
        }

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

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

        public Numeric.Value getActualData ()
        {
            return (Numeric.Value) rcvdData;
        }

        public Numeric.Pattern getExpectedData ()
        {
            return (Numeric.Pattern) rcvdData;
        }

        @Override
        public boolean matches (Command.Result obj)
        {
            Command8.Result r = (Command8.Result) obj;
            if (exception != null)
                return exception.matches (r.exception);
            if (r.exception != null)
                return false;
            assert rcvdData.getType () == Numeric.Type.uint16;
            assert r.rcvdData.getType () == Numeric.Type.uint16;
            Numeric.Pattern p = (Numeric.Pattern) rcvdData;
            return p.matches ((Numeric.Value) r.rcvdData);
        }

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

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

    Command8 (String name, String description, int slaveId, int subFunction,
        int radix, int sendData, ModbusException exception,
        Numeric.Pattern rcvdData)
    {
        assert rcvdData.getType () == Numeric.Type.uint16;
        this.name = name;
        this.description = description;
        this.slaveId = slaveId;
        this.subFunction = subFunction;
        this.radix = radix;
        this.sendData = sendData;
        expectedResult = new Result (rcvdData, exception, 0);
        actualResult = null;
    }

    Command8 (String name, String description, int slaveId, int subFunction,
        int radix, int sendData)
    {
        this.name = name;
        this.description = description;
        this.slaveId = slaveId;
        this.subFunction = subFunction;
        this.radix = radix;
        this.sendData = sendData;
        expectedResult = null;
        actualResult = null;
    }

    public int getSlaveId ()
    {
        return slaveId;
    }

    public int getSubFunction ()
    {
        return subFunction;
    }

    public int getRadix ()
    {
        return radix;
    }

    public int getSendData ()
    {
        return sendData;
    }

    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;
        Command8 cmd = (Command8) obj;
        if (!cmd.name.equals (name))
            return false;
        if (!cmd.description.equals (description))
            return false;
        if (cmd.slaveId != slaveId)
            return false;
        if (cmd.subFunction != subFunction)
            return false;
        if (cmd.radix != radix)
            return false;
        if (cmd.sendData != sendData)
            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 + subFunction;
        hash = 29 * hash + radix;
        hash = 29 * hash + sendData;
        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
    {
        try
        {
            MessageBuilder body = new MessageBuilder ();
            body.addInt (subFunction);
            body.addInt (sendData);
            ModbusTransaction trans = client.createTransaction (slaveId,
                Modbus.FUNC_DIAGNOSTICS, body.getData ());
            client.handleTransaction (trans);
            assert trans.isFinished () : trans.getState ();
            Tracer tracer = client.getTracer ();
            if (tracer != null)
                tracer.endTransaction ();
            ModbusException exception = trans.getException ();
            if (exception != null)
                throw exception;
            ModbusMessage response = trans.getResponse ();
            if (settings.getGeneral ().getStrictChecking ().getValue ())
            {
                response.checkSize (4);
                if (response.getInt (0) != subFunction)
                {
                    Modbus.dataError ("C101",
                        "Wrong subfunction in response: " +
                        response.getInt (0) + " instead of " + subFunction);
                }
            }
            else
            {
                response.checkMinSize (4);
            }
            actualResult = new Result (
                Numeric.Type.uint16.createValue (response.getInt (2)), null,
                trans.getResponseTime ());
        }
        catch (ModbusException e)
        {
            actualResult = new Result (Numeric.Type.uint16.undef, e, 0);
        }
    }

    public void save (Xml.Saver saver)
        throws IOException
    {
        saver.saveValue ("name", name);
        saver.saveValue ("description", description);
        saver.saveValue ("slaveid", slaveId);
        saver.saveValue ("subfunction", subFunction);
        saver.saveValue ("radix", RadixSelector.convertRadix (radix));
        saver.saveValue ("data", sendData, 10);
        if (expectedResult != null)
        {
            saver.startTag ("result");
            saver.saveValue ("drcvddata",
                (Numeric.Pattern) expectedResult.rcvdData, 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 subFunction;
        private int radix;
        private int sendData;
        private boolean compareData;
        private Numeric.Pattern rcvdData;
        private ModbusException exception;

        XmlLoader (Xml.Receiver<Command> receiver, boolean isTester)
        {
            this.receiver = receiver;
            this.isTester = isTester;
            name = "";
            description = "";
            slaveId = 1;
            subFunction = 1;
            radix = 10;
            sendData = 0;
            compareData = true;
            rcvdData = Numeric.Type.uint16.emptyPattern;
            assert rcvdData.getType () == Numeric.Type.uint16;
            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 ("subfunction"))
            {
                return new Xml.IntegerLoader (0, 65535,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                            throws ValueException
                        {
                            subFunction = value;
                        }
                    });
            }

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

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

            if (tag.equalsIgnoreCase ("senddata"))
            {
                return new Xml.IntegerLoader (0, 65535, radix,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            sendData = 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 ("drcvddata"))
                        {
                            return new Xml.NumericPatternLoader (
                                Numeric.Type.uint16, 10,
                                new Xml.Receiver<Numeric.Pattern> ()
                                {
                                    public void receive (Numeric.Pattern value)
                                    {
                                        assert rcvdData.getType () ==
                                            Numeric.Type.uint16;
                                        rcvdData = value;
                                    }
                                });
                        }

                        if (tag.equalsIgnoreCase ("rcvddata"))
                        {
                            return new Xml.NumericLoader (
                                Numeric.Type.uint16, radix, true,
                                new Xml.Receiver<Numeric.Value> ()
                                {
                                    public void receive (Numeric.Value value)
                                    {
                                        assert rcvdData.getType () ==
                                            Numeric.Type.uint16;
                                        rcvdData = new Numeric.Pattern (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 (isTester)
            {
                if (exception != null || !compareData)
                    rcvdData = Numeric.Type.uint16.emptyPattern;
                assert rcvdData.getType () == Numeric.Type.uint16;
                Command8 command = new Command8 (name, description, slaveId,
                    subFunction, radix, sendData, exception, rcvdData);
                receiver.receive (command);
            }
            else
            {
                Command8 command = new Command8 (name, description, slaveId,
                    subFunction, radix, sendData);
                receiver.receive (command);
            }
        }
    }
}

