//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <NetIp.java Tue 2004/04/20 10:12:52 barr pompom.cs.cornell.edu>
//

// Copyright (C) 2004 by Cornell University
// All rights reserved.
// Refer to LICENSE for terms and conditions of use.

package jist.swans.net;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacInterface;
import jist.swans.mac.MacLoop;
import jist.swans.route.RouteInterface;
import jist.swans.trans.TransInterface.TransMessage;
import jist.swans.misc.Message;
import jist.swans.misc.Mapper;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;
import jist.swans.route.RouteInterface;

import org.apache.log4j.Logger;

/**
 * IPv4 implementation based on RFC 791. Performs protocol
 * multiplexing, and prioritized packet queuing, but no
 * RED or packet fragmentation.
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: NetIp.java,v 1.45 2004/11/03 23:53:22 barr Exp $
 * @since SWANS1.0
 */

public class NetIp extends AbstractNet implements NetInterface
{
  /**
   * IP logger.
   */
  public static final Logger log = Logger.getLogger(NetIp.class.getName());

  /**
   * Information about each network interface.
   */
  public static class NicInfo
  {
    /** mac entity. */
    public MacInterface mac;
    /** outgoing packet queue. */
    public MessageQueue q;
    /** whether link layer is busy. */
    public boolean busy;
  }

  /**
   * Bridges the {@link RawNetHandler} interface to {@link NetHandler}
   * @author kurth
   *
   */
  public static class NetHandlerAdapter implements RawNetHandler {
    private NetHandler handler;

    public NetHandlerAdapter(NetHandler handler) {
      this.handler = handler;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.NetInterface.RawNetHandler#receive(jist.swans.net.NetMessage, jist.swans.misc.MessageAnno, jist.swans.mac.MacAddress, byte)
     */
    public void receive(NetMessage msg, MessageAnno anno, MacAddress lastHop,
        byte macId) {
      NetMessage.Ip ipmsg = (NetMessage.Ip) msg;
      handler.receive(ipmsg.getPayload(), ipmsg.getSrc(), lastHop, macId,
          ipmsg.getDst(), ipmsg.getPriority(), ipmsg.getTTL(), anno);
    }
  }

  public class DefaultNetHandler implements RawNetHandler {

    /*
     * (non-Javadoc)
     * @see jist.swans.net.NetInterface.RawNetHandler#receive(jist.swans.net.NetMessage, jist.swans.misc.MessageAnno, jist.swans.mac.MacAddress, byte)
     */
    public void receive(NetMessage msg, MessageAnno anno, MacAddress lastHop,
        byte macId) {
      NetMessage.Ip ipmsg = (NetMessage.Ip) msg;
      log.error(NetIp.this + "(" + JistAPI.getTime() + "): protocol " +
          ipmsg.getProtocol() + " not known (" + ipmsg.toString() + ")");
    }
  }

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

  /**
   * Packet fragmentation threshold.
   */
  public static final int THRESHOLD_FRAGMENT = 2048;

  /**
   * Maximum packet queue length.
   */
  public static final byte MAX_QUEUE_LENGTH = 50;

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

  // entity hookup
  /** self-referencing proxy entity. */
  protected NetInterface self;

  /** local network address. */
  protected NetAddress localAddr;

  /** routing protocol. */
  protected RouteInterface routing;

  /** protocol number mapping. */
  protected Mapper protocolMap;

  /** protocol handlers. */
  protected RawNetHandler[] protocolHandlers;

  /** network interfaces. */
  protected NicInfo[] nics;

  /** packet loss models. */
  protected PacketLoss incomingLoss, outgoingLoss;

  /** min. delay of the net layer (uniform distribution) */
  protected long netMinDelay = Constants.NET_DELAY;
  /** max. delay of the net layer (uniform distribution) */
  protected long netMaxDelay = Constants.NET_DELAY + Constants.NET_JITTER;

  private DefaultNetHandler defaultNetHanlder;

