package brn.swans.net;

import java.util.LinkedList;
import java.util.List;

import jist.runtime.JistAPI;
import jist.runtime.Main;
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.misc.Util;
import jist.swans.net.MessageQueue;
import jist.swans.net.NetAddress;
import jist.swans.net.NetInterface;
import jist.swans.net.NetIp;
import jist.swans.net.NetMessage;
import jist.swans.net.PacketLoss;
import jist.swans.net.QueuedMessage;
import jist.swans.route.RouteInterface;
import brn.swans.mac.MacTxDORInterface;
import brn.swans.route.RouteDsrTxDORInterface;

public class NetTxDOR extends NetIpNotify implements NetTxDORInterface {

  public static class IterableMessageQueue implements MessageQueue {

    /** list of lists for packet storage */
    protected List[] lstPriorities;

    /** Index of highest priority. */
    protected byte topPri;

    /** List size limit. */
    protected int capacity;

    public IterableMessageQueue(int priorities, int capacity) {
      lstPriorities = new LinkedList[priorities];
      for (int i = 0; i < lstPriorities.length; i++)
        lstPriorities[i] = new LinkedList();
      topPri = (byte) Constants.NET_PRIORITY_D_BESTEFFORT;
      this.capacity = capacity;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#isEmpty()
     */
    public boolean isEmpty() {
      return size()==0;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#isFull()
     */
    public boolean isFull() {
      return size()==capacity;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#size()
     */
    public int size() {
      int size = 0;
      for (int i = 0; i < lstPriorities.length; i++) {
        size += lstPriorities[i].size();
      }
      return size;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#insert(jist.swans.net.QueuedMessage, int)
     */
    public void insert(QueuedMessage msg, int pri) {
      if(isFull()){
        // simply drop the packet.
        if (log.isDebugEnabled())
          log.debug(this + " Maximum list size "
              + capacity + " reached, dropping packet" +
              msg.toString());
        return;
      }

      topPri = (byte)Math.max(pri, topPri);
      lstPriorities[pri].add(msg);
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#insert(jist.swans.net.QueuedMessage)
     */
    public void insert(QueuedMessage msg) {
      insert(msg, lstPriorities.length-1);
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#getPri()
     */
    public int getPri() {
      while(lstPriorities[topPri].size()==0)
        topPri--;
      return topPri;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#get(int)
     */
    public QueuedMessage get(int pri) {
      return (QueuedMessage) lstPriorities[pri].get(0);
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#get()
     */
    public QueuedMessage get() {
      return (QueuedMessage) lstPriorities[getPri()].get(0);
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#remove(int)
     */
    public QueuedMessage remove(int pri) {
      return (QueuedMessage) lstPriorities[pri].remove(0);
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.net.MessageQueue#remove()
     */
    public QueuedMessage remove() {
      return (QueuedMessage) lstPriorities[getPri()].remove(0);
    }

    /**
     * Returns the specified queue.
     *
     * @param pri priority of the queue to retrieve
     * @return the specified queue.
     */
    public List getQueue(int pri) {
      return lstPriorities[pri];
    }
  }

  public static class NicInfoExt extends NetIp.NicInfo {
    /** whether a time scheduled transmission is in progress */
    public boolean timeSchedule = false;
  }


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

  /** self-referencing proxy entity. */
  protected NetTxDORInterface self;

  /** aka routing */
  protected RouteDsrTxDORInterface routing;

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

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

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetIp#createNicInfo()
   */
  protected NicInfo createNicInfo() {
    return new NicInfoExt();
  }

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

  /**
   * Return self-referencing proxy entity.
   *
   * @return self-referencing proxy entity
   */
  public NetInterface getProxy() {
    if (null == this.self) {
      // proxy entity
      this.self = (NetTxDORInterface)JistAPI.proxy(new NetTxDORInterface.Dlg(this),
          NetTxDORInterface.class);
    }
    return this.self;
  }

  /* (non-Javadoc)
   * @see jist.swans.net.NetIp#setRouting(jist.swans.route.RouteInterface)
   */
  public void setRouting(RouteInterface routingEntity) {
    this.routing = (RouteDsrTxDORInterface) routingEntity;
    super.setRouting(routingEntity);
  }

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

  /*
   * (non-Javadoc)
   * @see brn.swans.net.NetIpNotify#endSend(jist.swans.misc.Message, int, jist.swans.misc.MessageAnno)
   */
  public void endSend(Message msg, int interfaceId, MessageAnno anno) {
    NicInfoExt nic = (NicInfoExt) nics[interfaceId];
    nic.timeSchedule = false;

    super.endSend(msg, interfaceId, anno);
  }

  /*
   * (non-Javadoc)
   * @see brn.swans.net.NetTxDORInterface#instantPump(byte, jist.swans.mac.MacAddress, java.util.List, short, boolean, long, jist.swans.net.NetAddress, short, byte, byte)
   */
  public void instantPump(byte interfaceId, MacAddress src, List candidates,
      short seqNo, boolean retry, long timeToSend, NetAddress netSrc,
      short netId, byte ttl, byte idxFinalDst) {
    if (Main.ASSERT)
      Util.assertion(timeToSend > JistAPI.getTime());

    // check if the mac currently processes a scheduled message
    NicInfoExt nic = (NicInfoExt) nics[interfaceId];
    if (nic.timeSchedule)
      return;

    // scan the packet queue and look if there is such a packet
    IterableMessageQueue q = (IterableMessageQueue) nic.q;
    QueuedMessage qmsg = null;

    // look in all queues cause we reenqueue packets with higher priority.
    for (int prio = 0; prio < Constants.NET_PRIORITY_NUM; prio++) {
      List queue = q.getQueue(prio);
      for (int i = 0; i < queue.size(); i++) {
        QueuedMessage tmp = (QueuedMessage) queue.get(i);
        NetMessage.Ip ipMsg = (NetMessage.Ip) tmp.getPayload();

        // src and id are used as unique identifiers
        // NOTE: only if incoming ttl is le
        if (netSrc.equals(ipMsg.getSrc())
            && ipMsg.getId() == netId
            && ipMsg.getTTL() >= ttl) {
          queue.remove(i);

          if(ipMsg.isFrozen()) ipMsg = ipMsg.copy();
          ipMsg.setTTL(ttl);
          // immutable once packet leaves node
          qmsg = new QueuedMessage(ipMsg.freeze(), tmp.getNextHop(), tmp.getAnno());
          break;
        }
      }
    }

    // if there is no such packet, return
    if (null == qmsg)
      return;

    // pump message (regardless whether the interface is busy or not)
    nic.timeSchedule = true;
    nic.busy = true;

    if(log.isInfoEnabled())
      log.info("send t="+JistAPI.getTime()+" to="+qmsg.getNextHop()+" data="+qmsg.getPayload());
    if (dequeueEvent.isActive())
      dequeueEvent.handle(qmsg.getPayload(), qmsg.getAnno(), localAddr, qmsg.getNextHop(), nic.q);
    if (sendToMacEvent.isActive())
      sendToMacEvent.handle(qmsg.getPayload(), qmsg.getAnno(), localAddr, qmsg.getNextHop());

    /*
     * NUGGET no jittering here
     */
    JistAPI.sleep(netMinDelay);

    ((MacTxDORInterface)nic.mac).sendWithTxDiv(qmsg.getPayload(), qmsg.getAnno(),
        candidates, timeToSend, src, seqNo, retry, idxFinalDst);
  }

  /*
   * (non-Javadoc)
   * @see brn.swans.net.NetTxDORInterface#reenqueuePacket(byte, jist.swans.misc.Message, jist.swans.mac.MacAddress, jist.swans.misc.MessageAnno)
   */
  public void reenqueuePacket(byte netId, Message msg, MacAddress nextHop, MessageAnno anno) {

    // TODO statistics about successful and unsuccessful forwarder selections

    // if the mac has currently canceled a packet and we should try it later again
    nics[netId].q.insert(new QueuedMessage(msg, nextHop, anno),
        Constants.NET_PRIORITY_D_CONTROLLEDLOAD);
  }

  /**
   * Only for testing purposes.
   */
  public void testEnqueuePacket(byte netId, Message msg, MacAddress nextHop,
      MessageAnno anno) {
    nics[netId].q.insert(new QueuedMessage(msg, nextHop, anno));
  }

}
