
package uk.co.wingpath.modbus;

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

/**
* This class provides communication channels over which Modbus messages
* may be sent and received.
* <p>Legacy class - now only supports slave end.
* <p>The {@link ModbusService} class is recommended for handling the slave
* end of a connection. The {@link ModbusService#serve serve} method in that
* class contains the server loop, and provides proper coordination between
* the request and the response.
* <p>Any code that still uses this class should be checked against the code
* in {@link ModbusService} to ensure that it does all the correct checks and
* tracing.
*/
@Deprecated
public class ModbusChannel
{
    private final Connection connection;
    private final PacketType packetType;
    private Tracer tracer;
    private boolean alwaysRespond;
    private final Reporter reporter;

    /**
    * Constructs a {@code ModbusChannel} using the supplied
    * {@code Connection} and {@code PacketType}.
    * @param connection the connection.
    * @param packetType the packet type.
    * @param alwaysRespond whether response should be sent to broadcast request,
    * and exception responses 10/11 should be used. Normally {@code true} for
    * socket connection, and {@code false} for serial connection.
    * @param reporter handler for reporting recoverable errors.
    * May be {@code null} if no reporting is required.
    */
    public ModbusChannel (Connection connection, PacketType packetType,
        boolean alwaysRespond, Reporter reporter)
    {
        this.connection = connection;
        this.packetType = packetType;
        this.alwaysRespond = alwaysRespond;
        this.reporter = reporter;
    }

    /**
    * Sets the tracer to be used for tracing messages.
    * @param tracer the tracer.
    */
    public void setTracer (Tracer tracer)
    {
        this.tracer = tracer;
    }

    /**
    * Receives a Modbus request message.
    * @return The received message.
    * @throws InterruptedException if the thread is interrupted.
    * @throws IOException if an I/O error occurs.
    * @throws InterruptedIOException if a timeout occurs.
    */
    public ModbusRequest receiveRequest ()
        throws InterruptedException, IOException, InterruptedIOException
    {
        synchronized (connection)
        {
            ModbusMessage message =
                packetType.receive (connection, tracer, true, reporter);
            message.traceReceive (tracer);
            return (ModbusRequest) message;
        }
    }

    /**
    * Sends a Modbus message.
    * <p>The packet type is called to format the message and write it over
    * the connection.
    * <p>The message is not sent if the packet type is not TCP and the message
    * is a response to a broadcast or a TCP-specific error response.
    * @param message the message to be sent.
    * @throws InterruptedException if the thread is interrupted.
    * @throws IOException if an I/O error occurs.
    */
    public void send (ModbusResponse message)
        throws InterruptedException, IOException
    {
        synchronized (connection)
        {
            if (!packetType.hasTransactionIds ())
            {
                // Discard any unexpected input

                byte [] data = connection.discardInput ();
                if (data != null)
                {
                    String msg = "Unexpected";
                    ModbusMessage.traceDiscard (tracer, "S401", msg,
                        data, 0, data.length);
                    if (reporter != null)
                    {
                        reporter.warning ("S401",
                            "Invalid data received: " + msg);
                    }
                }
            }

            // If not using TCP, don't send reply to broadcast and don't
            // send 0x0A or 0x0B error responses.

            if (!alwaysRespond)
            {
                if (message.getSlaveId () == 0)
                {
                    if (message instanceof ModbusErrorResponse)
                    {
                        ModbusErrorResponse reply = (ModbusErrorResponse) message;
                        if (reply.getErrorCode () !=
                            Modbus.ERROR_TARGET_FAILED_TO_RESPOND)
                        {
                            reply.traceExplanation (tracer);
                        }
                    }
                    ModbusMessage.traceExplanation (tracer, "S402",
                        "Response not sent: Request was broadcast");
                    if (tracer != null)
                        tracer.endTransaction ();
                    return;
                }
                if (message instanceof ModbusErrorResponse)
                {
                    ModbusErrorResponse reply = (ModbusErrorResponse) message;
                    int error = reply.getErrorCode ();
                    if (error == Modbus.ERROR_PATH_UNAVAILABLE ||
                        error == Modbus.ERROR_TARGET_FAILED_TO_RESPOND)
                    {
                        reply.traceExplanation (tracer);
                        ModbusMessage.traceExplanation (tracer, "S403",
                            "Response not sent: Exception 10/11");
                        if (tracer != null)
                            tracer.endTransaction ();
                        return;
                    }
                }
            }

            packetType.send (connection, tracer, message);
            if (tracer != null)
                tracer.endTransaction ();
        }
    }
}



