
package uk.co.wingpath.modbus;

import java.io.*;
import uk.co.wingpath.util.*;

/**
* This class implements ModbusTransactionHandler by forwarding requests
* to another ModbusTransactionHandler, and it monitors the data transferred
* in those requests. It does this by also forwarding any write requests
* and replies to read requests (converted into write requests) to a second
* ModbusTransactionHandler.
*/

public class ModbusMonitor
    implements ModbusTransactionHandler
{
    private final ModbusClient client;
    private final ModbusTransactionHandler slave;
    private final Reporter reporter;

    public ModbusMonitor (ModbusClient client, ModbusTransactionHandler slave,
        Reporter reporter)
    {
        if (reporter == null)
            throw new NullPointerException ("reporter must not be null");
        this.client = client;
        this.slave = slave;
        this.reporter = reporter;
    }

    private void copyReplyToSlave (ModbusMessage request,
            ModbusMessage response, int function, Reporter reporter)
        throws InterruptedException
    {
        MessageBuilder body = new MessageBuilder ();
        body.addInt (request.getInt (0));     // address
        body.addInt (request.getInt (2));     // count
        int length = response.getByte (0);
        body.addByte ((byte) length);
        body.addData (response.getData (1, length));
        ModbusMessage req = new ModbusMessage (
            true, request.getSlaveId (), function, 0, body.getData (),
            client.getTracer ());
        ModbusTransaction trans = new ModbusTransaction (req, reporter);
        slave.handleTransaction (trans);
        assert trans.isFinished () : trans.getState ();
    }

    private void copyFileRecordReplyToSlave (ModbusMessage request,
            ModbusMessage response, Reporter reporter)
        throws InterruptedException
    {
        assert reporter.debug ("copyFileRecordReplyToSlave OK");
        MessageBuilder body = new MessageBuilder ();
        body.addByte (0);       // place-holder for data length.
        int responseOffset = 1;

        for (int requestOffset = 1 ; requestOffset < request.size () ; )
        {
            int refType = request.getByte (requestOffset);
            if (refType != 6)
            {
                assert reporter.debug (
                    "copyFileRecordReplyToSlave %d %d: bad request refType %d",
                    requestOffset, responseOffset, refType);
                return;
            }
            int fileNum = request.getInt (requestOffset + 1);
            if (fileNum < 1)
            {
                assert reporter.debug (
                    "copyFileRecordReplyToSlave %d %d: bad filenum %d",
                    requestOffset, responseOffset, fileNum);
                return;
            }
            int address = request.getInt (requestOffset + 3);
            int count = request.getInt (requestOffset + 5);

            if (responseOffset + 2 >= response.size ())
            {
                assert reporter.debug (
                    "copyFileRecordReplyToSlave %d %d: bad response size %d",
                    requestOffset, responseOffset, response.size ());
                return;
            }
            int len = response.getByte (responseOffset);
            if (responseOffset + len > response.size ())
            {
                assert reporter.debug (
                    "copyFileRecordReplyToSlave %d %d: bad response len %d %d",
                    requestOffset, responseOffset, len, response.size ());
                return;
            }
            refType = response.getByte (responseOffset + 1);
            if (refType != 6)
            {
                assert reporter.debug (
                    "copyFileRecordReplyToSlave %d %d: bad response refType %d",
                    requestOffset, responseOffset, refType);
                return;
            }
            byte [] data = response.getData (responseOffset + 2, len - 1);

            body.addByte (6);
            body.addInt (fileNum);
            body.addInt (address);
            body.addInt (count);
            body.addData (data);

            requestOffset += 7;
            responseOffset += len + 1;
        }

        byte [] data = body.getData ();
        data [0] = (byte) (data.length > 256 ? 0 : data.length - 1);

        ModbusMessage req = new ModbusMessage (
            true, request.getSlaveId (), Modbus.FUNC_MONITOR_FILE_RECORDS,
            0, data, client.getTracer ());
        ModbusTransaction trans = new ModbusTransaction (req, reporter);
        slave.handleTransaction (trans);
        assert trans.isFinished () : trans.getState ();
        ModbusException exception = trans.getException ();
        if (exception != null)
        {
            assert reporter.debug ("copyFileRecordReplyToSlave exception: %s",
                exception.getMessage ());
        }
        else
        {
            assert reporter.debug ("copyFileRecordReplyToSlave OK");
        }
    }

    public void handleTransaction (ModbusTransaction trans)
        throws InterruptedException
    {
        trans.setActive ();

        // Send the request to the client and get its reply

        ModbusTransaction tr = new ModbusTransaction (
            trans.getRequest (), trans.getReporter ());
        client.handleTransaction (tr);
        assert tr.isFinished () : tr.getState ();

        ModbusException exception = tr.getException ();
        if (exception != null)
        {
            trans.setException (exception);
            Tracer tracer = client.getTracer ();
            if (tracer != null)
                tracer.endTransaction ();
            return;
        }
        ModbusMessage request = trans.getRequest ();
        ModbusMessage response = tr.getResponse ();

        // Try to copy any data to the slave

        switch (request.getFunctionCode ())
        {
        case Modbus.FUNC_WRITE_MULTIPLE_REGISTERS:
        case Modbus.FUNC_WRITE_SINGLE_REGISTER:
        case Modbus.FUNC_WRITE_MULTIPLE_COILS:
        case Modbus.FUNC_WRITE_SINGLE_COIL:
        case Modbus.FUNC_MASK_WRITE_REGISTER:
        case Modbus.FUNC_WRITE_FILE_RECORD:
            {
                ModbusTransaction slaveTrans =
                    new ModbusTransaction (request, reporter);
                slave.handleTransaction (slaveTrans);
                assert slaveTrans.isFinished () : slaveTrans.getState ();
            }
            break;

        case Modbus.FUNC_READ_HOLDING_REGISTERS:
            copyReplyToSlave (request, response,
                Modbus.FUNC_MONITOR_HOLDING_REGISTERS, reporter);
            break;

        case Modbus.FUNC_READ_COILS:
            copyReplyToSlave (request, response,
                Modbus.FUNC_MONITOR_COILS, reporter);
            break;

        case Modbus.FUNC_READ_DISCRETE_INPUTS:
            copyReplyToSlave (request, response,
                Modbus.FUNC_MONITOR_DISCRETE_INPUTS, reporter);
            break;

        case Modbus.FUNC_READ_INPUT_REGISTERS:
            copyReplyToSlave (request, response,
                Modbus.FUNC_MONITOR_INPUT_REGISTERS, reporter);
            break;

        case Modbus.FUNC_READ_FILE_RECORD:
            copyFileRecordReplyToSlave (request, response, reporter);
            break;

        case Modbus.FUNC_READ_WRITE_MULTIPLE_REGISTERS:
            {
                ModbusTransaction slaveTrans =
                    new ModbusTransaction (request, reporter);
                slave.handleTransaction (slaveTrans);
                assert slaveTrans.isFinished () : slaveTrans.getState ();
                copyReplyToSlave (request, response,
                    Modbus.FUNC_MONITOR_HOLDING_REGISTERS, reporter);
            }
            break;
        default:
            Tracer tracer = client.getTracer ();
            if (tracer != null)
                tracer.endTransaction ();
            break;
        }

        trans.setResponse (tr.getResponse ());
        if (!trans.hasResponseHandler ())
            trans.waitUntilFinished ();
    }
}

