//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <Mac802_11e.java Thu 2007/05/29 beilke>
//

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

package jist.swans.mac;

import jist.runtime.JistAPI;
import jist.runtime.JistAPI.Continuation;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.mac.MacDcf.NetworkAllocationVector;
import jist.swans.mac.MacInterface.IMacEDcf;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.MessageQueue;
import jist.swans.net.NetInterface;
import jist.swans.net.NetMessage;
import jist.swans.net.QueuedMessage;
import jist.swans.net.MessageQueue.NoDropMessageQueue;
import jist.swans.phy.Phy802_11;
import jist.swans.phy.PhyMessage;
import jist.swans.radio.AbstractRadio;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.swans.rate.RateControlAlgorithmIF;

import org.apache.log4j.Logger;

/**
 * This class wraps the 4 EDCF instances with their queues.
 *
 * @author beilke
 */
public class Mac802_11e extends AbstractMac implements IMacEDcf {

  /** MAC logger. */
  public static final Logger log = Logger
      .getLogger(jist.swans.mac.Mac802_11e.class);

  /** which edcf has an TXOP */
  private short txopAC = TXOP_AC_NONE;

  /** 4 concurrent Macs working with EDCF */
  private MacEDcf edcfs[] = new MacEDcf[4];

//  /** size of the Mac input queue */
//  private static final byte QUEUE_CAPACITY = 127;
//
//  /** Mac input queue */
//  private NoDropMessageQueue queue = new NoDropMessageQueue((byte) 4,
//      QUEUE_CAPACITY);
  
  /** mac mode considering the modes of the concurrent EDCFs */
  public byte Qmode;

  private Message[] myMessage = new Message[4];
  private MessageAnno[] myAnno = new MessageAnno[4];

  /** for eventhandler */
  long txopStart;

  /** couldn't be inherited stuff */

  /**
   * Threshold packet size to activate RTS. Default=3000. Broadcast packets
   * never use RTS. Set to zero to always use RTS.
   */
  public int  THRESHOLD_RTS              = 2346;

  /**
   * Threshold packet size for fragmentation. Default=2346. Broadcast packets
   * are not fragmented.
   */
  public int  THRESHOLD_FRAGMENT         = 2346;

  /** Retransmissions attempted for long packets (those with RTS). */
  public byte RETRY_LIMIT_LONG           = 4;

  /** Retransmissions attempted for short packets (those without RTS). */
  public byte RETRY_LIMIT_SHORT    = 7;

  /** end not inherited stuff */


  /** wrapper for the real radio interface */
  protected class RadioDelegate extends AbstractRadio implements RadioInterface {

    /** self-referencing radio entity reference. */
    protected RadioInterface self;

    public RadioDelegate() {
      this.self = (RadioInterface)JistAPI.proxy(new RadioInterface.Dlg(this),
          RadioInterface.class);
    }

    public void endReceive(Message msg, Double power, RFChannel rfChannel, Object event) {
      radioEntity.endReceive(msg, power, rfChannel, event);
    }

    public void endSetChannel(RFChannel channel) {
      radioEntity.endSetChannel(channel);
    }

    public void endTransmit() {
      throw new RuntimeException("enable me again!");
//      radioEntity.endTransmit();
    }

    public RFChannel getChannel() throws Continuation {
      return radioEntity.getChannel();
    }
    
//    public double getNoise_mW() throws Continuation {
//      return radioEntity.getNoise_mW();
//    }

    public void receive(Message msg, Double power, Long duration) {
      radioEntity.receive(msg, power, duration);
    }

    public void setChannel(RFChannel channel, long delay) {
      radioEntity.setChannel(channel, delay);
    }

    public void setSleepMode(boolean sleep) {
      radioEntity.setSleepMode(sleep);
    }

    public void transmit(Message msg, long delay, long duration,
        MessageAnno anno) {
      this.transmit(msg, anno, delay, duration, 0);
    }

