package jist.swans.mac;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;
import jist.swans.net.MessageQueue;
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.RadioInfo;
import jist.swans.rate.ConstantRate;
import jist.swans.rate.RateControlAlgorithmIF;

import org.apache.log4j.Logger;

/**
 * This is a MAC which realizes the Enhanced Distributed Coordination Function
 * (EDCF).
 * 
 * @author Beilke
 */
public class MacEDcf extends jist.swans.mac.Mac802_11 implements
    MacInterface.IMacEDcf {
  /** MAC logger. */
  public static final Logger log = Logger.getLogger(MacEDcf.class);

  /** Self-referencing mac entity reference. */
  protected IMacEDcf self;
    
  /** QOS vars */

  /** size of the Mac input queue */
  private static final byte QUEUE_CAPACITY = 127;

  /** Mac input queue */
  private NoDropMessageQueue queue = new NoDropMessageQueue((byte) 1,
      QUEUE_CAPACITY);
  
  /** own Access Category */
  private short myAC;

  /** Minimum collision window (for backoff). */
  public short CW_MIN;

  /** Maximum collision window per EDCF (for backoff). */
  public short CW_MAX;

  /** Maximum collision window as defined in 802.11 */
  public static final short aCW_MAX = 1023;

  /** Number of SlotTimes making up AIFS */
  public short AIFSN;

  /** Access Category dependent Interframe Space (unit:us) */
  public long AIFS;

  /** Limit for Transmission Opportunities */
  public long TXOP_LIMIT;

  /** parent Mac802_11e reference */
  public Mac802_11e parent;

  /** absolut time when last TXOP ends */
  public long txop;

  /** absolut time when the next timeout will be called */
  private long timeOutTime;

  /** mac mode: waiting for aifs timer. */
  public static final byte MAC_MODE_AIFS = 14;
  
  /** mac mode: waiting for block ack. */
  public static final byte MAC_MODE_SWFBA = 16;
  
  /** mac mode: transmitting Block ACK packet. */
  public static final byte MAC_MODE_XBACK = 15;

  /** factory for mac messages */
  private MacMessageFactory.M802_11e msgFactory;

  /** last nav set by this dcf in absolut time */
  private long lastNav;

  // TODO implement
  /** relative time, MSDUs may stay in the MAC layer */
  private long MSDU_LIEFTIME;

  /** priority of packet currently being transmitted. */
  protected byte                      packetPri;
  
  /** BlockAck vars */
  
  /** 
   * holds the packets, so they can be retransmitted
   * Hashtable(MacAddress, Hashtable(Short TID, Hashtable(Short seq, PacketSafer))) 
   **/
  private Hashtable blockAckPackets = new Hashtable();
  //private Hashtable<MacAddress, Hashtable<Short,Hashtable<Short, PacketSafer>>> blockAckPackets = new Hashtable<MacAddress, new Hashtable<Short, Hashtable<Short, PacketSafer>>>();
  
  /** list of transmitted sequence numbers for block ack 
   * Hashtable(MacAddress, Hashtable(Short TID, ArrayList(Short)))*/
  private Hashtable blockAckTransmitted = new Hashtable();
  //private Hashtable<MacAddress, Hashtable<Short, ArrayList<Short>>> blockAckTransmitted = new Hashtable<MacAddress, new Hashtable<Short, ArrayList<Short>>>();
  
  /** list of received sequence numbers for block ack 
   * Hashtable(MacAddress, Hashtable(Short TID, ArrayList(Short)))*/
  private Hashtable blockAckReceived = new Hashtable();
  //private Hashtable<MacAddress, Hashtable<Short, ArrayList<Short>>> blockAckReceived = new Hashtable<MacAddress, new Hashtable<Short, ArrayList<Short>>>();
  
  /** not acknowledged packets under block ack counter 
   * Hashtable(MacAddress, Hashtable(Short tid, ArrayList(Short)))*/
  private Hashtable blockAcksFailed = new Hashtable();
  //private Hashtable<MacAddress, Hashtable<Short, Integer>> blockAcksFailed = new Hashtable<MacAddress, new Hashtable<Short, Integer>>();
  
  /** block ack req received */
  private boolean blockAckReqReceived = false;
  
  /** block ack req should be send */
  private boolean blockAckReqToSend = false;
  
  /** Address of the Block Ack requester */
  private MacAddress blockAckRequester;
  
  /** Address of the Block Ack receiver */
  private MacAddress blockAckReqReceiver;
  
  /** TID of the BlockAck to request */
  private byte blockAckReqPri;
  
  /** TID of the BlockAckReq to recieved */
  private byte blockAckSendPri;
  
  /** Seq number of the recieved block ack req */
  private short blockAckReqSeq;
  
  /** BlockAck Buffer Size */
  public short blockAckBufferSize = 64;

  /** Mark for collisions */
  private boolean collisionFlag = false;
  
  /** QOS vars end */

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

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

  public MacEDcf(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo,
      RateControlAlgorithmIF rateSelection, Phy802_11 phy,
      short accessCategory, Mac802_11e parentClass) {
    super(addr, radioInfo, macInfo, rateSelection, phy);

    // proxy
    self = (IMacEDcf) JistAPI.proxy(new IMacEDcf.DlgE(
            this), IMacEDcf.class);
    
    /** QOS stuff p. 171*/
    // TODO make variable setable from outside
    myAC = accessCategory;
    // default values
    switch (accessCategory) {
    case AC_BK:
      CW_MAX = aCW_MAX;
      CW_MIN = cwMin();
      AIFSN = 7;
      TXOP_LIMIT = 0;
      break;
    case AC_BE:
      CW_MAX = aCW_MAX;
      CW_MIN = cwMin();
      AIFSN = 3;
      TXOP_LIMIT = 0;
      break;
    case AC_VI:
      CW_MAX = cwMin();
      CW_MIN = (short) (((cwMin() + 1) / 2) - 1);
      AIFSN = 2;
      TXOP_LIMIT = 6016 * Constants.MICRO_SECOND;
      break;
    case AC_VO:
    default:
      CW_MAX = (short) (((cwMin() + 1) / 2) - 1);
      CW_MIN = (short) (((cwMin() + 1) / 4) - 1);
      AIFSN = 2;
      TXOP_LIMIT = 3264 * Constants.MICRO_SECOND;
      break;
    }
    parent = parentClass;
    cw = CW_MIN;

    /** QOS stuff end */

    // TODO for tests
      rca = new ConstantRate(Constants.BANDWIDTH_1Mbps == radioInfo
              .getBitratesSupported()[0] ? Constants.BANDWIDTH_11Mbps
              : Constants.BANDWIDTH_6Mbps);
    // this.getAddress();
  }

  /** Properties */

  public void setMsgFactory(MacMessageFactory.M802_11e msgFactory) {
    super.setMsgFactory(msgFactory);
    this.msgFactory = msgFactory;
  }

  /**
   * Exposes the absolut time of the next schedule timeout.
   */
  public long getTimeOutTime() {
    return timeOutTime;
  }

  // /**
  // * The nav concurrent EDCFs should respect.
  // */
  // public long getInternalNav() {
  // return internalNav;
  // }

  /**
   * @param collisionFlag the collisionFlag to set
   */
  public void setCollisionFlag(boolean collisionFlag) {
    this.collisionFlag = collisionFlag;
  }

  /**
   * @return AIFS[AC] time for the current MAC (unit: us). Contains SIFS, thus
   *         includes Rx-Tx-Turnaround time.
   */
  public long getAifs() {
    return getSifs() + AIFSN * getSlotTime();
  }

  /**
   * @return The AIFS[AC] time minus the Rx/Tx turnaround time. During this time
   *         the radio is not deaf.
   */
  public long getTxAifs() {
    return AIFSN * getSlotTime() + getTxSifs();
  }

  /** end Properties */

  /**
   * Local class used to manage QOS Control Fields.
   */
  public static class QOSControl {

    /**
     * Defines the ack policies.
     * Use byte to avoid subtle mistakes in comparisons.
     */
//    public static class AckPolicy {
//      /** Directed frames with ack required */
//    	public static final Byte NORMAL_ACK = new Byte((byte) 0);
//      /** Directed frames where sender doesn't require ack, and broadcast and multicast */
//    	public static final Byte NO_ACK = new Byte((byte) 1);
//      /** only used for QoS CF-Poll and QoS CF-Ack+CF-Poll */
//    	public static final Byte NO_EXPLICIT_ACK = new Byte((byte) 2);
//      /** recipent does not take action upon receipt, but expects BlockAckReq */
//    	public static final Byte BLOCK_ACK = new Byte((byte) 3);
//    	
//    	/** The type of the members of this class (enum). */
//    	public static final Class TYPE;
//    	static {
//    		try {
//					TYPE = Class.forName("java.lang.Byte");
//				} catch (ClassNotFoundException e) {
//					throw new RuntimeException("Class of enum elements not found!");
//				} 
//    	}
//    }

    // public static final int QOS_Ctl_AckPol_NORMAL_ACK = 0;
    // public static final int QOS_Ctl_AckPol_NO_ACK = 1;
    // public static final int QOS_Ctl_AckPol_NO_EXPLICIT_ACK = 2;
    // public static final int QOS_Ctl_AckPol_BLOCK_ACK = 3;

    /** TID 0-3 */
    public short tid;

    /** EndOfServicePeriod 4 */
    public short eosp = 0;

    /** ack policy 5-6 */
    public short ackPolicy;

    /** TXOP Limit/duration and queue size 7-15 */
    public short txopQueue;

    /**
     * Get the field constructed from its subfields.
     * 
     * @return the field QOSControl field.
     */
    public short getQOSControl() {
      return (short) (txopQueue + ((short) (ackPolicy << 8))
          + ((short) (eosp << 11)) + ((short) (tid << 12)));
    }

    /**
     * Make the subfields accessable by parsing them from the 16 bit field.
     * 
     * @param qosCtl
     *          16 bit header field
     */
    public void setQOSControl(short qosCtl) {
      this.tid = (short) (qosCtl >> 12);
      this.eosp = (short) ((qosCtl >> 11) & 0x1);
      this.ackPolicy = (short) ((qosCtl >> 8) & 0x7);
      this.txopQueue = (short) (qosCtl & 0xF);
    }

    /**
     * Sets the Traffic Identifier.
     * 
     * @param tid
     *          0-7 as in 802.1d 8-15 as TSPECs, otherwise mapped to 0
     */
    public void setTID(short tid) {
      if (tid > 15 || tid < 0) {
        tid = 0;
      }
      this.tid = tid;
    }

    /**
     * Set End of Service Period subfield.
     * Only used by HC in QAP!
     * 
     * @param isEnd
     *          Last packet in Service Period.
     */
    public void setEOSP(boolean isEnd) {
      if (isEnd == true) {
        this.eosp = 1;
      } else {
        this.eosp = 0;
      }
    }
    
    /**
     * This bit toggles whether the
     * txopQueue is the TXOP duration requested or the queue size.
     * Use during EDCA!
     * @param isQueueSize
     *          Last packet in Service Period.
     */
    public void set(boolean isQueueSize) {
      if (isQueueSize == true) {
        this.eosp = 1;
      } else {
        this.eosp = 0;
      }
    }

    /**
     * Set the Acknowledgement Policy.
     * 
     * @param ackPolicy
     *          policy to set
     */
    public void setAckPolicy(AckPolicy ackPolicy) {
//      switch (ackPolicy.val()) {
//      case NORMAL_ACK:
//        this.ackPolicy = 0;
//        break;
//      case NO_ACK:
//        this.ackPolicy = 1;
//        break;
//      case NO_EXPLICIT_ACK:
//        this.ackPolicy = 2;
//        break;
//      case BLOCK_ACK:
//        this.ackPolicy = 3;
//        break;
//      }
    	this.ackPolicy = ackPolicy.val();
    }

    /**
     * Set the TXOP limit (for polls by QAP).
     * 
     * @param limit for TXOPs
     */
    public void setTXOPLimit(short limit) {
      this.txopQueue = limit;
    }
    
    /**
     * Set the TXOP request duration.
     * 
     * @param duration requested duration
     */
    public void setTXOPReqDuration(short duration) {
      this.txopQueue = duration;
      this.eosp = 0;
    }
    
    /**
     * Set the Queue size.
     * 
     * @param size current queue size
     */
    public void setQueueSize(short size) {
      this.txopQueue = size;
      this.eosp = 1;
    }

    /**
     * Get the Acknowledgement Policy.
     * 
     * @return the ackPolicy
     */
    public AckPolicy getAckPolicy() {
      switch (this.ackPolicy) {
      case 1:
        return AckPolicy.NORMAL_ACK;
      case 2:
        return AckPolicy.NO_ACK;
      case 3:
        return AckPolicy.NO_EXPLICIT_ACK;
      case 4:
        return AckPolicy.BLOCK_ACK;
      }
      return AckPolicy.NORMAL_ACK;
//      switch (this.ackPolicy) {
//      case 0:
//        return AckPolicy.NORMAL_ACK;
//      case 1:
//        return AckPolicy.NO_ACK;
//      case 2:
//        return AckPolicy.NO_EXPLICIT_ACK;
//      case 3:
//        return AckPolicy.BLOCK_ACK;
//      }
//      return AckPolicy.NORMAL_ACK;
    }

    /**
     * Returns whether bit 4 is set.
     * 
     * @return the eosp
     */
    public boolean isEOSP() {
      return eosp == 1 ? true : false;
    }
    
    /**
     * Returns whether bit 4 is set.
     * 
     * @return the eosp
     */
    public boolean isQueueSize() {
      return eosp == 1 ? true : false;
    }

    /**
     * Get the traffic identifier or TSPEC.
     * 
     * @return the tid
     */
    public short getTid() {
      return tid;
    }

    /**
     * Get the TXOP limit or TXOP req duration or queue size.
     * 
     * @return the txop limit/req duration or Queue size
     */
    public short getTxopQueue() {
      return txopQueue;
    }
  }

  /** overriden functions */
  
  /**
   * Perform backoff.
   */
  protected void backoff() {
    if (log.isDebugEnabled()) {
      log.debug("backoff() on: " + node.getNodeId() + "." + myAC);
    }
    resetTXOP();
    super.backoff();
  }

  /**
   * Extended inter frame space. Wait used by stations to gain access to the
   * medium after an error.
   * 
   * @return EIFS time for the current MAC (unit: us).
   */
  public long getEifs() {
    // 802.11e-2005.pdf p. 76 & 81
    return super.getEifs() - getDifs() + getAifs();
  }

  // MacInterface.MacDcf interface
  public void cfDone(boolean backoff, boolean delPacket) {
    if (log.isDebugEnabled()) {
      log.debug("cfDone backoff: " + backoff + " delPacket: " + delPacket
          + " at: " + this.getNode().getNodeId() + "." + myAC);
    }

    if (delPacket) {
      // since endSend in the network layer is not called anymore
      // there will be no sleep time, to clean up (code below)
      // continueSend would never work in that case
      // this is a hack for the eventhandler
      parent.finishedSend(packet, netId, anno, myAC);

      // broadcast packets are not acknowledged; so we will miss the
      // macTxFinished event.
      if (isBroadcast()) {
        if (macTxFinished.isActive())
          macTxFinished.handle(packet, anno, true, (byte) 0);
      }

      packet = null;
      anno = null;
      packetNextHop = null;

      // don't dequeue next packet directly, need to check it in doDifs() first
      ((IMacEDcf) parent.getProxy()).continueSend(myAC);
    }
    if (backoff) {
      setBackoff();
    }
    doDifs();
  }

  /**
   * Set the current mac mode.
   * 
   * @param mode
   *          new mac mode
   */
  protected void setMode(byte mode) {
    if (log.isDebugEnabled()) {
      log.debug("setting mode from " + this.mode + " to " + mode + " at: "
          + this.getNode().getNodeId() + "." + myAC);
    }
    if (this.mode != mode && macModeChanged.isActive()) {
      macModeChanged.handle(this.mode, mode, myAC);
    }
    this.mode = mode;

    switch (mode) {
    case MAC_MODE_AIFS:
    case MAC_MODE_DIFS:
    case MAC_MODE_SIFS:
    case MAC_MODE_SBO:
    case MAC_MODE_SIDLE:
      parent.setMode(MAC_QMODE_SIDLE);
      break;
    case MAC_MODE_SNAV:
      parent.setMode(MAC_QMODE_SNAV);
      break;
    case MAC_MODE_SWFACK:
    case MAC_MODE_SWFCTS:
    case MAC_MODE_SWFDATA:
    case MAC_MODE_XACK:
    case MAC_MODE_XBROADCAST:
    case MAC_MODE_XCTS:
    case MAC_MODE_XRTS:
    case MAC_MODE_XUNICAST:
    case MAC_MODE_SWFBA:
    case MAC_MODE_XBACK:
      parent.setMode(MAC_QMODE_STX);
      break;
    default:
      throw new RuntimeException();
    }
  }

  // ////////////////////////////////////////////////
  // timer routines
  //

  // MacInterface
  public void startTimer(long delay, byte mode) {
    if (log.isDebugEnabled()) {
      log.debug("starttimer on " + this.getNode().getNodeId() + "." + myAC
          + " mode: " + mode + " delay: " + delay);
    }
    // save expected timeout to check for internal collisions
    timeOutTime = JistAPI.getTime() + delay;
    super.startTimer(delay, mode);
  }

  // MacInterface
  public void timeout(int timerId) {
    if (timerId != this.timerId)
      return;
    
    if (log.isDebugEnabled()) {
      log.debug("timeout on " + this.getNode().getNodeId() + "." + myAC
          + " mode: " + mode);
    }
    // hide new modes from base classes
    if (mode == MAC_MODE_AIFS) {
      mode = MAC_MODE_DIFS;
    }
    if (mode == MAC_MODE_SWFBA) {
      retry();
      return;
    }
    
    // do we have to serve or request a BlockAck
    if (blockAckReqReceived == true) {
      if (blockAckReceived.containsKey(blockAckRequester) && ((Hashtable)blockAckReceived.get(blockAckRequester)).containsKey(Byte.valueOf(blockAckSendPri)) && ((ArrayList) ((Hashtable)blockAckReceived.get(blockAckRequester)).get(Byte.valueOf(blockAckSendPri))).size() > 0) {
        switch (mode) {
          case MAC_MODE_SIFS:
              break;
          case MAC_MODE_DIFS:
                  if (hasBackoff()) {
                      backoff();
                      return;
                  }
              break;
          case MAC_MODE_SBO:
              if (Main.ASSERT)
                  Util.assertion(boStart + bo == JistAPI.getTime());
              clearBackoff();
              break;
          default:
            super.timeout(timerId);
            return;
        }
        sendBlockAck();
      } else {
        blockAckReqReceived = false;
        log.error("recieved block ack req, but no response available");
        throw new RuntimeException("recieved block ack req, but no response available");
//        super.timeout(timerId);
      }
    } else if (blockAckReqToSend == true) {
      switch (mode) {
        case MAC_MODE_SIFS:
            break;
        case MAC_MODE_DIFS:
                if (hasBackoff()) {
                    backoff();
                    return;
                }
            break;
        case MAC_MODE_SBO:
            if (Main.ASSERT)
                Util.assertion(boStart + bo == JistAPI.getTime());
            clearBackoff();
            break;
        default:
          super.timeout(timerId);
          return;
      }
      sendBlockAckRequest();
    } else {
      
      super.timeout(timerId);
    }
  }

  /**
     * Return whether current packet large enough to require RTS. TODO: Are
     * there other reasons to request RTS/CTS? How/when is CTS-to-self
     * triggered?
     * 
     * @return does current packet require RTS.
     */
    public boolean shouldRTS() {
        if (packet == null) {
          return false;
        }
        return super.shouldRTS();
    }
    
  /** */
  protected void doDifs() {
    if (log.isDebugEnabled()) {
      log.debug("doDifs on: " + node.getNodeId() + "." + myAC);
    }
    // we have to effectivly use TXOPs
//    QueuedMessage next = parent.getQueue().get(myAC);
    QueuedMessage next = getFromQueue();

    if (isRadioIdle()) {
      if (nav.waitingNav()) {
        startTimer(nav.getNav() - JistAPI.getTime(), MAC_MODE_SNAV);
      } else {
        if ((packet != null && fitsInTXOP())
            || (packet == null && next != null && fitsInTXOP(next.getPayload(),
                next.getNextHop()))) {
          startTimer(needEifs ? getTxEifs() : getTxSifs(), MAC_MODE_SIFS);
        } else {
          if (hasTXOP()) {
            resetTXOP();
            setBackoff();
          }
          startTimer(needEifs ? getTxEifs() : getTxAifs(), MAC_MODE_AIFS);
        }
      }
    } else {
      // This enables TXOP use for NORMAL_ACK (get around backoff)
      if (mode == MAC_MODE_SWFACK && radioMode == Constants.RADIO_MODE_RECEIVING && next != null
          && fitsInTXOP(next.getPayload(), next.getNextHop()) && !needEifs
          && !hasBackoff()) {
        startTimer(getTxSifs(), MAC_MODE_SIFS);
      } else {
        if (hasTXOP()) {
          resetTXOP();
          setBackoff();
        }
        idle();
      }
    }
  }


  // ////////////////////////////////////////////////
  // send-related functions
  //

  //MacInterface
  public void send(Message msg, MacAddress nextHop, MessageAnno anno) {
    NetMessage.Ip ip = (NetMessage.Ip) msg;
    this.packetPri = ip.getPriority();
    super.send(msg, nextHop, anno);
  }
  
  protected void sendPacket() {
    if (log.isDebugEnabled()) {
      log.debug("sendPacket on " + this.getNode().getNodeId() + "." + myAC);
    }
    if (isInternalCollision()) {
      retry(false);
    } else {
      if (priorityToAcknowlegment(packetPri) == AckPolicy.NO_ACK) {
        decCW();
      }
      if (!isBroadcast()) {
        if (hasPacket() && !hasTXOP() && TXOP_LIMIT > 0) {
          parent.setTXOPAC(myAC);
          txop = JistAPI.getTime() + TXOP_LIMIT;
        }
      }
      super.sendPacket();
    }
  }

  /**
   * Return whether mac currently has a packet to send.
   * 
   * @return whether mac has packet to send.
   */
  protected boolean hasPacket() {
    Object[] result = hasFailedBlockAckPackets();
    if (result[0] != null && result[1] != null) {
      return true;
    }
    return super.hasPacket();
  }
    
  protected void sendData() {
    if (log.isDebugEnabled()) {
      log.debug("sendData on " + this.getNode().getNodeId() + "." + myAC);
    }
    if (isInternalCollision()) {
      retry(false);
    } else {
      if (priorityToAcknowlegment(packetPri) == AckPolicy.NO_ACK) {
        decCW();
      }
      // check for multicast
      if (!isBroadcast()) {
        if (hasPacket() && !hasTXOP() && TXOP_LIMIT > 0) {
          parent.setTXOPAC(myAC);
          txop = JistAPI.getTime() + TXOP_LIMIT;
        }
      }
      super.sendData();
    }
  }

  /**
   * Decide on whether to wait for an acknowledgement. 
   */
  protected void sendDataUnicast() {
    MacAddress dst = null;
    Byte tid = null;
    
    setUpBlockAck();
    
//    // first send outstanding (failed) packets
//    Enumeration e1 = blockAcksFailed.keys();
//    while (e1.hasMoreElements()) {
//      dst = (MacAddress) e1.nextElement();
//      Hashtable tids = (Hashtable)blockAcksFailed.get(dst);
//      boolean found = false;
//      Enumeration e2 = tids.keys();
//      while (e2.hasMoreElements()) {
//        tid = (Byte) e2.nextElement();
//        found = false;
//        if (((ArrayList) tids.get(tid)).size() >  0) {
//          found = true;
//          break;
//        }
//      }
//      if (found == true) {
//        break;
//      }
//    }
    Object [] results = hasFailedBlockAckPackets();
    dst = (MacAddress) results[0];
    tid = (Byte) results[1];
    
    if (dst != null && tid != null && ((ArrayList)((Hashtable)blockAcksFailed.get(dst)).get(tid)).size() > 0) {
      short nextSeq = ((Short) ((ArrayList)((Hashtable)blockAcksFailed.get(dst)).get(tid)).remove(0)).shortValue();
      PacketSafer ps = (PacketSafer) ((Hashtable)((Hashtable)blockAckPackets.get(dst)).get(tid)).get(Short.valueOf(nextSeq));     
      sendDataUnicastFromCache(ps.packet, ps.packetNextHop, ps.anno, ps.packetPri, nextSeq);
    }
    // now new packets
    else if (priorityToAcknowlegment(packetPri) == AckPolicy.NORMAL_ACK) {
      sendDataUnicast(true);
    }
    else {
      sendDataUnicast(false);
    }
  }
  
  private Object[] hasFailedBlockAckPackets() {
    MacAddress dst = null;
    Byte tid = null;
    boolean found = false;
 // first send outstanding (failed) packets
    Enumeration e1 = blockAcksFailed.keys();
    while (e1.hasMoreElements()) {
      dst = (MacAddress) e1.nextElement();
      Hashtable tids = (Hashtable)blockAcksFailed.get(dst);
      Enumeration e2 = tids.keys();
      while (e2.hasMoreElements()) {
        tid = (Byte) e2.nextElement();
        found = false;
        if (((ArrayList) tids.get(tid)).size() >  0) {
          found = true;
          break;
        }
      }
      if (found == true) {
        break;
      }
    }
    if (found == true) {
      return new Object[] {dst, tid};
    } else {
      return new Object[] {null, null};
    }
  }
  
  protected void sendBlockAck() {
    if (log.isDebugEnabled()) {
      log.debug("sendBlockAck on " + this.getNode().getNodeId() + "." + myAC);
    }
    QueuedMessage queuedMessage;
    
//    if (blockAcksFailed.containsKey(blockAckRequester) && ((Hashtable)blockAcksFailed.get(blockAckRequester)).containsKey(Byte.valueOf(blockAckSendPri)) && ((Integer)((Hashtable)blockAcksFailed.get(blockAckRequester)).get(Byte.valueOf(blockAckSendPri))).intValue() >= 1) {
//      // take next unacknowledged packet
//      short nextSeq = ((Short) ((ArrayList)((Hashtable)blockAckTransmitted.get(blockAckRequester)).get(Byte.valueOf(blockAckSendPri))).get(((ArrayList)((Hashtable)blockAckTransmitted.get(blockAckRequester)).get(Byte.valueOf(blockAckSendPri))).size() - ((Integer)((Hashtable)blockAcksFailed.get(blockAckRequester)).get(Byte.valueOf(blockAckSendPri))).intValue())).shortValue();
//      PacketSafer ps = (PacketSafer) ((Hashtable)((Hashtable)blockAckPackets.get(blockAckRequester)).get(Byte.valueOf(blockAckSendPri))).get(Short.valueOf(nextSeq));
//      queuedMessage = new QueuedMessage(ps.packet, ps.packetNextHop, ps.anno);
//    } else {
//      // take next real packet
//      if (packet != null) {
//        queuedMessage = new QueuedMessage(packet, packetNextHop, anno);
//      } else {
//        queuedMessage = parent.getQueue().get(myAC);
    queuedMessage = getFromQueue();
//      }
//    }
    
    ArrayList seqs = (ArrayList)((Hashtable)blockAckReceived.get(blockAckRequester)).get(Byte.valueOf(blockAckSendPri));
    short barSeqControl = blockAckReqSeq;
    short [] blockAckBitmap = new short[64];
    // TID instead of AC
    short barControl = blockAckSendPri;
    
    for (int i = 0; i < 64; ++i) {
      if (seqs.contains(Short.valueOf((short)(blockAckReqSeq + i)))) {
        blockAckBitmap[i] = 1;
      } else {
        blockAckBitmap[i] = 0;
      }
    }
        
    // TODO bitrate HACK
    Mac802_11eMessage.QOS_BlockAckResp blockAck = msgFactory.createQOSBlockAckResp(
        blockAckRequester, localAddr, (int) (lastNav = calculateDuration(0, queuedMessage,
            false)), barControl, barSeqControl, blockAckBitmap);
    
    // Do bit-rate selection according to algorithm.
    adjustBitrates(blockAck, null, blockAckRequester, true, blockAck.getSize());

//    // create data packet
//    int duration = (int) (getSifs() + Constants.PROPAGATION + transmitTime(
//        msgFactory.getSize(Mac802_11eMessage.QOS_BlockAckResp.TYPE_ACK), controlRate));
    
//    Mac802_11eMessage.QOS_BlockAckResp blockAck = msgFactory.createQOSBlockAckResp(
//        blockAckRequester, localAddr, (int) (lastNav = calculateDuration(duration, queuedMessage,
//            false)), barControl, barSeqControl, blockAckBitmap);
    
    // set mode and transmit
    long delay = getRxTxTurnaround(); // waited txDifs in send()

    // Add PHY layer information to the MAC message
    PhyMessage phyMessage = new PhyMessage(blockAck, dataRate, phy);
    long phyDuration = phyMessage.txDuration();

    radioEntity.transmit(phyMessage, delay, phyDuration, anno);

    lastNav += JistAPI.getTime();

    // wait for EOT, and schedule ACK timer
    JistAPI.sleep(delay);
    setMode(MAC_MODE_XBACK);
    
    if (sendEvent.isActive())
      sendEvent.handle(blockAck, anno, delay + phyDuration
          + Constants.EPSILON_DELAY, dataRate, getRetry() > 0);
    
    JistAPI.sleep(phyDuration);
    
    blockAckReqReceived = false;
    
    // do backoff or not?
    if (wasLastPacketInTXOP()) {
      // reset everything and backoff
      resetTXOP();
      cfDone(true, false);
    } else {
      cfDone(false, false);
    }  
  }
  
  /**
   * Send a BlockAckReq and wait for an BlockAck.
   */
  protected void sendBlockAckRequest() {
    if (log.isDebugEnabled()) {
      log.debug("sendBlockAckRequest on " + this.getNode().getNodeId() + "." + myAC);
    }
    
    // disregard txops right here
//    QueuedMessage queuedMessage;
//    
//    if (blockAcksFailed.containsKey(blockAckReqReceiver) &&
//        ((Hashtable)blockAcksFailed.get(blockAckReqReceiver)).containsKey(Byte.valueOf(blockAckReqPri)) &&
//        ((Integer)((Hashtable)blockAcksFailed.get(blockAckReqReceiver)).get(Byte.valueOf(blockAckReqPri))).intValue() >= 1) {
//      // take next unacknowledged packet
//      short nextSeq = 
//        ((Short)(
//              (ArrayList)((Hashtable)blockAckTransmitted.get(blockAckReqReceiver)).get(Byte.valueOf(blockAckReqPri))).get(
//              ((ArrayList)((Hashtable)blockAckTransmitted.get(blockAckReqReceiver)).get(Byte.valueOf(blockAckReqPri))).size() 
//              - ((Integer)((Hashtable)blockAcksFailed.get(blockAckReqReceiver)).get(Byte.valueOf(blockAckReqPri))).intValue())
//           ).shortValue();
//      PacketSafer ps = (PacketSafer) ((Hashtable)((Hashtable)blockAckPackets.get(blockAckReqReceiver)).get(Byte.valueOf(blockAckReqPri))).get(Short.valueOf(nextSeq));
//      queuedMessage = new QueuedMessage(ps.packet, ps.packetNextHop, ps.anno);
//      
//    } else {
//      // take next real packet
//      if (packet != null) {
//        queuedMessage = new QueuedMessage(packet, packetNextHop, anno);
//      } else {
//        queuedMessage = parent.getQueue().get(myAC);
//      }
//    }

//  TODO bitrate HACK
    Mac802_11eMessage.QOS_BlockAckReq blockAckReqTEMP = msgFactory.createQOSBlockAckReq(
        blockAckReqReceiver, localAddr, 0, myAC, (short)0);
    
    // Do bit-rate selection according to algorithm.
    adjustBitrates(blockAckReqTEMP, null, blockAckReqReceiver, true, blockAckReqTEMP.getSize());
    
    int duration = (int) (getSifs() + Constants.PROPAGATION + transmitTime(Mac802_11eMessage.QOS_BlockAckResp.getHeaderSize() + 128 + 4, controlRate));
//        msgFactory.getSize(Mac802_11eMessage.QOS_BlockAckResp.WIFI_FC0_TYPE_CONTROL, Mac802_11eMessage.QOS_BlockAckResp.WIFI_FC0_SUBTYPE_BLOCKACK_RESP), controlRate));
    
    ArrayList seqs = (ArrayList)((Hashtable)blockAckTransmitted.get(blockAckReqReceiver)).get(Byte.valueOf(blockAckReqPri));
    short barSeqControl = ((Short) seqs.get(0)).shortValue();

    // use saved tid
    short barControl = blockAckReqPri;
       
    Mac802_11eMessage.QOS_BlockAckReq blockAckReq = msgFactory.createQOSBlockAckReq(
        blockAckReqReceiver, localAddr, (int) (lastNav = duration), barControl, barSeqControl);
    
    // set mode and transmit
    long delay = getRxTxTurnaround(); // waited txDifs in send()

    // Add PHY layer information to the MAC message
    PhyMessage phyMessage = new PhyMessage(blockAckReq, dataRate, phy);
    long phyDuration = phyMessage.txDuration();

    radioEntity.transmit(phyMessage, delay, phyDuration, anno);

    lastNav += JistAPI.getTime();

    // wait for EOT, and schedule ACK timer
    JistAPI.sleep(delay);
    setMode(MAC_MODE_XBACK);
    
    if (sendEvent.isActive())
      sendEvent.handle(blockAckReq, anno, delay + phyDuration
          + Constants.EPSILON_DELAY, dataRate, getRetry() > 0);
    
    JistAPI.sleep(phyDuration);
    
    self.startTimer(duration + getSlotTime(), MAC_MODE_SWFBA);
  }
  
  
  /**
   * Translate ip priority to Acknowledgment Policy.
   * pretty random.
   * Since a mesh is basically a QIBSS only priorities 0 - 7 are supported.
   * 802.11e-2005, p. 14, 6.1.1.2
   * 
   * @param priority
   *          ip message priority
   * @return AC
   */
  private AckPolicy priorityToAcknowlegment(final byte priority) {
//    return AckPolicy.BLOCK_ACK;
    switch (priority) {
    case Constants.NET_PRIORITY_D_BACKGROUND:
      return AckPolicy.NORMAL_ACK;
    case Constants.NET_PRIORITY_D_UNDEFINED:
      return AckPolicy.NO_ACK;
    case Constants.NET_PRIORITY_D_BESTEFFORT:
      return AckPolicy.NO_ACK;
    case Constants.NET_PRIORITY_D_EXCELLENTEFFORT:
      return AckPolicy.NORMAL_ACK;
    case Constants.NET_PRIORITY_D_CONTROLLEDLOAD:
      return AckPolicy.NORMAL_ACK;
    case Constants.NET_PRIORITY_D_VIDEO:
      return AckPolicy.NO_ACK;
    case Constants.NET_PRIORITY_D_VOICE:
      return AckPolicy.NO_ACK;
    case Constants.NET_PRIORITY_D_NETWORKCONTROL:
      return AckPolicy.NORMAL_ACK;
    default:
      return AckPolicy.NORMAL_ACK;
    }
  }

  protected void sendDataBroadcast() {
    if (log.isDebugEnabled()) {
      log.debug("sendDataBroadcast on " + this.getNode().getNodeId() + "."
          + myAC);
    }

    // Do bit-rate selection according to algorithm.
    adjustBitrates(packet, anno, packetNextHop, true, packet.getSize());

    QOSControl qosCtl = new QOSControl();
    // send broadcasts with no ack policy
    qosCtl.setAckPolicy(AckPolicy.NO_ACK);
    qosCtl.setTID(packetPri);
//    qosCtl.setQueueSize((short)parent.getQueue().size());
    qosCtl.setQueueSize((short)queue.size());

    Mac802_11eMessage data = msgFactory.createQOSDataBroadcast(packetNextHop,
        localAddr, 0, qosCtl.getQOSControl(), packet, anno);

    // set mode and transmit
    long delay = getRxTxTurnaround(); // waited txDifs in send()

    // Add PHY layer information to the MAC message
    PhyMessage phyMessage = new PhyMessage(data, dataRate, phy);
    long duration = phyMessage.txDuration();

    radioEntity.transmit(phyMessage, delay, duration, anno);

    // wait for EOT, check for outgoing packet
    JistAPI.sleep(delay);
    setMode(MAC_MODE_XBROADCAST);
    
    if (sendEvent.isActive())
      sendEvent.handle(data, anno, delay + duration + Constants.EPSILON_DELAY,
          dataRate, false);
    
    JistAPI.sleep(duration);

    setAnno(anno, MessageAnno.ANNO_MAC_SUCCESS, Boolean.TRUE);
    setAnno(anno, MessageAnno.ANNO_MAC_RETRIES, new Byte((byte) 0));

    // broadcasts in QIBSS are not subject to TXOPs
    // 802.11e-2005, p. 81, 9.9.1.2
    // reset everything and backoff
    resetTXOP();
    self.cfDone(true, true);
  }

  private class PacketSafer {
    private Message packet;
    private MacAddress packetNextHop;
    private MessageAnno anno;
    private byte packetPri;
    
    public PacketSafer(Message packet, MacAddress packetNextHop, MessageAnno anno, byte packetPri) {
      this.packet = packet;
      this.packetNextHop = packetNextHop;
      this.anno = anno;
      this.packetPri = packetPri;
    }

    /**
     * @return the anno
     */
    public MessageAnno getAnno() {
      return anno;
    }

    /**
     * @return the packet
     */
    public Message getPacket() {
      return packet;
    }

    /**
     * @return the packetNextHop
     */
    public MacAddress getPacketNextHop() {
      return packetNextHop;
    }

    /**
     * @return the packetPri
     */
    public byte getPacketPri() {
      return packetPri;
    }
  }
  
  
  protected void sendDataUnicast(boolean waitForAck) {
    if (log.isDebugEnabled()) {
      log
          .debug("sendDataUnicast on " + this.getNode().getNodeId() + "."
              + myAC);
    }
    if (radioMode > 0) {
      throw new RuntimeException("wrong radio mode while sending: " + radioMode);
    }

//    QueuedMessage queuedMessage = parent.getQueue().get(myAC);
    QueuedMessage queuedMessage = getFromQueue();

    // Do bit-rate selection according to algorithm.
    adjustBitrates(packet, anno, packetNextHop, true, packet.getSize());

    // create data packet
    int duration = (int) (getSifs() + Constants.PROPAGATION + (waitForAck ? transmitTime(
        msgFactory.getSize(MacDcfMessage.TYPE_ACK), controlRate) : 0));
    
    QOSControl qosCtl = new QOSControl();
    qosCtl.setAckPolicy(priorityToAcknowlegment(packetPri));
    qosCtl.setTID(packetPri);
//    qosCtl.setQueueSize((short)parent.getQueue().size());
    qosCtl.setQueueSize((short)queue.size());
    
    Mac802_11eMessage data = msgFactory.createQOSDataUnicast(packetNextHop,
        localAddr, (int) (lastNav = calculateDuration(duration, queuedMessage,
            false)), (getRetry() > 0) ? seq : incSeq(), getRetry() > 0,
        qosCtl.getQOSControl(), packet, anno);
    
    // block ack handling
    if (priorityToAcknowlegment(packetPri) == AckPolicy.BLOCK_ACK) {      
      if (!packetHasBeenSaved() && !packetHasBeenTransmitted()) {
        safeCurrentPacket();
      }
    }

    // set mode and transmit
    long delay = getRxTxTurnaround(); // waited txDifs in send()

    // Add PHY layer information to the MAC message
    PhyMessage phyMessage = new PhyMessage(data, dataRate, phy);
    long phyDuration = phyMessage.txDuration();
    if (null != anno)
        anno.put(MessageAnno.ANNO_MAC_BITRATE, Integer.valueOf(dataRate));

    radioEntity.transmit(phyMessage, delay, phyDuration, anno);

    lastNav += JistAPI.getTime();

    // wait for EOT, and schedule ACK timer
    JistAPI.sleep(delay);
    setMode(MAC_MODE_XUNICAST);
    
    if (sendEvent.isActive())
      sendEvent.handle(data, anno, delay + phyDuration
          + Constants.EPSILON_DELAY, dataRate, getRetry() > 0);
    
    JistAPI.sleep(phyDuration);

    if (waitForAck) {
      self.startTimer(duration + getSlotTime(), MAC_MODE_SWFACK);
    } else {
      resetRetry();
      // do backoff or not?
      if (wasLastPacketInTXOP()) {
        // reset everything and backoff
        resetTXOP();
        cfDone(true, true);
      } else {
        cfDone(false, true);
      }
    }
  }
  
  /**
   * Save the current packet for possible retransmission under BlockAck.
   * Sets flags for BlockAckReq, if queue max is reached.
   */
  private void safeCurrentPacket() {
    PacketSafer ps = new PacketSafer(packet, packetNextHop, anno, packetPri);
    ((Hashtable)((Hashtable)blockAckPackets.get(packetNextHop)).get(Byte.valueOf(packetPri))).put(Short.valueOf(seq), ps);
    ((ArrayList)((Hashtable)blockAckTransmitted.get(packetNextHop)).get(Byte.valueOf(packetPri))).add(Short.valueOf(seq));
    
    // request a blockAck if necessary
    if (((ArrayList)((Hashtable)blockAckTransmitted.get(packetNextHop)).get(Byte.valueOf(packetPri))).size() >= blockAckBufferSize) {
      blockAckReqToSend = true;
      blockAckReqReceiver = packetNextHop;
      blockAckReqPri = packetPri;
    }
  }
  
  /**
   * Has the current packet already been transmitted under BlockAck?
   * @return status
   */
  private boolean packetHasBeenTransmitted() {
    Hashtable tids = (Hashtable)blockAckTransmitted.get(packetNextHop);
    if (tids != null) {
      if (tids.containsKey(Byte.valueOf(packetPri))) {
        return ((ArrayList)tids.get(Byte.valueOf(packetPri))).contains(Short.valueOf(seq));
      }
    }
    return false;
  }
  
  /**
   * Has the current packet already been saved for retransmission under BlockAck?
   * @return status
   */
  private boolean packetHasBeenSaved() {
    Hashtable tids = (Hashtable)blockAckPackets.get(packetNextHop);
    if (tids != null) {
      if (tids.containsKey(Byte.valueOf(packetPri))) {
        return ((Hashtable)tids.get(Byte.valueOf(packetPri))).containsKey(Short.valueOf(seq));
      }
    }
    return false;
  }
  
  /**
   * If its not already there, this function
   * adds the currents packets destination (packetNextHop) to
   * the list of send packets (blockAckPackets), 
   * and to the list of seq# for transmitted BlockAcks (blockAckTransmitted)
   */
  private void setUpBlockAck() {
    if (!blockAckPackets.containsKey(packetNextHop)) {
      blockAckPackets.put(packetNextHop, new Hashtable());
      //blockAckPackets.put(packetNextHop, new Hashtable<Byte,new Hashtable<Short, PacketSafer>()>());
    }
    if (!((Hashtable)blockAckPackets.get(packetNextHop)).containsKey(Byte.valueOf(packetPri))) {
      ((Hashtable)blockAckPackets.get(packetNextHop)).put(Byte.valueOf(packetPri), new Hashtable());
    }
    
    if (!blockAckTransmitted.containsKey(packetNextHop)) {
      blockAckTransmitted.put(packetNextHop, new Hashtable());
      //blockAckTransmitted.put(packetNextHop, new ArrayList<Short>());
    }
    if (!((Hashtable)blockAckTransmitted.get(packetNextHop)).containsKey(Byte.valueOf(packetPri))) {
      ((Hashtable)blockAckTransmitted.get(packetNextHop)).put(Byte.valueOf(packetPri), new ArrayList());
    }
    
    if (!blockAcksFailed.containsKey(packetNextHop)) {
      blockAcksFailed.put(packetNextHop, new Hashtable());
    }
    if (!((Hashtable)blockAcksFailed.get(packetNextHop)).containsKey(Byte.valueOf(packetPri))) {
      ((Hashtable)blockAcksFailed.get(packetNextHop)).put(Byte.valueOf(packetPri), new ArrayList());
    }
  }
  
  /**
   * Resend an already transmitted but still unacknowledged packet.
   * @param packet
   * @param packetNextHop
   * @param anno
   * @param packetPri
   * @param seq
   */
  protected void sendDataUnicastFromCache(Message packet, MacAddress packetNextHop, MessageAnno anno, byte packetPri, short seq) {
    if (log.isDebugEnabled()) {
      log
          .debug("sendDataUnicastFromCache on " + this.getNode().getNodeId() + "."
              + myAC);
    }
    QueuedMessage queuedMessage;    
    
    // TODO what if this.packet == null? are we called in that case? is a check for blockAcksFailed.size > 0 necessary?
    
    if (((ArrayList)((Hashtable)blockAcksFailed.get(packetNextHop)).get(Byte.valueOf(packetPri))).size() == 0) {
      // take next real packet
      queuedMessage = new QueuedMessage(this.packet, this.packetNextHop, this.anno);
    } else {
      // take next unacknowledged packet
      short nextSeq = ((Short)((ArrayList)((Hashtable)blockAcksFailed.get(packetNextHop)).get(Byte.valueOf(packetPri))).get(0)).shortValue();
      PacketSafer ps = (PacketSafer) ((Hashtable)((Hashtable)blockAckPackets.get(packetNextHop)).get(Byte.valueOf(packetPri))).get(Short.valueOf(nextSeq));
      queuedMessage = new QueuedMessage(ps.packet, ps.packetNextHop, ps.anno);
    }

    // Do bit-rate selection according to algorithm.
    adjustBitrates(packet, anno, packetNextHop, true, packet.getSize());

    // create data packet
    int duration = (int) (getSifs() + Constants.PROPAGATION + transmitTime(
        msgFactory.getSize(MacDcfMessage.TYPE_ACK), controlRate));
    
    QOSControl qosCtl = new QOSControl();
    qosCtl.setAckPolicy(priorityToAcknowlegment(packetPri));//waitForAck ? QOSControl.AckPolicy.NORMAL_ACK : QOSControl.AckPolicy.NO_ACK);
    qosCtl.setTID(packetPri);
//    qosCtl.setQueueSize((short)parent.getQueue().size());
    qosCtl.setQueueSize((short)queue.size());
    
    // MacDcfMessage data = msgFactory.createDataUnicast(packetNextHop,
    // localAddr,
    // (int) (lastNav = calculateDuration(duration, queuedMessage, false)),
    // (getRetry() > 0) ? seq : incSeq(), getRetry() > 0, packet, anno);
    Mac802_11eMessage data = msgFactory.createQOSDataUnicast(packetNextHop,
        localAddr, (int) (lastNav = calculateDuration(duration, queuedMessage,
            false)), seq, getRetry() > 0,
        qosCtl.getQOSControl(), packet, anno);
    
    // block ack handling not neccessary here
//    if (priorityToAcknowlegment(packetPri) == QOSControl.AckPolicy.BLOCK_ACK) {
//      if (!blockAckPackets.containsKey(seq) && !blockAckTransmitted.contains(seq)) {
//        PacketSafer ps = new PacketSafer(packet, packetNextHop, anno, packetPri);
//        blockAckPackets.put(seq, ps);
//        blockAckTransmitted.add(seq);
//      }
//    }

    // set mode and transmit
    long delay = getRxTxTurnaround(); // waited txDifs in send()

    // Add PHY layer information to the MAC message
    PhyMessage phyMessage = new PhyMessage(data, dataRate, phy);
    long phyDuration = phyMessage.txDuration();

    radioEntity.transmit(phyMessage, delay, phyDuration, anno);

    lastNav += JistAPI.getTime();

    // wait for EOT, and schedule ACK timer
    JistAPI.sleep(delay);
    setMode(MAC_MODE_XUNICAST);
    
    if (sendEvent.isActive())
      sendEvent.handle(data, anno, delay + phyDuration
          + Constants.EPSILON_DELAY, dataRate, getRetry() > 0);
    
    JistAPI.sleep(phyDuration);
    
    // do backoff or not?
    if (wasLastPacketInTXOP()) {
      // reset everything and backoff
      resetTXOP();
      cfDone(true, false);
    } else {
      cfDone(false, false);
    }  
  }

  protected void sendAck() {
    if (log.isDebugEnabled()) {
      log.debug("sendAck on " + this.getNode().getNodeId() + "." + myAC);
    }
//    QueuedMessage queuedMessage = parent.getQueue().get(myAC);
    QueuedMessage queuedMessage = getFromQueue();
//    if (!hasTXOP() || queuedMessage == null) {
//      super.sendAck();
//      return;
//    }
    // Do bit-rate selection according to algorithm.
    adjustBitrates(dataRcvd, dataRcvdAnno, dataRcvd.getSrc(), false, msgFactory
        .getSize(MacDcfMessage.TYPE_ACK));

    // create ack
    MacDcfMessage.Ack ack = msgFactory.createAck(dataRcvd.getSrc(),
        /*(int) (lastNav = calculateDuration(0, queuedMessage, false))*/0, dataRcvd,
        dataRcvdAnno);
    // not needed anymore
    dataRcvd = null;
    dataRcvdAnno = null;

    // Add PHY layer information to the MAC message
    // Use control rate for now; but could use higher (=data) rate in unicast
    // scenario!
    PhyMessage phyMessage = new PhyMessage(ack, controlRate, phy);
    long delay = getRxTxTurnaround(); // already waited txSifs
    long duration = phyMessage.txDuration();

    if (sendEvent.isActive())
      sendEvent.handle(ack, anno, delay + duration + Constants.EPSILON_DELAY,
          controlRate, false);

    radioEntity.transmit(phyMessage, delay, duration, anno);

    lastNav += JistAPI.getTime();

    // wait for EOT, check for outgoing packet
    JistAPI.sleep(delay);
    setMode(MAC_MODE_XACK);
    JistAPI.sleep(duration);

    self.cfDone(false, false);
  }

  protected void sendRts() {
    if (log.isDebugEnabled()) {
      log.debug("sendRts on " + this.getNode().getNodeId() + "." + myAC);
    }
//    QueuedMessage queuedMessage = parent.getQueue().get(myAC);
    QueuedMessage queuedMessage = getFromQueue();
//    if (!hasTXOP() || queuedMessage == null) {
//      super.sendRts();
//      return;
//    }

    // Do bit-rate selection according to algorithm.
    adjustBitrates(packet, anno, packetNextHop, true, msgFactory.getSize(
        Mac802_11Message.TYPE_RTS, this.packet, this.anno));

    // create rts packet
    long ctsTime = getSifs()
        + transmitTime(msgFactory.getSize(Mac802_11Message.TYPE_CTS,
            this.packet, this.anno), controlRate) + Constants.PROPAGATION;
    long dataTime = getSifs()
        + transmitTime(msgFactory.getSize(Mac802_11Message.TYPE_DATA,
            this.packet, this.anno)
            + packet.getSize(), dataRate) + Constants.PROPAGATION;
    long ackTime = getSifs()
        + transmitTime(msgFactory.getSize(Mac802_11Message.TYPE_ACK),
            controlRate) + Constants.PROPAGATION;
    long duration = ctsTime + dataTime + ackTime;

    Mac802_11Message.Rts rts = msgFactory.createRts(packetNextHop, localAddr,
        (int) (lastNav = calculateDuration(duration, queuedMessage, false)),
        packet, anno);
    // set mode and transmit
    long delay = getRxTxTurnaround();

    // Add PHY layer information to the MAC message
    PhyMessage phyMessage = new PhyMessage(rts, controlRate, phy);
    long phyDuration = phyMessage.txDuration();

    radioEntity.transmit(phyMessage, delay, phyDuration, anno);

    lastNav += JistAPI.getTime();

    // wait for EOT, schedule CTS wait timer
    JistAPI.sleep(delay);
    setMode(MAC_MODE_XRTS);
    
    if (sendEvent.isActive())
      sendEvent.handle(rts, anno, delay + duration + Constants.EPSILON_DELAY,
          controlRate, false);
    
    JistAPI.sleep(phyDuration);
    // plus extra slot time to cover slightly larger delays
    self.startTimer(ctsTime + getSlotTime() + Constants.EPSILON_DELAY,
        MAC_MODE_SWFCTS);
  }

  /**
   * Override to check whether to send Ack.
   */
  protected void endReceiveData(Message msg) {
    if (msg instanceof Mac802_11eMessage.QOS_Data) {
      Mac802_11eMessage.QOS_Data qMsg = (Mac802_11eMessage.QOS_Data) msg;
      QOSControl qosCtl = new QOSControl();
      qosCtl.setQOSControl(qMsg.getQoSControl());
      AckPolicy ackPol = qosCtl.getAckPolicy();
      
      if (ackPol == AckPolicy.NORMAL_ACK) {
        super.endReceiveData(msg);
        return;
      }
      if (ackPol == AckPolicy.NO_ACK) {
        doCfDone(false);
        return;
      }
      if (ackPol == AckPolicy.NO_EXPLICIT_ACK) {
        doCfDone(false);
        return;
      }
      if (ackPol == AckPolicy.BLOCK_ACK) {
        short seq = qMsg.getSeq();
        MacAddress src = qMsg.getSrc();
        short tid = qosCtl.getTid();
        
        addSeqToRecievedBlockAcks(src, seq, tid);
        
        doCfDone(false);
        return;
      }
      
    } else {
      super.endReceiveData(msg);
    }
  }
  
  /**
   * Add the seq# of the recieved packet to the list of to 
   * acknowledge-able packets for the given source MacAddress (blockAckReceived).
   * @param src MacAddress of the sender
   * @param seq seq# of the packet
   */
  private void addSeqToRecievedBlockAcks(MacAddress src, short seq, short tid) {
    setUpBlockAcksRecieved(src, tid);
    
    ArrayList seqs = (ArrayList) ((Hashtable)blockAckReceived.get(src)).get(Byte.valueOf((byte) tid));
    if (!seqs.contains(Short.valueOf(seq))) {
      seqs.add(Short.valueOf(seq));
    }
  }
  
  /**
   * If the source is not already present in the list of recieved BlockAcks (blockAckReceived),
   * it will be added.
   * @param src MacAddress of the sender
   */
  private void setUpBlockAcksRecieved(MacAddress src, short tid) {
    if (!blockAckReceived.containsKey(src)) {
      blockAckReceived.put(src, new Hashtable());
    }
    if (!((Hashtable)blockAckReceived.get(src)).containsKey(Byte.valueOf((byte)tid))) {
      ((Hashtable)blockAckReceived.get(src)).put(Byte.valueOf((byte)tid), new ArrayList());
    }
  }
  
//MacInterface
  public void peek(Message msg, MessageAnno anno) {
      if (Main.ASSERT && null != msg)
          // If the following assert fires, try to use setUseAnnotations().
          // Hint: ClickRouter uses annos, so setUseAnnotations(true) on your
          // radio and mac during bootstrap
          Util.assertion(useAnnotations ? (null != anno) : (null == anno));
      
      // set needEifs = true at beginning of every packet reception; if reception
  // successful, needEifs is set to false in receivePacket()
//    needEifs = true;

      if (receiveStartEvent.isActive()) {
          // log the start of the TX
          MacDcfMessage macMsg = (MacDcfMessage) ((PhyMessage) msg)
                  .getPayload();
          if (macMsg.getType() == MacDcfMessage.TYPE_DATA) {
              receiveStartEvent.handle(msg, anno);
          }
      }
  }
  
  protected void receivePacket(MacDcfMessage msg, MessageAnno anno) {
    needEifs = false;
    MacAddress dst = msg.getDst();

    // just handle BlockAcks, otherwise delegat to base
    if (localAddr.equals(dst) && msg.getType() == MacDcfMessage.TYPE_ACK) {
      if (msg instanceof Mac802_11eMessage.QOS_BlockAckResp) {
        Mac802_11eMessage.QOS_BlockAckResp qMsg = (Mac802_11eMessage.QOS_BlockAckResp) msg;
        receiveBlockAck(qMsg, anno);
      } else if (msg instanceof Mac802_11eMessage.QOS_BlockAckReq) {
        Mac802_11eMessage.QOS_BlockAckReq qMsg = (Mac802_11eMessage.QOS_BlockAckReq) msg;
        receiveBlockAckReq(qMsg, anno);
      } else {
        super.receivePacket(msg, anno);
      }
    } else {
      super.receivePacket(msg, anno);
    }
  }
  
  protected void receiveBlockAckReq(Mac802_11eMessage.QOS_BlockAckReq qMsg, MessageAnno anno) {
    if (log.isDebugEnabled()) {
      log.debug("receiveBlockAckReq on " + this.getNode().getNodeId() + "." + myAC);
    }
    
    if (isAwaitingResponse())
        return;
    
    blockAckReqReceived = true;
    blockAckRequester = qMsg.getSrc();
    blockAckSendPri = (byte) qMsg.getBARControl();
    blockAckReqSeq = qMsg.getBARSeqControl();
    
    startTimer(getTxSifs(), MAC_MODE_SIFS);
  }
  
  protected void receiveBlockAck(Mac802_11eMessage.QOS_BlockAckResp blockAck, MessageAnno anno) {
    if (log.isDebugEnabled()) {
      log.debug("recieveBlockAck on " + this.getNode().getNodeId() + "." + myAC);
    }
    // wrong mode
    if (mode != MAC_MODE_SWFBA) {
      log.error("didnt get expected BlockAckResponse after Request");
      throw new RuntimeException();
    }

//      // DATA successful:
//      rca.reportPacketTx(packetNextHop, MacDcfMessage.TYPE_DATA, packet
//          .getSize(), controlRate, getRetry() + 1,
//          Constants.RADIO_RECV_STATUS_OK.byteValue());

      cancelTimer();

      if (receiveEvent.isActive())
        receiveEvent.handle(blockAck, anno, null == anno ? -1 : ((Long) anno
            .get(MessageAnno.ANNO_MAC_DURATION)).longValue()
            + getRxTxTurnaround());

      setAnno(this.anno, MessageAnno.ANNO_MAC_SUCCESS, Boolean.TRUE);
      setAnno(this.anno, MessageAnno.ANNO_MAC_RETRIES, new Byte(getRetry()));
      
      if (macTxFinished.isActive())
        macTxFinished.handle(packet, anno, true, getRetry());

      resetRetry();
      decCW();
      
      MacAddress sender = blockAck.getSrc();
      
      if (sender == blockAckReqReceiver) {
        blockAckReqToSend = false;
      }
      short startSeq = blockAck.getBARSeqControl();
      short [] bitmap = blockAck.getBlockAckBitmap();
      byte tid = (byte) blockAck.barControl;
      if (blockAckTransmitted.containsKey(sender) && ((Hashtable)blockAckTransmitted.get(sender)).containsKey(Byte.valueOf(tid))) {
        
        // TODO assumes no fragmentation
        for (int i = 0; i < bitmap.length; ++i) {
          if (((ArrayList)((Hashtable)blockAckTransmitted.get(sender)).get(Byte.valueOf(tid))).contains(Short.valueOf((short)(startSeq + i)))) {
            if (bitmap[i] == 1) {          
              ((Hashtable)((Hashtable)blockAckPackets.get(sender)).get(Byte.valueOf(tid))).remove(Short.valueOf((short)(startSeq + i)));
              ((ArrayList)((Hashtable)blockAckTransmitted.get(sender)).get(Byte.valueOf(tid))).remove(((ArrayList)((Hashtable)blockAckTransmitted.get(sender)).get(Byte.valueOf(tid))).indexOf(Short.valueOf((short)(startSeq + i))));
            } else {
              ((ArrayList)((Hashtable)blockAcksFailed.get(sender)).get(Byte.valueOf(tid))).add(Short.valueOf((short)(startSeq + i)));
            }
          }
          // dont do an else here sender has to handle shit thrown at him gracefully
//          else {
//            if (bitmap[i] == 1) {
//              log.fatal("unexpected seq# from: " + sender.getId() + "  with tid: " + tid + "  startseq: " + startSeq + " + " + i + " on "+ this.getNode().getNodeId() + "." + myAC);
//              throw new RuntimeException();
//            }
//          }
        }
        
      }
      // do cfDone with delPacket set to false
      doCfDone(false);
//    }
  }
  
  /*
   * (non-Javadoc)
   * 
   * @see jist.swans.mac.MacDcf#isAwaitingResponse()
   */
  protected boolean isAwaitingResponse() {
      switch (mode) {
          case MAC_MODE_SWFBA:
              return true;
          default:
              return super.isAwaitingResponse();
      }
  }
  
  /*
     * (non-Javadoc)
     * 
     * @see jist.swans.mac.MacDcf#isTransmitting()
     */
    protected boolean isTransmitting() {
        switch (mode) {
            case MAC_MODE_XBACK:
                return true;
            default:
                return super.isTransmitting();
        }
    }
    
  protected void receiveAck(MacDcfMessage.Ack ack, MessageAnno anno) {
    if (log.isDebugEnabled()) {
      log.debug("recieveAck on " + this.getNode().getNodeId() + "." + myAC);
    }
    if (mode == MAC_MODE_SWFACK) {

      // DATA successful:
      rca.reportPacketTx(packetNextHop, MacDcfMessage.TYPE_DATA, packet
          .getSize(), controlRate, getRetry() + 1, 0,
          Constants.RADIO_RECV_STATUS_OK.byteValue());

      cancelTimer();

      if (receiveEvent.isActive())
        receiveEvent.handle(ack, anno, null == anno ? -1 : ((Long) anno
            .get(MessageAnno.ANNO_MAC_DURATION)).longValue()
            + getRxTxTurnaround());

      setAnno(this.anno, MessageAnno.ANNO_MAC_SUCCESS, Boolean.TRUE);
      setAnno(this.anno, MessageAnno.ANNO_MAC_RETRIES, new Byte(getRetry()));
      if (macTxFinished.isActive())
        macTxFinished.handle(packet, anno, true, getRetry());

      resetRetry();
      decCW();

      // do cfDone with delPacket set to true
      doCfDone(true);
    }
  }
  
  /**
   * Decide whether to do a backoff in cfDone depending on TXOP state.
   * 
   * @param delPacket whether to delete the current packet
   */
  protected void doCfDone(boolean delPacket) {
//  do backoff or not?
    if (wasLastPacketInTXOP()) {
      // reset everything and backoff
      resetTXOP();
      cfDone(true, delPacket);
    } else {
      cfDone(false, delPacket);
    }
  }

  // ////////////////////////////////////////////////
  // retry
  //
  // TODO does this prevent setting the retry header bits? 802.11e p. 72 d)
  protected void retry(boolean modifyAnno) {
    if (log.isDebugEnabled()) {
      log.debug("retry on " + this.getNode().getNodeId() + "." + myAC);
    }
    if (modifyAnno || shortRetry < macInfo.getRetryLimitShort()) {
      retry();
    } else {
      // use long retry count for frames larger than RTS_THRESHOLD
      if (shouldRTS() && mode != MAC_MODE_SWFCTS) {
        if (longRetry < macInfo.getRetryLimitLong()) {
          if (retryEvent.isActive())
            retryEvent.handle(this.packet, this.anno, longRetry, false);
          longRetry++;
          retryYes();
        } else {
          setAnno(anno, MessageAnno.ANNO_MAC_RETRIES, new Byte(longRetry));
          if (macTxFinished.isActive())
            macTxFinished.handle(packet, anno, false, longRetry);
          longRetry = 0;
          retryNo();
        }
      } else {
        if (shortRetry < macInfo.getRetryLimitShort()) {
          if (retryEvent.isActive())
            retryEvent.handle(this.packet, this.anno, shortRetry, true);
          shortRetry++;
          retryYes();
        } else {
          setAnno(anno, MessageAnno.ANNO_MAC_RETRIES, new Byte(
              (byte) shortRetry));
          if (macTxFinished.isActive())
            macTxFinished.handle(packet, anno, false, shortRetry);
          resetRetry();
          retryNo();
        }

        // setAnno(anno, MessageAnno.ANNO_MAC_RETRIES, new Byte((byte)
        // shortRetry));
        // if (macTxFinished.isActive())
        // macTxFinished.handle(packet, anno, false, shortRetry);
        // resetRetry();
        // retryNo();
      }
    }
  }

  protected void retryYes() {
    if (log.isDebugEnabled()) {
      log.debug("retryYes on " + this.getNode().getNodeId() + "." + myAC);
    }
    // if (!fitsInTXOP()) {
    resetTXOP();
    super.retryYes();
    // } else {
    // doDifs();
    // }
  }

  public String getModeString(byte mode) {
    switch (mode) {
    case MAC_MODE_AIFS:
      return "AIFS";
    default:
      return super.getModeString(mode);
    }
  }

  protected void radioBusy() {
    if (log.isDebugEnabled()) {
      log.debug("radioBusy() on " + this.getNode().getNodeId() + "." + myAC);
    }
    switch (mode) {
    case MAC_MODE_AIFS:
      mode = MAC_MODE_DIFS;
      super.radioBusy();
      break;
    case MAC_MODE_SIFS:
      if (hasTXOP()) {
        resetTXOP();
        idle();
        break;
      }
      super.radioBusy();
      break;
    case MAC_MODE_SWFBA:
      // don't care
      break;
    default:
      super.radioBusy();
    }
  }

  protected void radioIdle() {
    if (log.isDebugEnabled()) {
      log.debug("radioIdle() on " + this.getNode().getNodeId() + "." + myAC);
    }
    switch (mode) {
    case MAC_MODE_SIDLE:
    case MAC_MODE_SNAV:
      if (!parent.isIdle()) {
        break;
      }
      super.radioIdle();
    case MAC_MODE_SWFBA:
        // don't care
        break;
    default:
      super.radioIdle();
    }
  }

  /**
   * overriden just for the eventhandler.
   */
  public void setRadioMode(byte mode) {
    if (log.isDebugEnabled()) {
      log.debug("now radiomode is set to " + mode + " at: "
          + this.getNode().getNodeId() + "." + myAC);
    }
    if (this.radioMode != mode && radioModeChanged.isActive())
      radioModeChanged.handle(this.radioMode, mode, myAC);

    super.setRadioMode(mode);

    // idle, if internal nav not set
    if (mode == Constants.RADIO_MODE_TRANSMITTING && !isTransmitting()) {
      if (this.mode != MAC_MODE_SNAV) {
        radioBusy();
      }
    }
  }

  protected void updateNav(MacDcfMessage msg, MessageAnno anno) {
    if (log.isDebugEnabled()) {
      log.debug("updateNav at: " + this.getNode().getNodeId() + "." + myAC);
    }
    if (parent.getTXOPAC() == myAC) {
      parent.setTXOPAC(TXOP_AC_NONE);
    }
    super.updateNav(msg, anno);
  }

  /** end overridden functions */

  /** additional functions */

  /**
   * Has this EDCF instance a TXOP.
   * 
   * @return TXOP status
   */
  private boolean hasTXOP() {
    if (log.isDebugEnabled()) {
      log.debug("hasTXOP at: " + this.getNode().getNodeId() + "." + myAC);
    }
    return txop > JistAPI.getTime() && parent.getTXOPAC() == myAC;
  }
  
  //DUMMY
  public void resetTXOPEvent() {
    if (log.isDebugEnabled()) {
      log.debug("resetTXOP at: " + this.getNode().getNodeId() + "." + myAC);
    }
    txop = -1;
    if (parent.getTXOPAC() == myAC) {
      parent.setTXOPAC(TXOP_AC_NONE);
    }
  }
  
  /**
   * Insert a new message into the Queue.
   * @param msg new Message to queue
   * @return false if queue is full
   */
  public boolean insertIntoQueue(QueuedMessage msg) {
    if (queue.isFull()) {
      return false;
    }
    if (enqueueEvent != null && enqueueEvent.isActive()) {
      enqueueEvent.handle(msg, anno, msg.getNextHop(), queue);
    }
    queue.insert(msg, 0);
    return true;
  }
  
  /**
   * Checks whether queue is full.
   * @return true if full
   */
  public boolean isQueueFull() {
    return queue.isFull();
  }
  
  /**
   * Checks whether queue is empty.
   * @return true if empty
   */
  public boolean isQueueEmpty() {
    return queue.isEmpty();
  }
  
  public QueuedMessage removeFromQueue() {
    if (queue.isEmpty()) {
      return null;
    }
    QueuedMessage qmsg = queue.remove();
    if (dequeueEvent != null && dequeueEvent.isActive()) {
      dequeueEvent.handle(qmsg, qmsg.getAnno(), qmsg.getNextHop(), queue);
    }
    return qmsg;
  }
  
  public QueuedMessage getFromQueue() {
    if (!queue.isEmpty()) {
      return queue.get();
    }
    return null;
  }

  /**
   * Resets the TXOP.
   * 
   */
  private void resetTXOP() {
    self.resetTXOPEvent();
//    if (log.isDebugEnabled()) {
//      log.debug("resetTXOP at: " + this.getNode().getNodeId() + "." + myAC);
//    }
    txop = -1;
//    if (parent.getTXOPAC() == myAC) {
//      parent.setTXOPAC(TXOP_AC_NONE);
//    }
  }

  /**
   * Does the current packet fit in current TXOP.
   * 
   * @return whether to still try to squeeze the current packet into the txop
   */
  private boolean fitsInTXOP() {
    if (log.isDebugEnabled()) {
      log.debug("fitsInTXOP() at: " + this.getNode().getNodeId() + "." + myAC);
    }
    if (!hasTXOP() || packet == null) {
      return false;
    }
    return fitsInTXOP(packet, packetNextHop);
  }

  /**
   * Does the given packet fit in current TXOP.
   * 
   * @param msg
   *          Message to check
   * @param nextHop
   *          destination
   * @return whether to still try to squeeze the given packet into the txop
   */
  private boolean fitsInTXOP(Message msg, MacAddress nextHop) {
    if (log.isDebugEnabled()) {
      log.debug("fitsInTXOP(msg, nextHop) at: " + this.getNode().getNodeId()
          + "." + myAC);
    }
    if (isBroadcast(nextHop)) {
      return false;
    }
    // TODO DEBUG
    return hasTXOP() && JistAPI.getTime() + getDuration(msg, nextHop) <= txop;
  }

  /**
   * Whether all packet supposed to be send inside a TXOP were send
   * successfully.
   * 
   * @return TXOP status
   */
  private boolean wasLastPacketInTXOP() {
    if (log.isDebugEnabled()) {
      log.debug("wasLastPacketInTXOP() at: " + this.getNode().getNodeId() + "."
          + myAC);
    }

//    QueuedMessage qMsg = parent.getQueue().get(myAC);
    QueuedMessage qMsg = getFromQueue();

    // exclude every unsane case
    if (TXOP_LIMIT == 0 || !hasTXOP() || txop <= JistAPI.getTime()
        || qMsg == null) {
      return true;
    }

    Message msg = qMsg.getPayload();
    MacAddress nextHop = qMsg.getNextHop();

    if (!fitsInTXOP(msg, nextHop)) {
      return true;
    }

    if (log.isInfoEnabled()) {
      log
          .info("Was Last Packet at: " + this.getNode().getNodeId() + "."
              + myAC);
    }
    return false;
  }

  /**
   * Check the TimeOut times of concurrent EDCFs to recognize internal
   * collisions.
   * 
   */
  private boolean isInternalCollision() {
    if (log.isDebugEnabled()) {
      log.debug("isInternalCollision() at: " + this.getNode().getNodeId() + "."
          + myAC);
    }
    // check for marked collisions
    if (collisionFlag == true) {
      collisionFlag = false;
      return true;
    }
    short acWin = parent.internalCollision();

    if (acWin == myAC) {
      return false;
    }

    if (acWin == TXOP_AC_NONE) {
      return false;
    }

    if (log.isInfoEnabled()) {
      log.info("Internal Collision() at: " + this.getNode().getNodeId() + "."
          + myAC);
    }
    return true;
  }

  /**
   * Update/Set the NAV due to an other EDCF won access to the medium. Or a
   * recieved packet in an other EDCF requires it.
   * 
   * @param nav
   *          new NAV
   */
  public void internalNav(long theNav) {
    if (log.isDebugEnabled()) {
      log.debug("internalNav(nav) at: " + this.getNode().getNodeId() + "."
          + myAC);
    }
    // set initiator to localAddr
    MacAddress initiator = localAddr;
    boolean changed = nav.setNav(theNav, initiator);

    if (!isTransmitting() && !isAwaitingResponse() && isRadioIdle()) {
      if (mode == MAC_MODE_SBO && boStart > 0) {
        pauseBackoff();
      }

      // this method needs to be unblocking
      // so it can be called from the parent for all concurrent edcfs at once
      // but that opens the door to race conditions in the eventhandling
      // so we just disable the current pending event
      cancelTimer();
      self.startTimer(nav.getNav(), MAC_MODE_SNAV);
    }
  }

  /**
   * Calculate the duration for the nav, when as much packets as possible would
   * be send during the current TXOP.
   * 
   * @param duration
   *          calculated duration without the packet in queuedMessage
   * @param queuedMessage
   *          the next packet to be send
   * 
   * @return expected nav
   */
  public long calculateDuration(long duration, QueuedMessage queuedMessage,
      boolean burst) {
    if (log.isDebugEnabled()) {
      log.debug("calculateDuration at: " + this.getNode().getNodeId() + "."
          + myAC);
    }
    
    // broadcast alwasy require backoff, so stop before it
    if (isBroadcast(packetNextHop)) {
      return duration;
    }
    
    long currentPacketDuration = 0;
    if (packet != null) {
      currentPacketDuration = getDuration(packet, packetNextHop);
    }
    if (queuedMessage != null) {
      return duration
        + calculateDuration(currentPacketDuration, queuedMessage, 1, burst)
        - currentPacketDuration;
    } else {
      return duration;
    }
    
  }

  /**
   * Calculate the duration for the nav, when as much packets as possible would
   * be send during the current TXOP.
   * 
   * @param duration
   *          calculated duration without the packet in queuedMessage
   * @param queuedMessage
   *          the next packet to be send
   * @param count
   *          recursion counter
   * 
   * @return expected nav
   */
  public long calculateDuration(long duration, QueuedMessage queuedMessage,
      int count, boolean burst) {
    // calculate the duration in the queuedMessage
    Message msg = queuedMessage.getPayload();
    MacAddress nextHop = queuedMessage.getNextHop();
    
    // broadcast alwasy require backoff, so stop before it
    if (isBroadcast(nextHop)) {
      return duration;
    }

    long nextPacketDuration = getDuration(msg, nextHop);

    // next packet exceeds the TXOP?
    if (duration + JistAPI.getTime() + nextPacketDuration > txop) {
      return duration;
    } else {
      // increase duration for one packet
      duration += nextPacketDuration;

      // dequeue next possible packet
      QueuedMessage qMsg = queuedMessage.next;

      // there is no other packet queued? or ackpolicy doesn't allow burst
      if (qMsg == null || burst == false) {
        return duration;
      } else {
        // only when bursting
        return calculateDuration(duration, qMsg, ++count, burst);
      }
    }
  }

  /**
   * Get the transmission duration of the message on the medium. includes sifs
   * before every frame
   * 
   * @param msg
   *          the message containing the data
   * @param nextHop
   *          receiving address
   * 
   * @return the expected transmission duration (nav)
   */
  private long getDuration(Message msg, MacAddress nextHop) {
    if (log.isDebugEnabled()) {
      log.debug("getDuration at: " + this.getNode().getNodeId() + "." + myAC);
    }

    long duration = 0;

    if (isBroadcast(nextHop)) {
      // should never get this far, since broadcasts are not in TXOPs
      throw new RuntimeException("broadcast duration not implemented yet");
    }

    if (shouldRTS(msg)) {
      // add RTS and CTS
      // create rts packet
      long rtsTime = getSifs()
          + transmitTime(msgFactory.getSize(Mac802_11eMessage.TYPE_RTS),
              controlRate) + Constants.PROPAGATION;
      long ctsTime = getSifs()
          + transmitTime(msgFactory.getSize(Mac802_11eMessage.TYPE_CTS),
              controlRate) + Constants.PROPAGATION;

      duration += rtsTime + ctsTime;
    }

    // data packet
    long dataTime = getSifs()
        + transmitTime(msgFactory.getSize(Mac802_11eMessage.TYPE_DATA)
            + msg.getSize(), dataRate) + Constants.PROPAGATION;
    
    long ackTime = 0;
    
    // check Ack Policy
    if (msg instanceof Mac802_11eMessage.QOS_Data) {
      Mac802_11eMessage.QOS_Data qMsg = (Mac802_11eMessage.QOS_Data) msg;
      QOSControl qCtl = new QOSControl();
      qCtl.setQOSControl(qMsg.getQoSControl());
      AckPolicy ack = qCtl.getAckPolicy();
      
      if (ack == AckPolicy.NORMAL_ACK) {
        ackTime = getSifs()
          + transmitTime(msgFactory.getSize(Mac802_11eMessage.TYPE_ACK),
              controlRate) + Constants.PROPAGATION;
      }
    } else {
      ackTime = getSifs()
      + transmitTime(msgFactory.getSize(Mac802_11eMessage.TYPE_ACK),
          controlRate) + Constants.PROPAGATION;
    }
    
    duration += dataTime + ackTime;

    // // time to wire
    // duration += getRxTxTurnaround();
    //
    // // time on the wire
    // PhyMessage phyMessage = new PhyMessage(msg, dataRate, phy);
    // duration += phyMessage.txDuration();
    //
    // // time for following ack
    // duration += getSifs() + Constants.PROPAGATION +
    // transmitTime(msgFactory.getSize(MacDcfMessage.TYPE_ACK), controlRate);

    return duration;
  }

  /** end additional function */

  public void continueSend(int ac) {
    log.fatal("this is not supposed to be called!");
    // throw new Exception("this is not supposed to be called!");
  }

  //@Override
  public boolean isQueueFull(short ac) {
    // TODO Auto-generated method stub
    return false;
  };
}