  //////////////////////////////////////////////////
  // 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 NetIp(NetAddress addr, Mapper protocolMap, PacketLoss in, PacketLoss out)
  {
    // proxy entity
    this.self = (NetInterface)JistAPI.proxy(new NetInterface.Dlg(this),
        NetInterface.class);
    // local address
    setAddress(addr);
    // protocol number mapping
    this.protocolMap = protocolMap;
    // protocol handlers
    // @author Elmar Schoch >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    // Remark: since protocolMap.getLimit() always returns the CURRENT
    //         maximum index in the map, later assignment of net handlers
    //         than the NetIP constructor is not possible. This side effect
    //         makes development rather tricky and thus should be avoided
    //         I left this in here and decided to implement a sustainable
    //         solution - now, the array grows dynamically when new handlers
    //         are set (see setNetHandler method)
    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    this.protocolHandlers = new RawNetHandler[protocolMap.getLimit()];
    this.defaultNetHanlder = new DefaultNetHandler();
    // network interfaces
    this.nics = new NicInfo[0];
    // packet loss
    this.incomingLoss = in;
    this.outgoingLoss = out;
    // add loopback mac:
    //   therefore, loopback = 0, Constants.NET_INTERFACE_LOOPBACK
    //              next     = 1, Constants.NET_INTERFACE_DEFAULT
    MacLoop loopback = new MacLoop();
    byte netid = addInterface(loopback.getProxy());
    if(Main.ASSERT) Util.assertion(netid==Constants.NET_INTERFACE_LOOPBACK);
    loopback.setNetEntity(getProxy(), netid);
  }

  protected NicInfo createNicInfo() {
    return new NicInfo();
  }

  //////////////////////////////////////////////////
  // entity hookup
  //

  /**
   * Return self-referencing proxy entity.
   *
   * @return self-referencing proxy entity
   */
  public NetInterface getProxy()
  {
    return this.self;
  }

  //////////////////////////////////////////////////
  // address
  //

  /**
   * Set local network address.
   *
   * @param addr local network address
   */
  public void setAddress(NetAddress addr)
  {
    if(Main.ASSERT) Util.assertion(addr!=null);
    this.localAddr = addr;
  }

  /**
   * Whether packet is for local consumption.
   *
   * @param msg packet to inspect
   * @return whether packet is for local consumption
   */
  public boolean isForMe(NetMessage.Ip msg)
  {
    NetAddress addr = msg.getDst();
    return NetAddress.ANY.equals(addr)
      || NetAddress.LOCAL.equals(addr)
      || localAddr.equals(addr);
  }

  //////////////////////////////////////////////////
  // routing, protocols, interfaces
  //

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

  /**
   * Add network interface with default queue.
   *
   * @param macEntity link layer entity
   * @return network interface identifier
   */
  public byte addInterface(MacInterface macEntity)
  {
    return addInterface(macEntity,
        new MessageQueue.NoDropMessageQueue(
          Constants.NET_PRIORITY_NUM, MAX_QUEUE_LENGTH));
  }

  /**
   * Add network interface.
   *
   * @param macEntity link layer entity
   * @return network interface identifier
   */
  public byte addInterface(MacInterface macEntity, MessageQueue q)
  {
    if(!JistAPI.isEntity(macEntity)) throw new IllegalArgumentException("expected entity");
    // create new nicinfo
    NicInfo ni = createNicInfo();
    ni.mac = macEntity;
    ni.q = q;
    ni.busy = false;
    // store
    NicInfo[] nics2 = new NicInfo[nics.length+1];
    System.arraycopy(nics, 0, nics2, 0, nics.length);
    nics2[nics.length] = ni;
    nics = nics2;
    // return interface id
    return (byte)(nics.length-1);
  }

  /**
   * Set network protocol handler.
   *
   * @param protocolId protocol identifier
   * @param handler protocol handler
   */
  public void setProtocolHandler(int protocolId, NetHandler handler) {
    this.setProtocolHandler(protocolId, new NetHandlerAdapter(handler));
  }

  /**
   * Set network protocol handler for raw reception.
   *
   * @param protocolId protocol identifier
   * @param handler raw protocol handler
   */
  public void setProtocolHandler(int protocolId, RawNetHandler handler)
  {
    // @author Elmar Schoch >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    // grow protocol handler array, if necessary
    if (protocolMap.getLimit() > protocolHandlers.length) {
      if(log.isInfoEnabled())
        log.info("Growing protocol handler array at "+this.localAddr+" from "+protocolHandlers.length+" to "+protocolMap.getLimit());
      RawNetHandler[] protocolHandlersNew = new RawNetHandler[protocolMap.getLimit()];
      for (int i=0; i < protocolHandlers.length; i++) {
        protocolHandlersNew[i] = protocolHandlers[i];
      }
      protocolHandlers = protocolHandlersNew;
    }
    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    protocolHandlers[protocolMap.getMap(protocolId)] = handler;
  }