    public void transmit(Message msg, MessageAnno anno, long predelay,
        long duration, long postdelay) {
      PhyMessage phyMsg = (PhyMessage) msg;
      MacDcfMessage payloadMsg = (MacDcfMessage) phyMsg.getPayload();

      long internalNav = duration + predelay + payloadMsg.getDuration();
      setNavs(internalNav);
//      log.fatal("--- transmit nav: " + internalNav);
      radioEntity.transmit(msg, anno, predelay, duration, postdelay);
    }

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

    public void setMacEntity(MacInterface proxy) {
      throw new RuntimeException("who used me?");
    }

    /**
     * Return radio properties.
     *
     * @return radio properties
     */
    public RadioInfo getRadioInfo()
    {
      throw new RuntimeException("Cannot access RadioInfo object from here!");
    }

  }
  
//  /** the bit-rate selection algorithm used in this MAC entity */
//  protected RateControlAlgorithmIF rca;

  /** real radio wrapper */
  private RadioDelegate fakeRadio;

  /** PHY layer incl. radio information to pass to bit-rate selection */
  protected Phy802_11                    phy;

  /** MAC properties */
  protected MacInfo                      macInfo;

  /**
   * Instantiate new 802_11 mac entity.
   *
   * @param addr
   *          local mac address
   * @param radioInfo
   *          radio properties
   */
  public Mac802_11e(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo) {
    this(addr, radioInfo, macInfo, null);
  }

  public Mac802_11e(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo,
      RateControlAlgorithmIF rateSelection) {
    this(addr, radioInfo, macInfo, rateSelection, new Phy802_11(radioInfo));
  }

  public Mac802_11e(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo,
      RateControlAlgorithmIF rateSelection, Phy802_11 phy) {
    // properties

    //  proxy
    self = (MacInterface.IMacEDcf) JistAPI.proxy(
        new MacInterface.IMacEDcf.DlgE(this), MacInterface.IMacEDcf.class);

    macInfo.thresholdRts = THRESHOLD_RTS;

    // instantiate 4 DCFs
    edcfs[AC_BK] = new MacEDcf(addr, radioInfo, macInfo, rateSelection, phy, AC_BK, this);
    edcfs[AC_BE] = new MacEDcf(addr, radioInfo, macInfo, rateSelection, phy, AC_BE, this);
    edcfs[AC_VI] = new MacEDcf(addr, radioInfo, macInfo, rateSelection, phy, AC_VI, this);
    edcfs[AC_VO] = new MacEDcf(addr, radioInfo, macInfo, rateSelection, phy, AC_VO, this);

    Qmode = MAC_QMODE_SIDLE;

    this.phy = phy;
    this.macInfo = macInfo;

    fakeRadio = new RadioDelegate();

  }

  // entity hookup

  /** Radio downcall entity reference. */
  protected RadioInterface radioEntity;

  /** Network upcall entity interface. */
  protected NetInterface netEntity;

  /** network interface number. */
  protected byte netId;

  /** Self-referencing mac entity reference. */
  protected final MacInterface self;

  public MacInterface getProxy() {
    return self;
  }

  public Phy802_11 getPhy() {
    return phy;
  }

  public MacInfo getMacInfo() {
    return macInfo;
  }

  /**
   * Set the rate selection algorithm to rca.
   */
  public void setRateControlAlgorithm(RateControlAlgorithmIF rca) {
      //this.rca = rca;
      for (int i = AC_VO; i >= AC_BK; --i) {
        edcfs[i].setRateControlAlgorithm(rca);
      }
  }
  
