package test.sim.scenario.mac;

import org.apache.log4j.Logger;

import test.sim.scenario.mac.MacTest.MacTestMessage;
import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.misc.Mapper;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetIp;
import jist.swans.net.NetMessage;
import jist.swans.net.PacketLoss;
import jist.swans.net.QueuedMessage;


/**
 * @author Oliver
 *
 * Derived NetIp to override sendIp()
 */
public class NetIpMacTest extends NetIp implements NetIpMacTestInterface,
JistAPI.Proxiable
{
  private static final Logger log = Logger.getLogger(NetIp.class.getName());
  
  private long startTime;
  private long endTime;
  private long sendInterval;
  private int msgSize;

  private NetAddress[] recv;

  private NetIpMacTestInterface proxy;

  public NetIpMacTest(NetAddress addr, Mapper protocolMap, PacketLoss in, PacketLoss out)
  {
    super(addr, protocolMap, in, out);
  }

  public NetIpMacTest(NetAddress addr) {
    super(addr, new Mapper(new int[]{Constants.NET_PROTOCOL_DUMB}),
        new PacketLoss.Zero(), new PacketLoss.Zero());
  }
  
  public NetIpMacTest(NetAddress addr, long startTime, long endTime,
                   long sendInterval, int msgSize, NetAddress[] recv)
  {
    super(addr, new Mapper(new int[]{Constants.NET_PROTOCOL_DUMB}),
        new PacketLoss.Zero(), new PacketLoss.Zero());
    this.startTime = startTime;
    this.endTime = endTime;
    this.sendInterval = sendInterval;
    this.msgSize = msgSize;
    this.recv = recv;
    // proxy entity
    this.proxy = (NetIpMacTestInterface)JistAPI.proxy(new NetIpMacTestInterface.Dlg(this),
        NetIpMacTestInterface.class);
    this.self = proxy;
  }

  public NetIpMacTestInterface getMacTestIpProxy()
  {
    return proxy;
  }
  
  public void run() {
    // Sanity checks
    if (recv == null || recv.length < 1 || startTime > endTime) {
      log.warn("Proxy at " + localAddr + " is badly configured!\n\tstart:"
          + startTime + " end:" + endTime + " recv:" + recv);
      return;
    }

    JistAPI.sleepBlock(startTime);

    int i = -1;
    while (JistAPI.getTime() < endTime) {
      // Get next receiver (circle buffer, start at zero)
      i = (i + 1) % recv.length;
      NetAddress dest = recv[i];
      // Create message
      send(new MacTest.MacTestMessage("", msgSize), dest, Constants.NET_PROTOCOL_DUMB,
          Constants.NET_PRIORITY_D_UNDEFINED, (byte)255, null);
      JistAPI.sleepBlock(sendInterval);
    }
  }
  
  // @override
  protected void sendIp(NetMessage.Ip msg, MessageAnno anno)
  {
    if (NetAddress.ANY.equals(msg.getDst()))
    {
      // broadcast
      send(msg, Constants.NET_INTERFACE_DEFAULT, MacAddress.ANY, anno);
    }
    else if(NetAddress.LOCAL.equals(msg.getDst()) || localAddr.equals(msg.getDst()))
    {
      // loopback
      send(msg, Constants.NET_INTERFACE_LOOPBACK, MacAddress.LOOP, anno);
    }
    else
    {
      // pseudo-route (match net addr to mac addr) and send
      send(msg, Constants.NET_INTERFACE_DEFAULT, new MacAddress(msg.getDst().getId()), anno);
    }
  }
  
  // @override
  public void receive(Message msg, MacAddress lastHop, byte macId,
                      boolean promisc, MessageAnno anno)
  {
    if (msg == null) throw new NullPointerException();
    
    if (log.isInfoEnabled()) {
      log.debug("receive t=" + JistAPI.getTime() + " from=" + lastHop + " on="
          + macId + " data=" + msg);
      if (msg instanceof NetMessage.Ip)
        log.debug("net msg is IP");
      else if (msg instanceof MacTestMessage)
        log.debug("net msg is MacTestMessage");
    }
    
    if (recvFromMacEvent.isActive())
      recvFromMacEvent.handle(msg, anno, localAddr, lastHop);
  }

  //////////////////////////////////////////////////
  // send pump
  //

  // @override
  /** {@inheritDoc} */
  public void endSend(Message msg, int interfaceId, MessageAnno anno)
  {
    // log the feedback event
    if (msg != null) {
      if (sendToMacFinishEvent.isActive()) {
        sendToMacFinishEvent.handle(msg, anno, localAddr);
      }
    }

    NicInfo ni = nics[interfaceId];
    if(ni.q.isEmpty())
    {
      ni.busy = false;
    }
    else
    {
      ni.busy = true;
      QueuedMessage qmsg = ni.q.remove();
      NetMessage.Ip ip = (NetMessage.Ip)qmsg.getPayload();
      ip = ip.freeze(); // immutable once packet leaves node
      
      if (dequeueEvent.isActive())
        dequeueEvent.handle(ip, qmsg.getAnno(), localAddr, qmsg.getNextHop(), ni.q);
      if (sendToMacEvent.isActive())
        sendToMacEvent.handle(ip, qmsg.getAnno(), localAddr, qmsg.getNextHop());

      // jittering on network layer
      // after event stuff because otherwise the ordering gets confused
      JistAPI.sleep((long) (Constants.random.nextDouble() * 10 * Constants.EPSILON_DELAY));

      if(log.isInfoEnabled())
        log.info("send t="+JistAPI.getTime()+" to="+qmsg.getNextHop()+" data="+ip);

      ni.mac.send(ip, qmsg.getNextHop(), qmsg.getAnno());
    }
  }

}