  /**
   * Return network protocol handler.
   *
   * @param protocolId protocol identifier
   * @return procotol handler
   */
  protected RawNetHandler getProtocolHandler(int protocolId)
  {
    try {
      RawNetHandler ret = protocolHandlers[protocolMap.getMap(protocolId)];
      if (null == ret)
        return defaultNetHanlder;
      return ret;
    } catch (ArrayIndexOutOfBoundsException e) {
      return defaultNetHanlder;
    }
  }


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

  /** {@inheritDoc} */
  public NetAddress getAddress() throws JistAPI.Continuation
  {
    return localAddr;
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetInterface#receive(jist.swans.misc.Message, jist.swans.mac.MacAddress, byte, boolean, jist.swans.misc.MessageAnno)
   */
  public void receive(Message msg, MacAddress lastHop, byte macId,
      boolean promisc, MessageAnno anno)
  {
    if(msg==null) throw new NullPointerException();
    NetMessage.Ip ipmsg = (NetMessage.Ip)msg;
    if(incomingLoss.shouldDrop(ipmsg)) {
      if (linkDropEvent.isActive())
        linkDropEvent.handle(msg, anno, localAddr, lastHop, true);
      return;
    }

    // remember arrival
    if (null != anno) anno.put(MessageAnno.ANNO_NET_ARRIVAL, JistAPI.getTime());

    if(log.isInfoEnabled())
      log.info("receive t="+JistAPI.getTime()+" from="+lastHop+" on="+macId+" data="+msg);
    if (recvFromMacEvent.isActive())
      recvFromMacEvent.handle(ipmsg, anno, localAddr, lastHop);

    if(routing!=null) routing.peek(ipmsg, lastHop, anno);
    if(isForMe(ipmsg))
    {
      NetAddress addr = ipmsg.getDst();
      if (sendToTransEvent.isActive())
        sendToTransEvent.handle(ipmsg, anno, lastHop, macId);

      JistAPI.sleep(Constants.NET_DELAY);
      getProtocolHandler(ipmsg.getProtocol()).receive(ipmsg, anno, lastHop, macId);
    }
    else
    {
      if(ipmsg.getTTL()>0)
      {
        if(ipmsg.isFrozen()) ipmsg = ipmsg.copy();
        ipmsg.decTTL();
        sendIp(ipmsg, anno);
      }
      else if (linkDropEvent.isActive())
        linkDropEvent.handle(ipmsg, anno, localAddr, lastHop, true);
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetInterface#send(jist.swans.misc.Message, jist.swans.net.NetAddress, short, byte, byte, jist.swans.misc.MessageAnno)
   */
  public void send(Message msg, NetAddress dst,
      short protocol, byte priority, byte ttl, MessageAnno anno)
  {
    if(msg==null) throw new NullPointerException();

    // remember arrival
    if (null != anno) anno.put(MessageAnno.ANNO_NET_ARRIVAL, JistAPI.getTime());

    NetMessage.Ip ipmsg = new NetMessage.Ip(msg, localAddr, dst,
        protocol, priority, ttl);

    // This is a different packet id than ANNO_RTG_PACKETID
    // there is no use in pretending 32-Bit ids when we can only send 16 Bits
    // TODO implement for all packets, completely in net w/o any trans
//    TransMessage.registerPacket(
//        TransMessage.determineFlowId(localAddr, dst), ipmsg.getId());

    if (recvFromTransEvent.isActive())
      recvFromTransEvent.handle(ipmsg, anno);

    sendIp(ipmsg, anno);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetInterface#send(jist.swans.net.NetMessage.Ip, int, jist.swans.mac.MacAddress, jist.swans.misc.MessageAnno)
   */
  public void send(NetMessage.Ip msg, int interfaceId, MacAddress nextHop, MessageAnno anno)
  {
    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);

    NicInfo ni = 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(!ni.busy)
      endSend(null, interfaceId, null); // pump message
  }

  //////////////////////////////////////////////////
  // 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
   */
  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
    {
      // route and send
      if (routing != null)
        routing.send(msg, anno);
      else
        // use RouteTable if you need a simple routing layer!
        log.warn(this + "(" + JistAPI.getTime() + "): missing routing, dropping"
            + " packet for destination " + msg.getDst());
    }
  }

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

  /** {@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());

      // remember service start
      if (null != qmsg.getAnno())
        qmsg.getAnno().put(MessageAnno.ANNO_NET_SERVICE, JistAPI.getTime());

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

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

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

  //////////////////////////////////////////////////
  // display
  //

  /** {@inheritDoc} */
  public String toString()
  {
    return localAddr.toString();
  }

}