  public void setRadioMode(byte mode) {
    if(log.isDebugEnabled()) {
      log.debug("propagating radioMode change to " + mode + " to edcfs at: " + this.getNode().getNodeId());
    }
    // use interface method to achieve concurrency
    for (int i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].getProxy().setRadioMode(mode);
    }
  }

  public void setTXOPAC(short newAC) {
    if (newAC != txopAC) {
      if (txopEvent != null && txopEvent.isActive()) {
        if (newAC == TXOP_AC_NONE) {
          txopEvent.handle(false, txopAC);
        } else {
          txopEvent.handle(true, newAC);
        }
      }
      if (txopTimeEvent != null && txopTimeEvent.isActive()) {
        if (newAC != TXOP_AC_NONE) {
          txopStart = JistAPI.getTime();
        } else {
          txopTimeEvent.handle(JistAPI.getTime() - txopStart, txopAC);
        }
      }
      txopAC = newAC;
    }
  }

  public short getTXOPAC() {
    return txopAC;
  }

  public byte getMode() {
    return Qmode;
  }

  public short internalCollision() {
    if(log.isDebugEnabled()) {
      log.debug("internalCollision() at: " + this.getNode().getNodeId());
    }
    long currentTime = JistAPI.getTime();
    
    if (getTXOPAC() != TXOP_AC_NONE) {
      return getTXOPAC();
    }

    // if radio mode is still idle
    if (Qmode != MAC_QMODE_SIDLE) {
      for (short i = AC_VO; i >= AC_BK; --i) {
        if (edcfs[i].isTransmitting() || edcfs[i].isAwaitingResponse()) {
          if(log.isInfoEnabled()) {
            log.info("TXOP assumed at: " + this.getNode().getNodeId() + "." + i);
          }
          return i;
        }
      }
    }

    for (short i = AC_VO; i >= AC_BK; --i) {
      if ((edcfs[i].mode == jist.swans.mac.MacEDcf.MAC_MODE_DIFS
          || edcfs[i].mode == jist.swans.mac.MacEDcf.MAC_MODE_SIFS
          || edcfs[i].mode == jist.swans.mac.MacEDcf.MAC_MODE_AIFS
          || edcfs[i].mode == jist.swans.mac.MacEDcf.MAC_MODE_SBO)
          && edcfs[i].getTimeOutTime() == currentTime) {
        if(log.isInfoEnabled()) {
          log.info("internalCollision TXOP awarded to: " + this.getNode().getNodeId() + i + " at " + currentTime);
        }
        
        // set collisionflag in lowerpriority ACs
        for (short j = AC_BK; j < i; ++j) {
          if ((edcfs[j].mode == jist.swans.mac.MacEDcf.MAC_MODE_SIFS
              || ((edcfs[j].mode == jist.swans.mac.MacEDcf.MAC_MODE_AIFS
                  || edcfs[j].mode == jist.swans.mac.MacEDcf.MAC_MODE_DIFS) && !edcfs[j].hasBackoff())
              || edcfs[j].mode == jist.swans.mac.MacEDcf.MAC_MODE_SBO)
              && edcfs[j].getTimeOutTime() == currentTime
              && edcfs[j].hasPacket() 
              //TODO
              ) {
            edcfs[j].setCollisionFlag(true);
          }
        }
        return i;
      }
    }

    if (log.isInfoEnabled()) {
      log.info("no internal Collision found at: " + this.getNode().getNodeId());
    }

    return TXOP_AC_NONE;
  }

  /**
   * Set the global Mac mode, influences modes of all concurrent EDCFs.
   *
   * @param mode
   *          new Mac mode
   */
  public void setMode(byte mode) {
    if (mode != Qmode) {
      if(log.isDebugEnabled()) {
        log.debug("setMode at: " + this.getNode().getNodeId() + " from " + Qmode + " to " + mode);
      }
      switch (mode) {
      case MAC_QMODE_SIDLE:
        if (isIdle()) {
          Qmode = mode;
        }
        break;
      case MAC_QMODE_SNAV:
        if (!isTransmitting() && !isAwaitingResponse()) {
          setNavs(getNav());
          Qmode = mode;
        }
        break;
      case MAC_QMODE_STX:
        if (isTransmitting() || isAwaitingResponse()) {
          Qmode = mode;
        }
        break;
      }
    }
  }

  /**
   * Sets the mode to SNAV for EDCFs that are not occuping the medium.
   *
   * @param nav
   *          the new nav
   */
  private void setNavs(long nav) {
    if(log.isDebugEnabled()) {
      log.debug("setNavs at: " + this.getNode().getNodeId());
    }
    if (nav <= 0) {
      return;
    }

    for (short i = AC_VO; i >= AC_BK; --i) {
      if (!edcfs[i].isAwaitingResponse() || !edcfs[i].isTransmitting()) {
        edcfs[i].internalNav(nav);
      }
    }
  }

  /**
   * Get the maximum nav set in any EDCF.
   *
   * @return max nav
   */
  private long getNav() {
    if(log.isDebugEnabled()) {
      log.debug("getNav at: " + this.getNode().getNodeId());
    }
    long retNav = 0;

    for (short i = AC_VO; i >= AC_BK; --i) {
      if (edcfs[i].isAwaitingResponse() || edcfs[i].isTransmitting()) {
        retNav = edcfs[i].getNav().getNav() > retNav ? edcfs[i].getNav()
            .getNav() : retNav;
      }
    }

    return retNav;
  }


  /**
   * Is one EDCF awaiting a response?
   *
   * @return whether mac is awaiting a respone
   */
  private boolean isAwaitingResponse() {
    for (short i = AC_VO; i >= AC_BK; --i) {
      if (edcfs[i].isAwaitingResponse()) {
        return true;
      }
    }

    return false;
  }

  /**
   * Is one EDCF transmitting?
   *
   * @return whether mac is transmitting
   */
  private boolean isTransmitting() {
    for (short i = AC_VO; i >= AC_BK; --i) {
      if (edcfs[i].isTransmitting()) {
        return true;
      }
    }

    return false;
  }

  /**
   * Is the mac idle?
   *
   * @return whethter all EDCFs are idle
   */
  public boolean isIdle() {
    return !isTransmitting() && !isAwaitingResponse();
  }

  /**
   * Radio has locked onto a packet signal; mac may have a peek.
   *
   * @param msg
   *          packet currently in flight
   */
  public void peek(Message msg, MessageAnno anno) {
    if(log.isInfoEnabled()) {
      log.info("peeking " + this.getNode().getNodeId());
    }
 // choose the right EDCF
    PhyMessage phyMsg = (PhyMessage) msg;

    if (phyMsg.getPayload() instanceof Mac802_11eMessage.QOS_Data) {
      Mac802_11eMessage.QOS_Data macMsg = (Mac802_11eMessage.QOS_Data) phyMsg.getPayload();
      MacEDcf.QOSControl qosCtl = new MacEDcf.QOSControl();
      qosCtl.setQOSControl(macMsg.getQoSControl());
      short ac = priorityToAccessCategory((byte)qosCtl.getTid());

      edcfs[ac].getProxy().peek(msg, anno);
      return;
    }
    else if (phyMsg.getPayload() instanceof Mac802_11eMessage.QOS_BlockAck) {
      Mac802_11eMessage.QOS_BlockAck macMsg = (Mac802_11eMessage.QOS_BlockAck) phyMsg.getPayload();
      short ac = priorityToAccessCategory((byte)macMsg.getBARControl());

      edcfs[ac].getProxy().peek(msg, anno);
      return;
    }
    else {
      for (short i = AC_VO; i >= AC_BK; --i) {
        if (edcfs[i].isAwaitingResponse()) {
          edcfs[i].getProxy().peek(msg, anno);
          return;
        }
      }
    }
    edcfs[AC_BE].getProxy().peek(msg, anno);
//    throw new RuntimeException("peeking default, debug only");
  }

  /**
   * MacInterface.
   */
  public void receive(Message msg, MessageAnno anno) {
    if(log.isInfoEnabled()) {
      log.info("recieving " + this.getNode().getNodeId());
    }
    
    // choose the right EDCF
    PhyMessage phyMsg = (PhyMessage) msg;

    if (phyMsg.getPayload() instanceof Mac802_11eMessage.QOS_Data) {
      Mac802_11eMessage.QOS_Data macMsg = (Mac802_11eMessage.QOS_Data) phyMsg.getPayload();
      MacEDcf.QOSControl qosCtl = new MacEDcf.QOSControl();
      qosCtl.setQOSControl(macMsg.getQoSControl());
      short ac = priorityToAccessCategory((byte)qosCtl.getTid());

      edcfs[ac].getProxy().receive(msg, anno);
      return;
    }
    else if (phyMsg.getPayload() instanceof Mac802_11eMessage.QOS_BlockAck) {
      Mac802_11eMessage.QOS_BlockAck macMsg = (Mac802_11eMessage.QOS_BlockAck) phyMsg.getPayload();
      short ac = priorityToAccessCategory((byte)macMsg.getBARControl());

      edcfs[ac].getProxy().receive(msg, anno);
      return;
    }
    else {
      // check if a EDCF is expecting a packet
      for (short i = AC_VO; i >= AC_BK; --i) {
        if (edcfs[i].isAwaitingResponse()) {
          edcfs[i].getProxy().receive(msg, anno);
          return;
        }
      }
    }
    // fail-safe or unexpected receive
    edcfs[AC_BE].getProxy().receive(msg, anno);
    // TODO remove next line
//    throw new RuntimeException("failsafe receive catch, just for debugging");
  }

  // ////////////////////////////////////////////////
  // from network layer
  //

  /**
   * Network layer would like to send the following packet. Should be called
   * only after Mac has notified that it is wants a packet.
   *
   * @param msg
   *          packet to send
   * @param nextHop
   *          destination mac
   */
  public void send(Message msg, MacAddress nextHop, MessageAnno anno) {
    if(log.isInfoEnabled()) {
      log.info("sending called from net" + this.getNode().getNodeId());
    }
    
    NetMessage.Ip ip = (NetMessage.Ip) msg;
    byte pri = ip.getPriority();
    short ac = priorityToAccessCategory(pri);

    if (!edcfs[ac].isQueueFull()) {
      edcfs[ac].insertIntoQueue(new QueuedMessage(msg, nextHop, anno));
    } else {
      
      //  throw new RuntimeException("Queue handling failure at AC " + ac);
      
    }
//    if (!queue.isFull()) {
//      if (enqueueEvent != null && enqueueEvent.isActive()) {
//        enqueueEvent.handle(msg, anno, nextHop, queue);
//      }
//      queue.insert(new QueuedMessage(msg, nextHop, anno), ac);
//    }
//    else {
//      // TODO not calling endSend in the netEntity
////      long time = JistAPI.getTime();
////      return;
////      throw new RuntimeException("Queue full");
//    }

    continueSend(-1);
  }

  /**
   * Takes a messages from the queue and gives it to the corresponding EDCFs.
   */
  private void doSend() {
    if(log.isDebugEnabled()) {
      log.debug("doSend at: " + this.getNode().getNodeId());
    }

    for (short i = AC_VO; i >= AC_BK; --i) {
//      // TODO not good
//      if (queue.get(i) != null && edcfs[i].hasPacket() == false && (/*!edcfs[i].isAwaitingResponse() &&*/ !edcfs[i].isTransmitting())) {
//        QueuedMessage qmsg = queue.remove(i);
//        if (dequeueEvent != null && dequeueEvent.isActive()) {
//          dequeueEvent.handle(qmsg, qmsg.getAnno(), qmsg.getNextHop(), queue);
//        }
//        edcfs[i].getProxy().send(qmsg.getPayload(), qmsg.getNextHop(), qmsg.getAnno());
//      }
      if (edcfs[i].isQueueEmpty() == false && edcfs[i].hasPacket() == false && (/*!edcfs[i].isAwaitingResponse() &&*/ !edcfs[i].isTransmitting())) {
        QueuedMessage qmsg = edcfs[i].removeFromQueue();
        
        edcfs[i].getProxy().send(qmsg.getPayload(), qmsg.getNextHop(), qmsg.getAnno());
      }
    }
  }

  /**
   * Send message and ask NET layer for more packets.
   *
   */
  public void continueSend(int ac) {
    if(log.isDebugEnabled()) {
      log.debug("continueSend at: " + this.getNode().getNodeId());
    }
    doSend();

    // call endSend again
    if (ac != -1) {
      netEntity.endSend(myMessage[ac], netId, myAnno[ac]);
      myMessage[ac] = null;
      myAnno[ac] = null;
    } else {
      netEntity.endSend(null, netId, null);
    }
  }
  
  // DUMMY
  public void resetTXOPEvent() { }

  /**
   * deliver information about send message.
   * temporary keep this info to give it to the net layer
   * for event reporting
   */
  public void finishedSend(Message msg, int netId, MessageAnno anno, int ac) {
    if (myMessage[ac] != null) {
      throw new RuntimeException("this will lead to lost message delivery report");
    }
    myMessage[ac] = msg;
    myAnno[ac] = anno;
  }

  /**
   * Translate IP priority to access category. TODO make it dynamic
   *
   * @param priority
   *          IP message priority
   * @return AC
   */
  public static short priorityToAccessCategory(final byte priority) {
    short ap;

    switch (priority) {
    case Constants.NET_PRIORITY_D_BACKGROUND:
    case Constants.NET_PRIORITY_D_UNDEFINED:
      ap = AC_BK;
      break;
    case Constants.NET_PRIORITY_D_BESTEFFORT:
    case Constants.NET_PRIORITY_D_EXCELLENTEFFORT:
      ap = AC_BE;
      break;
    case Constants.NET_PRIORITY_D_CONTROLLEDLOAD:
    case Constants.NET_PRIORITY_D_VIDEO:
      ap = AC_VI;
      break;
    case Constants.NET_PRIORITY_D_VOICE:
    case Constants.NET_PRIORITY_D_NETWORKCONTROL:
      ap = AC_VO;
      break;
    default:
      throw new RuntimeException("unknown priority byte, debug only");
//      ap = AC_BE;
//      break;
    }
    return ap;
  }

