
package uk.co.wingpath.modbus;

import uk.co.wingpath.util.*;

/**
* This class is used to represent Modbus messages.
* <p>This class is abstract - all instances must created using one of the
* sub-classes: {@link ModbusRequest} or {@link ModbusResponse}.
* <p>The "size" of a ModbusMessage is the number of data bytes in the message
* (excluding the header fields and CRC/LRC). It is one less than the
* "PDU size" that is now defined in the Modbus specification (this code
* was originally written long before the current version of the specification).
*/
public abstract class ModbusMessage
{
    protected final int slaveId;        // Slave identifier
    protected final int function;       // Function code
    protected final byte data [];       // Data
    protected final int transId;        // Transaction identifier

    /**
    * Constructs a ModbusMessage with the specified slave id,
    * function code, transaction identifier, and data.
    * @param slaveId slave identifier.
    * @param function function code.
    * @param transId transaction identifier, or -1 if the transaction
    * identifier is not defined (i.e. for RTU and ASCII packets).
    * @param data body of message.
    */
    protected ModbusMessage (int slaveId, int function, int transId,
        byte [] data)
    {
        this.slaveId = slaveId;
        this.function = function;
        this.transId = transId;
        this.data = data;
    }

    /**
    * Gets the slave identifier.
    * @return the slave identifier.
    */
    public int getSlaveId ()
    {
        return slaveId;
    }

    /**
    * Gets the transaction identifier.
    * If the transaction identifier is not defined (i.e. for RTU ans ASCII),
    * 0 is returned.
    * @return the transaction identifier.
    */
    public int getTransId ()
    {
        return transId < 0 ? 0 : transId;
    }

    /**
    * Gets the function code.
    * @return the function code.
    */
    public int getFunctionCode ()
    {
        return function;
    }

    /**
    * Gets the message size (number of bytes of data).
    * This size is one less than the PDU size.
    * @return message size.
    */
    public int size ()
    {
        return data.length;
    }

    /**
    * Checks that the message size is consistent with the function code.
    * <p>This method is used by {@link RtuPacketType} to help detect the
    * end of a message. It is not recommended for any other purpose, since it
    * straddles protocol layers.
    * @throws ModbusException if the size is inconsistent.
    */
    public abstract void checkSize (boolean allowLongMessages)
        throws ModbusException;

    /**
    * Gets the data.
    * @return the data array.
    */
    public byte [] getData ()
    {
        return data;
    }

    /**
    * Gets a byte of the data.
    * @param offset start of data required.
    * @return the requested data byte.
    */
    public int getByte (int offset)
    {
        return (data [offset] & 0xff);
    }

    /**
    * Gets an unsigned 16-bit integer from the data.
    * @param offset start of data required.
    * @return the requested 16-bit integer.
    */
    public int getInt (int offset)
    {
        return (((data [offset] & 0xff) << 8) | (data [offset + 1] & 0xff));
    }

    /**
    * Gets a sub-array of the data.
    * @param offset start of data required.
    * @param len number of bytes required.
    * @return the requested data.
    */
    public byte [] getData (int offset, int len)
    {
        byte [] a = new byte [len];
        System.arraycopy (data, offset, a, 0, len);
        return a;
    }

    /**
    * Returns a string describing the function code (and sub-function code,
    * if any).
    * @return function code description.
    */
    public String getFunctionName ()
    {
        String str = Modbus.getFunctionName (function);
        if (str == null)
            str = "Function code " + function;
        if (function == Modbus.FUNC_DIAGNOSTICS && data.length >= 2)
        {
            int subfunc = getInt (0);
            String s = Modbus.getDiagFunctionName (subfunc);
            if (s != null)
                str += " - " + s;
            else
                str += " - subfunction " + subfunc;
        }
        else if (function == Modbus.FUNC_ENCAPSULATED_INTERFACE_TRANSPORT &&
            data.length >= 1)
        {
            int type = getByte (0);
            String s = Modbus.getEitTypeName (type);
            if (s != null)
                str += " - " + s;
            else
                str += " - type " + type;
        }

        return str;
    }

    void traceSend (Tracer tracer)
    {
        trace (tracer, ">", null);
    }

    void traceReceive (Tracer tracer)
    {
        trace (tracer, "<", null);
    }

    abstract void traceExplanation (Tracer tracer);

    protected void trace (Tracer tracer, String prefix, String helpId)
    {
        if (tracer == null || !tracer.isTraceEnabled ())
            return;
        StringBuilder str = new StringBuilder ();
        if (transId >= 0)
        {
            str.append (" transid ");
            str.append (transId);
        }
        str.append (" slave ");
        str.append (slaveId);
        str.append (" pdulen ");
        str.append (data.length + 1);
        str.append (" func ");
        str.append (function);
        str.append (": ");
        if (data.length <= 10)
        {
            str.append (Bytes.toHexString (data, 0, data.length));
        }
        else
        {
            str.append (Bytes.toHexString (data, 0, 10));
            str.append (" ...");
        }
        tracer.trace (null, prefix, str.toString ());
        traceExplanation (tracer);
    }

    static void traceExplanation (Tracer tracer, String helpId, String msg)
    {
        if (tracer == null || !tracer.isTraceEnabled ())
            return;
        tracer.trace (helpId, "      " + msg);
    }

    static void traceError (Tracer tracer, String helpId, String msg)
    {
        if (tracer == null || !tracer.isTraceEnabled ())
            return;
        tracer.trace (helpId, "<", "     " + msg);
    }

    static void traceDiscard (Tracer tracer, String helpId, String msg,
        byte [] data, int offset, int len)
    {
        if (tracer == null || !tracer.isTraceEnabled ())
            return;
        if (!tracer.isRawTraceEnabled ())
        {
            tracer.trace (null, "<",
                " " + Bytes.toHexString (data, offset, len));
        }
        traceExplanation (tracer, helpId,
            "Discarded " + len + " bytes: " + msg);
    }

    static void traceDiscard (Tracer tracer, String helpId, String msg)
    {
        traceExplanation (tracer, helpId, "Discarded message: " + msg);
    }
}

