package click.swans.net;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDumbMessageFactory;
import jist.swans.mac.MacInterface;
import jist.swans.misc.Mapper;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.MessageQueue;
import jist.swans.net.NetAddress;
import jist.swans.net.NetMessage;
import jist.swans.net.PacketLoss;
import jist.swans.net.QueuedMessage;
import jist.swans.route.RouteInterface;

import org.apache.log4j.Logger;

import click.runtime.ClickAdapter;
import click.runtime.ClickException;
import click.runtime.ClickInterface;

/**
 * Click modular router with optional host interfaces (and simulator defined routing)
 * 
 * @author kurth
 */
public class ClickHostRouter extends ClickRouter {

  /**
   * IP logger.
   */
  public static final Logger log = Logger.getLogger(ClickHostRouter.class.getName());

  
  public static class HostNicInfo extends NicInfo {
//    /** outgoing packet queue. */
//    public MessageQueue q;
    public String name;
    public MacAddress addr;
    public int encap;
    public Message packet;
  }

  //////////////////////////////////////////////////
  // constants
  //

  //////////////////////////////////////////////////
  // locals
  //

  /** host (i.e. linux) routing */
  private RouteInterface routing;

  /** our click message factroy */
  private MacDumbMessageFactory msgFactory;

  //////////////////////////////////////////////////
  // initialization
  //

  /**
   * Initialize IP implementation with given address and protocol mapping.
   *
   * @param addr local network address
   * @param protocolMap protocol number mapping
   * @param in incoming packet loss model
   * @param out outgoing packet loss model
   */
  public ClickHostRouter(NetAddress addr, Mapper protocolMap, PacketLoss in,
      PacketLoss out) throws ClickException {
    super(addr, protocolMap, in, out);
  }

  public MacDumbMessageFactory getMsgFactory() {
    return msgFactory;
  }

  public void setMsgFactory(MacDumbMessageFactory msgFactory) {
    this.msgFactory = msgFactory;
  }
  
  //////////////////////////////////////////////////
  // entity hookup
  //

  //////////////////////////////////////////////////
  // routing, protocols, interfaces
  //
  
  /**
   * Add network interface.
   *
   * @param macEntity link layer entity
   * @return network interface identifier
   */
  public byte addHostInterface(MacInterface macEntity, MacAddress addr, String name) {
    return addHostInterface(macEntity, new MessageQueue.DropTailMessageQueue(
        Constants.NET_PRIORITY_NUM, MAX_QUEUE_LENGTH), addr, name);
  }

  /**
   * Add network interface.
   *
   * @param macEntity link layer entity
   * @return network interface identifier
   */
  public byte addHostInterface(MacInterface macEntity, MessageQueue q, 
      MacAddress addr, String name) {
    // create new nicinfo
    HostNicInfo ni = new HostNicInfo();
    ni.mac = macEntity;
    ni.name = name;
    ni.addr = addr;
    ni.encap = ClickInterface.SIMCLICK_PTYPE_ETHER;
    ni.q = new MessageQueue.DropTailMessageQueue(Constants.NET_PRIORITY_NUM, MAX_QUEUE_LENGTH);
    return addInterface(ni);
  }

  /**
   * Set routing implementation.
   *
   * @param routingEntity routing entity
   */
  @Override
  public void setRouting(RouteInterface routingEntity)
  {
    if(!JistAPI.isEntity(routingEntity)) throw new IllegalArgumentException("expected entity");
    this.routing = routingEntity;
  }

  //////////////////////////////////////////////////
  // NetInterface implementation
  //

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetInterface#send(jist.swans.net.NetMessage.Ip, int, jist.swans.mac.MacAddress, jist.swans.misc.MessageAnno)
   */
  @Override
  public void send(NetMessage.Ip msg, int interfaceId, MacAddress nextHop, MessageAnno anno)
  {
    // TODO interfaceId
     interfaceId = 1;
    
    if(msg==null) throw new NullPointerException();
    if(outgoingLoss.shouldDrop(msg)) {
      if (linkDropEvent.isActive())
        linkDropEvent.handle(msg, anno, localAddr, nextHop, false);

      return;
    }

    /*
    if(msg.getSize()>THRESHOLD_FRAGMENT)
    {
      throw new RuntimeException("ip fragmentation not implemented");
    }
    */
    if(log.isDebugEnabled())
      log.debug("queue t="+JistAPI.getTime()+" to="+nextHop+" on="+interfaceId+" data="+msg);

    HostNicInfo ni = (HostNicInfo) nics[interfaceId];
    int size = ni.q.size();
    ni.q.insert(new QueuedMessage(msg, nextHop, anno), msg.getPriority());

    if (size != ni.q.size() && enqueueEvent.isActive())
      enqueueEvent.handle(msg, anno, localAddr, nextHop, ni.q);
    else if (linkDropEvent.isActive())
      linkDropEvent.handle(msg, anno, localAddr, nextHop, false);

    if(nics_ready[interfaceId])
      endSend(null, interfaceId, null);
  }

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

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetInterface#endSend(jist.swans.misc.Message, int, jist.swans.misc.MessageAnno)
   */
  @Override
  public void endSend(Message packet, int interfaceId, MessageAnno anno)
  {
    NicInfo nic = nics[interfaceId];
    
    if (nic instanceof ClickNicInfo) { 
      super.endSend(packet, interfaceId, anno);
      return;
    }
    
    HostNicInfo ni = (HostNicInfo) nics[interfaceId];
    // log the feedback event
    if (packet != null) {
      if (sendToMacFinishEvent.isActive()) {
        sendToMacFinishEvent.handle(packet, anno, localAddr);
      }
    }

    if(ni.q.isEmpty())
    {
      nics_ready[interfaceId] = true;
      ni.packet = null;
    }
    else
    {
      nics_ready[interfaceId] = false;
      QueuedMessage qmsg = ni.q.remove();
      NetMessage.Ip ip = (NetMessage.Ip)qmsg.getPayload();
      ip = ip.freeze(); // immutable once packet leaves node
      ni.packet = ip;
      
      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
      // TODO
//        JistAPI.sleep(netMinDelay +
//            (long) (Constants.random.nextDouble() * (netMaxDelay - netMinDelay)));

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

      Message msg = msgFactory.createData(ni.addr, qmsg.getNextHop(), ip);
      ni.mac.send(msg, qmsg.getNextHop(), qmsg.getAnno());
    }
  }

  //////////////////////////////////////////////////
  // send/receive
  //

  /**
   * Send an IP packet. Knows how to broadcast, to deal
   * with loopback. Will call routing for all other destinations.
   *
   * @param msg ip packet
   * @param anno
   */
  @Override
  protected void sendIp(NetMessage.Ip msg, MessageAnno anno)
  {
    // TODO
//    if(routing!=null) routing.peek(ipmsg, lastHop, 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
    {
      // route and send
      routing.send(msg, anno);
    }
  }

//  @Override
//  protected void recvFromClickSendDown(Message msg, MessageAnno anno, 
//      int ifid) throws ClickException {
//    if (ifid < 0 || ifid >= nics.length){
//      throw new ClickException("Packet sent to unknown interface");
//    }
//
//    NicInfo ni = nics[ifid];
//    if (ni.encap == ClickAdapter.SIMCLICK_PTYPE_ETHER) {
//      // only send ip messages, not brn and similar stuff
////      if (MacClickMessage.ETHER_TYPE_IP != etherType)
////        return;
//
//      recvFromClickSendDown(msg, new MessageAnno(), ifid, ni);
//    } else {
//      super.recvFromClickSendDown(ifid, msg);
//    }
//  }

}
