
package uk.co.wingpath.modbus;

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

/**
* Implementation of {@code ModbusTransactionHandler} interface that handles a
* request by forwarding it to other slaves.
* Which slave to forward to is determined by the slave ID in the request.
*/
public class ModbusMultiSlave
    implements ModbusTransactionHandler
{
    private final Map<Integer,ModbusTransactionHandler> slaves;

    /**
    * Constructs a {@code ModbusMultiSlave}.
    * The slave has an empty list of slaves that it can forward to.
    */
    public ModbusMultiSlave ()
    {
        slaves = new TreeMap<Integer,ModbusTransactionHandler> ();
    }

    /**
    * Adds a slave to the list of slaves that messages can be forwarded to.
    * @param slave slave to be added to the list.
    */
    public void add (ModbusSlave slave)
    {
        slaves.put (slave.getSlaveId (), slave);
    }

    /**
    * Adds a slave to the list of slaves that messages can be forwarded to.
    * @param slave slave to be added to the list.
    */
    public void add (int slaveId, ModbusTransactionHandler slave)
    {
        slaves.put (slaveId, slave);
    }

    /**
    * Adds slaves to the list of slaves that messages can be forwarded to.
    * To replace the list of slaves, call the {@link #clear} method first.
    * @param slaves slaves to be added.
    */
    public void add (List<ModbusSlave> slaves)
    {
        for (ModbusSlave slave : slaves)
            add (slave);
    }

    /**
    * Removes a slave from the list of slaves that messages can be
    * forwarded to.
    * @param slave slave to be removed from the list.
    */
    public void remove (ModbusSlave slave)
    {
        slaves.remove (slave.getSlaveId ());
    }

    /**
    * Removes a slave from the list of slaves that messages can be
    * forwarded to.
    * @param slaveId ID of slave to be removed from the list.
    */
    public void remove (int slaveId)
    {
        slaves.remove (slaveId);
    }

    /**
    * Removes all slaves from the list of slaves.
    */
    public void clear ()
    {
        slaves.clear ();
    }

    /**
    * Handles a Modbus transaction.
    * If the request is a broadcast, it is forwarded to all slaves in the
    * list. Otherwise, it is forwarded to the first slave that has a
    * matching slave ID.
    * @param trans the transaction.
    */
    public void handleTransaction (ModbusTransaction trans)
        throws InterruptedException
    {
        int id = trans.getRequest ().getSlaveId ();
        if (id == 0)
        {
            // It's a broadcast - forward a copy of the transaction to each
            // slave. For simplicity, wait for each transaction to finish before
            // moving on to the next.

            trans.setActive ();

            for (ModbusTransactionHandler slave : slaves.values ())
            {
                ModbusTransaction tr = new ModbusTransaction (
                    trans.getRequest (), trans.getReporter ());
                slave.handleTransaction (tr);
                assert tr.isFinished () : tr.getState ();
            }

            trans.setResponse ();
            assert trans.isFinished () : trans.getState ();
        }
        else
        {
            // It's not a broadcast - look for a slave that handles the
            // slave ID.

            ModbusTransactionHandler slave = slaves.get (id);
            if (slave != null)
            {
                slave.handleTransaction (trans);
            }
            else
            {
                trans.setActive ();
                trans.setException (new ModbusException (
                    Modbus.ERROR_PATH_UNAVAILABLE,
                    "S101", "No such slave ID: " + id));
            }
        }

        // If no response handler has been provided, wait for the
        // transaction to finish.
        if (!trans.hasResponseHandler ())
            trans.waitUntilFinished ();
    }
}