//  /**
//   * Queue accessor.
//   *
//   * @return the queue
//   */
//  public MessageQueue getQueue() {
//    return queue;
//  }


  /**
   * not in interface, but necessary.
   * which one is returned doesn't matter
   **/
  public Mac802_11 getMac() {
    return edcfs[AC_BE];
  }

  /**
   * @param nav the new network allocation vector implementation
   */
  public void setNav(NetworkAllocationVector nav) {
    if(log.isDebugEnabled()) {
      log.debug("setNav(" + nav + ") at: " + this.getNode().getNodeId());
    }
    for (short i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].nav = nav;
    }
  }

  public void setUseAnnotations(boolean useAnnotations) {
    for (short i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].useAnnotations = useAnnotations;
    }
  }

  /**
   * Set promiscuous mode (whether to pass all packets through).
   *
   * @param promisc
   *          promiscuous flag
   */
  public void setPromiscuous(boolean promisc) {
    for (short i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].promisc = promisc;
    }
  }

  public void setMsgFactory(MacMessageFactory.M802_11e msgFactory) {
    for (short i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].setMsgFactory(msgFactory);
    }
  }

  /**
   * Hook up with the network entity.
   *
   * @param net
   *          network entity
   * @param netid
   *          network interface number
   */
  public void setNetEntity(NetInterface net, byte netid) {
    if (!JistAPI.isEntity(net))
      throw new IllegalArgumentException("expected entity");
    this.netEntity = net;
    this.netId = netid;

    for (short i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].setNetEntity(net, netid);
    }
  }

  /**
   * Hook up with the radio entity.
   *
   * @param radio
   *          radio entity
   */
  public void setRadioEntity(RadioInterface radio) {
    if (!JistAPI.isEntity(radio))
      throw new IllegalArgumentException("expected entity");
    this.radioEntity = radio;

    for (short i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].setRadioEntity(fakeRadio.getProxy());
    }
  }

  public void setNode(Node node) {
    super.setNode(node);
    for (short i = AC_VO; i >= AC_BK; --i) {
      edcfs[i].setNode(node);
    }
  }

  /** needed  by interface */

  public void cfDone(boolean backoff, boolean delPacket) { }

  public void startTimer(long delay, byte mode) {}

  public void timeout(int timerId) {}

  public MacAddress getAddress() {
    return edcfs[AC_BK].getAddress();
  }

  //@Override
  public boolean isQueueFull(short ac) {
    return edcfs[ac].isQueueFull();
  }
}
