
package uk.co.wingpath.modbus;


/**
* This class is used to represent Modbus request messages.
*/
public class ModbusRequest
    extends ModbusMessage
{
    private boolean replyExpected;

    /**
    * Constructs a ModbusRequest 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.
    */
    public ModbusRequest (int slaveId, int function, int transId, byte [] data)
    {
        super (slaveId, function, transId, data);
        replyExpected = slaveId != 0;
    }

    /**
    * Constructs a ModbusResponse with the specified data, and the same
    * slave id, function code and transaction id as this request.
    * @param data body of message.
    * @return the response.
    */
    public ModbusResponse response (byte [] data)
    {
        return new ModbusResponse (slaveId, function, transId, data);
    }

    /**
    * Constructs an error response using the error code from the supplied
    * Modbus exception and the slave id, function code and transaction id from
    * this request.
    * @param e the Modbus exception.
    * @return the error response.
    * @see ModbusErrorResponse
    */
    public ModbusResponse errorResponse (ModbusException e)
    {
        return new ModbusErrorResponse (slaveId, function, transId, e);
    }

    /**
    * Constructs a response to this broadcast request.
    * <p>This is used where a master requires a response for its own purposes,
    * but no response is available from a slave (since the request is a
    * broadcast).
    * @return the response.
    */
    public ModbusResponse broadcastResponse ()
    {
        MessageBuilder body = new MessageBuilder ();
        switch (function)
        {
        case Modbus.FUNC_WRITE_MULTIPLE_REGISTERS:
        case Modbus.FUNC_WRITE_MULTIPLE_COILS:
            body.addInt (getInt (0));
            body.addInt (getInt (2));
            break;

        case Modbus.FUNC_WRITE_SINGLE_REGISTER:
        case Modbus.FUNC_MASK_WRITE_REGISTER:
        case Modbus.FUNC_WRITE_SINGLE_COIL:
        case Modbus.FUNC_DIAGNOSTICS:
            body.addData (getData ());
            break;
        }

        ModbusResponse response = response (body.getData ());
        return response;
    }

    /**
    * Gets the flag that indicates whether a reply is expected.
    * @return {@code true} if a reply is expected, {@code false} otherwise.
    */
    public boolean getReplyExpected ()
    {
        return replyExpected;
    }

    /**
    * Checks that the request size is what is expected, and throws a
    * ModbusException if isn't.
    * @param size expected request size.
    * @throws ModbusException if the request size is not correct.
    */
    public void checkSize (int size)
        throws ModbusException
    {
        if (size () != size)
        {
            Modbus.dataError ("R001",
                "Request PDU size incorrect: " +
                (size () + 1) + " instead of " + (size + 1));
        }
    }

    /**
    * Checks that the request size is not less than the specified minimum,
    * and throws a ModbusException if is.
    * @param minSize expected minimum request size.
    * @throws ModbusException if the request size is too small.
    */
    public void checkMinSize (int minSize)
        throws ModbusException
    {
        if (size () < minSize)
        {
            Modbus.dataError ("R002",
                "Request PDU too short: " +
                (size () + 1) + " when it should be at least " + (minSize + 1));
        }
    }

    @Override
    public void checkSize (boolean allowLongMessages)
        throws ModbusException
    {
        switch (function)
        {
        case Modbus.FUNC_READ_HOLDING_REGISTERS:
            checkSize (4);
            break;

        case Modbus.FUNC_READ_INPUT_REGISTERS:
            checkSize (4);
            break;

        case Modbus.FUNC_WRITE_MULTIPLE_REGISTERS:
            checkMinSize (5);
            if (!allowLongMessages)
                checkSize (5 + getByte (4));
            break;

        case Modbus.FUNC_WRITE_SINGLE_REGISTER:
            checkMinSize (3);
            break;

        case Modbus.FUNC_MASK_WRITE_REGISTER:
            checkMinSize (4);
            break;

        case Modbus.FUNC_READ_COILS:
            checkSize (4);
            break;

        case Modbus.FUNC_READ_DISCRETE_INPUTS:
            checkSize (4);
            break;

        case Modbus.FUNC_WRITE_MULTIPLE_COILS:
            checkMinSize (5);
            if (!allowLongMessages)
                checkSize (5 + getByte (4));
            break;

        case Modbus.FUNC_WRITE_SINGLE_COIL:
            checkSize (4);
            break;

        case Modbus.FUNC_READ_WRITE_MULTIPLE_REGISTERS:
            checkMinSize (9);
            if (!allowLongMessages)
                checkSize (9 + getByte (8));
            break;

        case Modbus.FUNC_READ_EXCEPTION_STATUS:
            checkSize (0);
            break;

        case Modbus.FUNC_DIAGNOSTICS:
            checkMinSize (2);
            break;

        case Modbus.FUNC_GET_COMM_EVENT_COUNTER:
            checkSize (0);
            break;

        case Modbus.FUNC_REPORT_SLAVE_ID:
            checkSize (0);
            break;

        case Modbus.FUNC_ENCAPSULATED_INTERFACE_TRANSPORT:
            checkMinSize (1);
            if (getByte (0) == Modbus.MEI_READ_DEVICE_IDENTIFICATION)
                checkSize (3);
            break;
        }
    }

    @Override
    void traceExplanation (Tracer tracer)
    {
        if (tracer == null || !tracer.isTraceEnabled ())
            return;
        StringBuilder msg = new StringBuilder ();
        msg.append ("Request: ");
        msg.append (getFunctionName ());

        try
        {
            switch (function)
            {
            case Modbus.FUNC_READ_HOLDING_REGISTERS:
            case Modbus.FUNC_READ_INPUT_REGISTERS:
            case Modbus.FUNC_READ_COILS:
            case Modbus.FUNC_READ_DISCRETE_INPUTS:
                msg.append (": address ");
                msg.append (getInt (0));
                msg.append (", count ");
                msg.append (getInt (2));
                break;

            case Modbus.FUNC_WRITE_MULTIPLE_REGISTERS:
            case Modbus.FUNC_WRITE_MULTIPLE_COILS:
                msg.append (": address ");
                msg.append (getInt (0));
                msg.append (", count ");
                msg.append (getInt (2));
                msg.append (", byte count ");
                msg.append (getByte (4));
                msg.append (", data bytes ");
                msg.append (data.length - 5);
                break;

            case Modbus.FUNC_WRITE_SINGLE_REGISTER:
                msg.append (": address ");
                msg.append (getInt (0));
                msg.append (", data bytes ");
                msg.append (data.length - 2);
                break;

            case Modbus.FUNC_MASK_WRITE_REGISTER:
                msg.append (": address ");
                msg.append (getInt (0));
                msg.append (", data bytes ");
                msg.append (data.length - 2);
                break;

            case Modbus.FUNC_WRITE_SINGLE_COIL:
                msg.append (": address ");
                msg.append (getInt (0));
                msg.append (", value ");
                msg.append (Integer.toHexString (getInt (2)));
                break;

            case Modbus.FUNC_READ_WRITE_MULTIPLE_REGISTERS:
                msg.append (": read address ");
                msg.append (getInt (0));
                msg.append (", read count ");
                msg.append (getInt (2));
                msg.append (": write address ");
                msg.append (getInt (4));
                msg.append (", write count ");
                msg.append (getInt (6));
                msg.append (", byte count ");
                msg.append (getByte (8));
                msg.append (", data bytes ");
                msg.append (data.length - 9);
                break;

            case Modbus.FUNC_READ_EXCEPTION_STATUS:
                break;

            case Modbus.FUNC_DIAGNOSTICS:
                msg.append (": data ");
                msg.append (getInt (2));
                break;

            case Modbus.FUNC_GET_COMM_EVENT_COUNTER:
                break;

            case Modbus.FUNC_REPORT_SLAVE_ID:
                break;

            case Modbus.FUNC_ENCAPSULATED_INTERFACE_TRANSPORT:
                if (getByte (0) == Modbus.MEI_READ_DEVICE_IDENTIFICATION)
                {
                    msg.append (": code ");
                    msg.append (getByte (1));
                    msg.append (" objectid ");
                    msg.append (String.format ("%02x", getByte (2)));
                }
                break;

            default:
                break;
            }
        }
        catch (Exception e)
        {
        }

        traceExplanation (tracer, null, msg.toString ());
    }
}


