package brn.swans.mac;

import jist.swans.mac.MacAddress;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.swans.phy.Phy802_11;
import jist.swans.phy.PhyMessage;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.Constants;
import jist.runtime.JistAPI;
import jist.runtime.Util;
import org.apache.log4j.Logger;

import java.util.*;

import brn.util.BrnUtil;

/**
 * Version of McExOR which makes use of RTS/CTS.
 *
 * TODO
 * - use RTS/CTS to detect deaf nodes
 *
 * @author Zubow
 */
public class MacMcExORwRCts extends MacMcExOR {

  /** logger for mac events. */
  public static final Logger log = Logger.getLogger(MacMcExORwRCts.class.getName());

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

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

  /**
   * 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;

  // mac modes

  /** mac mode: waiting for virtual carrier sense to RTS. */
  public static final byte  MAC_MODE_SNAV_RTS   = 4;

  /** mac mode: waiting for CTS packet. */
  public static final byte  MAC_MODE_SWFCTS     = 5;

  /** mac mode: waiting for DATA packet. */
  public static final byte  MAC_MODE_SWFDATA    = 6;

  /** mac mode: transmitting RTS packet. */
  public static final byte  MAC_MODE_XRTS       = 8;

  /** mac mode: transmitting CTS packet. */
  public static final byte  MAC_MODE_XCTS       = 9;

  /** The static MAC configuration. */
  protected static class ConfigurationRtsCts extends Configuration {

    /** Indicates whether the NAV value in RTS packets should be set or not. */
    protected boolean useNAVinRtsCts                 = true;
    /** the same idea as with ACKs */
    protected boolean useVariableCtsSize             = false;
    /** use compressed slotted cts */
    public boolean useCtsCompression                 = false;
    /** whether we want to use the feedback from RTS/CTS exchange to adapt the candidate set. */
    public boolean useRtsCtsFeedback                 = false;

    public ConfigurationRtsCts(long delayChannelSwitch, MacAddress localAddr,
                               RadioInterface.RFChannel homeChannel, int channels,
                               boolean useCompression, boolean usePreferencedAck,
                               boolean useAppSeqNumbers, boolean detectDuplicates,
                               boolean passiveAcknowledgement, RadioInfo radioInfo,
                               Phy802_11 phy, double refNoiseFactor,
                               boolean updatePrioOnNoiseDetection, boolean useVariableAckSize,
                               boolean useNAVinAcks, boolean useNAVinData,
                               int candidateSetSize, boolean testRun) {
      super(delayChannelSwitch, localAddr, homeChannel, channels, useCompression,
          usePreferencedAck, useAppSeqNumbers, detectDuplicates, passiveAcknowledgement,
          radioInfo, phy, refNoiseFactor, updatePrioOnNoiseDetection, useVariableAckSize,
          useNAVinAcks, useNAVinData, candidateSetSize, testRun);
    }

    public String getModeString(byte mode) {
      switch (mode) {
        case MAC_MODE_SNAV_RTS:
          return "NAV_RTS";
        case MAC_MODE_SWFCTS:
          return "WF_CTS";
        case MAC_MODE_SWFDATA:
          return "WF_DATA";
        case MAC_MODE_XRTS:
          return "X_RTS";
        case MAC_MODE_XCTS:
          return "X_CTS";
        default:
          return super.getModeString(mode);
      }
    }
  }

  /** The dynamic part of the MAC configuration */
  protected class MacCtxRtsCts extends MacCtx {

    /**
     * Return whether the mac is currently waiting for a response.
     *
     * @return whether mac waiting for response
     */
    public boolean isAwaitingResponse() {
      switch (mode) {
      case MAC_MODE_SWFCTS:
        return true;
      default:
        return super.isAwaitingResponse();
      }
    }

    /**
     * Return whether the mac is currently transmitting.
     *
     * @return whether mac is currently transmitting
     */
    public boolean isTransmitting() {
      switch (mode) {
      case MAC_MODE_XRTS:
      case MAC_MODE_XCTS:
        return true;
      default:
        return super.isTransmitting();
      }
    }
  }

  /** Encapsulation of all transmission-related state. */
  protected class TransmissionCtxRtsCts extends TransmissionCtx {

    /** long retry counter. */
    protected byte                         longRetry;
    /** List of cts packets {@link MacMcExORMessage.Cts} */
    protected List                         lstCtssReceived;
    /** we send out an RTS for this candidates */
    protected MacMcExORMessage.CSetAddress rtsNextHops;
    /** currently open cands in exor RTS/CTS */
    protected List                         rtsOpenHops;

    /** set of candidates from which we know that they received the corresponding rts. */
    protected Set                          rtsReceived;

    public TransmissionCtxRtsCts(Message msg, MacMcExORMessage.CSetAddress nextHops,
                                 RadioInterface.RFChannel channel, MessageAnno anno) {
      super(msg, nextHops, channel, anno);
      // retry counts
      longRetry       = 0;
      initRtsCtx();
    }

    protected void initRtsCtx() {

      lstCtssReceived = new LinkedList();
      rtsReceived     = new TreeSet();

      if (rtsNextHops == null && !config.testRun) {
        /**
         * Retrieve all possible candidate sets from the annotations. Put their addresses into a set
         * and use this set for the RTS destination.
         */
        List dstOptions = (List)anno.get(MessageAnno.ANNO_MAC_OPT_CANDS);

        if (dstOptions != null && ((ConfigurationRtsCts)config).useRtsCtsFeedback) {

          List newCset = new ArrayList();
          Set dstSet = new TreeSet();
          for (int k = 0; k < config.candidateSetSize; k++) {
            for (int i = 0; i < dstOptions.size(); i++) {
              MacMcExORMessage.CSetAddress addr = (MacMcExORMessage.CSetAddress)dstOptions.get(i);
              if (addr != null && addr.getCandidateSetSize() > k) {
                MacAddress cand = (MacAddress)addr.getLstAddresses().get(k);
                if (cand != null) {
                  if (!dstSet.contains(cand)) {
                    newCset.add(cand);
                    dstSet.add(cand);
                  }
                }
              }
            }
          }
          log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): new CSet for RTS/CTS: "
              + BrnUtil.listToString(newCset));
          rtsNextHops     = new MacMcExORMessage.CSetAddress(newCset);

        } else {
          // we are not using RTS/CTS feedback
          rtsNextHops     = new MacMcExORMessage.CSetAddress(packetNextHops);
        }
      } else if (rtsNextHops == null && config.testRun) {
        MacMcExORMessage.CSetAddress rtsAddr = (MacMcExORMessage.CSetAddress)anno.get(MessageAnno.ANNO_MAC_RTS_CANDIDATES);
        if (rtsAddr != null)
          rtsNextHops = new MacMcExORMessage.CSetAddress(rtsAddr);
        else
          rtsNextHops = new MacMcExORMessage.CSetAddress(packetNextHops);
      }

      rtsOpenHops     = new LinkedList(rtsNextHops.getLstAddresses());
    }

    /**
     * We overwrite this method, since in case of RTS/CTS is is dangenerous to prematurely finish WFACK.
     * Overwritten.
     */
    protected void finishSlottedAck(MacMcExORMessage.Ack ack) {
      if (packetOpenHops.isEmpty()) { // all acks received
        if (macTxFinished.isActive())
          macTxFinished.handle(packet, anno, true, shortRetry);
        cancelTimer();
        macCtx.decCW();
        self.cfDone(true, true);
      }
    }

    public byte getRetry() {
      if (null == packet)
        return 0; // no packet, no retry ...
      return (shouldRTS() ? longRetry : shortRetry);
    }

    protected void resetRetry() {
      shortRetry = 0;
      longRetry = 0;
    }

    /**
     * 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() {
      return packet.getSize() > THRESHOLD_RTS && !isBroadcast();
    }

    /**
     * Return whether current packet requires fragmentation.
     *
     * @return does current packet require fragmentation.
     */
    protected boolean shouldFragment() {
      return packet.getSize() > THRESHOLD_FRAGMENT && !isBroadcast();
    }

    /** Overwritten */
    protected void sendData() {
      if (shouldRTS()) {
        sendRts();
      } else {
        if (packetNextHops.isBroadCast()) {
          sendDataBroadcast();
        } else {
          sendDataAnycast();
        }
      }
    }

    protected void sendRts() {
      // Do bit-rate selection according to algorithm.
      //adjustBitrates(packetNextHop, true, msgFactory
      //    .getSize(Mac802_11Message.TYPE_RTS));

      // estimate the number of candidates
      int expectedCts  = rtsOpenHops.size();
      int expectedAcks = rtsOpenHops.size();

      // calculate time budget for RTS, x*CTS, DATA, x*ACK (NAV)

      long msgDuration = 0;
      /** silence neighboring nodes by setting NAV in RTS packets. */
      if (((ConfigurationRtsCts)config).useNAVinRtsCts) {
        long ctsTime = Constants.PROPAGATION
            + expectedCts // for all cts
            * (config.transmitTime(MacMcExORMessage.Cts.getSize(expectedAcks), config.controlRate)
                + Constants.PROPAGATION + config.getSifs());

        // AZu: to avoid collisions between candidate 0 and 2 cts have different sizes
        if (((ConfigurationRtsCts)config).useVariableCtsSize) {
          int addTime = 0;
          for (int i = 0; i < expectedCts; i++)
            addTime += (expectedCts - i - 1);
          ctsTime += addTime * config.getSifs();
        }

        /**
         * TODO: when we use variable bitrates is is likely that the calculated NAV is wrong
         * (too small or too big). However, we can use an adapted version of the algorithm
         * proposed in "A Rate-Adaptive MAC Protocol for Multi-Hop Wireless Networks".
         */
        long dataTime = config.getSifs()
          + config.transmitTime(MacMcExORMessage.Data.getSize(
            packet.getSize(), rtsNextHops.getCandidateSetSize()), config.dataRate)
          + Constants.PROPAGATION;

        long ackTime = Constants.PROPAGATION
            + expectedAcks // for all acks
            * (config.transmitTime(MacMcExORMessage.Ack.getSize(rtsNextHops.getCandidateSetSize()), config.controlRate)
                + Constants.PROPAGATION + config.getSifs());

        // AZu: to avoid collisions between candidate 0 and 2 cts have different sizes
        if (((ConfigurationRtsCts)config).useVariableAckSize) {
          int addTime = 0;
          for (int i = 0; i < expectedCts; i++)
            addTime += (expectedCts - i - 1);
          ackTime += addTime * config.getSifs();
        }

        msgDuration = ctsTime + dataTime + ackTime;
      }

      MacMcExORMessage.Rts rts =
        new MacMcExORMessage.Rts(rtsNextHops, config.localAddr, (int)msgDuration);
      /** set mode to transmitting RTS packet */
      macCtx.setMode(MAC_MODE_XRTS);
      long delay = config.getRxTxTurnaround();

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

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

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

      // wait for EOT, schedule CTS wait timer
      JistAPI.sleep(delay + duration + Constants.EPSILON_DELAY);

      // Rts completed + delay
      long ctsDuration = config.transmitTime(MacMcExORMessage.Cts.getSize(expectedCts), config.controlRate);
      long timeout = Constants.PROPAGATION + config.getSlotTime()
        + expectedCts * (ctsDuration + Constants.PROPAGATION + config.getSifs());

      /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
      if (((ConfigurationRtsCts)config).useVariableCtsSize) {
        int addTime = 0;
        for (int i = 0; i < expectedCts; i++)
          addTime += (expectedCts - i - 1);
        timeout += addTime * config.getSifs();
      }
      /** set mode to waiting for CTS packet */
      self.startTimer(timeout, MAC_MODE_SWFCTS);
    }

    /** Overwritten */
    protected long getMacDelay() {
      return afterCts ? config.getSifs() : config.getRxTxTurnaround();
    }

    /** Overwritten */
    protected void retry() {
      if (macCtx.getMode() == MAC_MODE_SWFCTS && !lstCtssReceived.isEmpty()) {
        /** We received at least one CTS; Therefore we can continue with our TX: sending data packet */

        // print out some stats ... candidates which receive the rts packet
        log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): RTS sender knows that the cands \n"
           + BrnUtil.setToString(((TransmissionCtxRtsCts)txCtx).rtsReceived) + " receives his RTS packet \n"
            + " with SNR = " + BrnUtil.listToString(((TransmissionCtxRtsCts)txCtx).lstCtssReceived));

        if (((ConfigurationRtsCts)config).useRtsCtsFeedback) {
          // modify the cset by analyzing the result of the RTS/CTS handshake
          adaptCSetToRCtsFeedback();
        }

        if (packetOpenHops.size() < config.candidateSetSize)
          log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): we are using a cset which is smaller "
              + packetOpenHops.size() + " than the prefered size " + config.candidateSetSize);

        // cleanup
        initRtsCtx();
        shortRetry = 0;

        cancelTimer();
        //macCtx.decCW();
        afterCts = true;
        /** only unicast packets are protected by RTS/CTS */
        Util.assertion(!packetNextHops.isBroadCast());
        sendDataAnycast();
        return;
      }

      if (macCtx.getMode() == MAC_MODE_SWFACK && !lstAcksReceived.isEmpty()) {
        /** we received at least one ack */
        cancelTimer();
        macCtx.decCW();
        self.cfDone(true, true);
        return;
      }

      // use long retry count for frames larger than RTS_THRESHOLD
      if (shouldRTS() && macCtx.mode != MAC_MODE_SWFCTS) {
        if (longRetry < RETRY_LIMIT_LONG) {
          if (retryEvent.isActive())
            retryEvent.handle(this.packet, this.anno, longRetry, false);
          longRetry++;
          retryYes();
        } else {
          anno.put(MessageAnno.ANNO_MAC_RETRIES, new Byte(longRetry));
          if (macTxFinished.isActive())
            macTxFinished.handle(packet, anno, false, longRetry);
          longRetry = 0;
          retryNo();
        }
      } else {
        super.retry();
      }
    }

    /**
     * Helper function adapts the candidate set for the deferred packet transmission
     * by analyzing the result of the RTS/CTS handshake.
     */
    private void adaptCSetToRCtsFeedback() {
      /** these candidates successfully received the RTS packet */
      List rtsReceiver = rtsNextHops.getLstAddresses();
      Util.assertion(rtsReceiver.removeAll(rtsOpenHops));

      if (rtsReceiver.containsAll(packetNextHops.getLstAddresses())) {
        // all candidates in the candidate set received the RTS packet and seem to be not deaf

      } else {
        // handle deafness/link quality problem here
        List dstOptions = (List)anno.get(MessageAnno.ANNO_MAC_OPT_CANDS);
        if (dstOptions != null) {
          boolean found = false;
          for (int i = 0; i < dstOptions.size(); i++) {
            MacMcExORMessage.CSetAddress addr = (MacMcExORMessage.CSetAddress)dstOptions.get(i);
            if (rtsReceiver.containsAll(addr.getLstAddresses())) {
              // perfect, we found a cset where all included candidates received the RTS
              packetNextHops = new MacMcExORMessage.CSetAddress(addr);
              packetOpenHops = new LinkedList(packetNextHops.getLstAddresses());

              log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): useRtsCtsFeedback: new cset "
                  + BrnUtil.listToString(packetOpenHops));
              found = true;
              break;
            }
          }
          if (!found) {
            /**
             * Can not find a solution which perfectly match apply the following new algorithm:
             * From all options we have remove the candidates which do not receive the rts.
             * Choose the solution with the most candidates.
             * */
            dstOptions = (List)anno.get(MessageAnno.ANNO_MAC_OPT_CANDS);
            dstOptions.add(new MacMcExORMessage.CSetAddress(packetNextHops));
            int best_cset_size = 0;
            MacMcExORMessage.CSetAddress best_cand = null;
            for (int i = 0; i < dstOptions.size(); i++) {
              MacMcExORMessage.CSetAddress addr = (MacMcExORMessage.CSetAddress)dstOptions.get(i);
              List cset = new ArrayList(addr.getLstAddresses());
              cset.retainAll(rtsReceiver);
              if (cset.size() > best_cset_size) {
                best_cset_size = cset.size();
                best_cand = addr;
              }
            }
            if (best_cand != null) {
              // perfect, we found a cset where all included candidates received the RTS
              packetNextHops = new MacMcExORMessage.CSetAddress(best_cand);
              packetOpenHops = new LinkedList(packetNextHops.getLstAddresses());
              log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): useRtsCtsFeedback: new cset "
                  + BrnUtil.listToString(packetOpenHops));
            }
          }
        }
      }
    }

    public void receiveCts(MacMcExORMessage.Cts cts, MessageAnno ctsAnno) {
      if (!rtsNextHops.contains(cts.getSrc())) {
        throw new RuntimeException("processing wrong cts packet.");
      } else if (!rtsOpenHops.remove(cts.getSrc())) {
        log.warn(getNode().getNodeId() + "(" + JistAPI.getTime() + "): received duplicate cts from " + cts.getSrc());
      }

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): receiveCts() from "
          + cts.getSrc() + " to " + BrnUtil.setToString(cts.getAllCandRecv()));

      if (receiveEvent.isActive()) {
        long duration = ((Long)ctsAnno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();
        receiveEvent.handle(cts, ctsAnno, duration);
      }

      // inspect the cts packet to find out how many candidates received the rts packet
      rtsReceived.addAll(cts.getAllCandRecv());

      // remember we received the cts.
      lstCtssReceived.add(cts);

      if (macCtx.getMode() == MAC_MODE_SWFCTS && rtsOpenHops.isEmpty()) { // we successfully received all cts packets
        // print out some stats ... candidates which receive the rts packet
        log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): RTS sender knows that the cands \n"
           + BrnUtil.setToString(((TransmissionCtxRtsCts)txCtx).rtsReceived) + " receives his RTS packet \n"
           + " with SNR = " + BrnUtil.listToString(((TransmissionCtxRtsCts)txCtx).lstCtssReceived));

        // cleanup
        initRtsCtx();
        shortRetry = 0;

        cancelTimer();
        //macCtx.decCW();
        afterCts = true;
        /** only unicast packets are protected by RTS/CTS */
        Util.assertion((!packetNextHops.isBroadCast()));
        sendDataAnycast();
      }
    }
  }

  /** Encapsulation of all receiving-related state. */
  protected class ReceivingCtxRtsCts extends ReceivingCtx {

    /** an RTS packet, if one arrived, or null */
    protected MacMcExORMessage.Rts         rtsRcvd;
    /** the corresponding rts anno */
    protected MessageAnno                  rtsAnno;

    /** set of candidates from which we know that they received the corresponding rts. */
    protected Set rtsReceived;

    public ReceivingCtxRtsCts(MacAddress addrExorSrc, byte highestPrioCandidate,
                              byte highestPref, RadioInterface.RFChannel rfChannel, MessageAnno anno) {
      super(addrExorSrc, highestPrioCandidate, highestPref, rfChannel, anno);
      /** received RTS packet short term memory */
      rtsRcvd = null;
      rtsAnno = null;
      rtsReceived = new TreeSet();
    }

    public void receiveRts(final MacMcExORMessage.Rts rts, final byte ownCandId, final MessageAnno anno) {

      if (log.isInfoEnabled())
        log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): receiving packet with id " + rts.getSeq());

      /*
       * processes the complete ack phase. Enters at the point in time the ack
       * is received, and returns after finishing the slotted ack phase.
       */
      /** cancel all active timers */
      cancelTimer();
      /** entering cts mode */
      macCtx.setMode(MAC_MODE_XCTS);

      /** TX duration of such a slotted ack packet */
      final long durationCts = config.transmitTime(
          MacMcExORMessage.Cts.getSize(rts.getCandidates().size()), config.controlRate);
      MacMcExORMessage.CSetAddress candidates = (MacMcExORMessage.CSetAddress) rts.getDsts().clone();
      int expectedCandId = 0; /** we expect to receive the ack from this candidate */

      JoinSlottedCts(candidates, candidates.getCandidateSetSize(),
          expectedCandId, ownCandId, rts.getSrc(), durationCts, null);

      // check highest prio candidate
      Util.assertion (highestPrioCandidate >= 0);

      // release the mac layer here, because we must wait all cts slots !!!
      //self.cfDone(false, false);
    }

    /**
     * Similiar to the slotted ack algorithm.
     * {@link #JoinSlottedAck(brn.swans.mac.MacMcExORMessage.CSetAddress, int, int, int, jist.swans.mac.MacAddress, int, long)}
     */
    private void JoinSlottedCts(MacMcExORMessage.CSetAddress candidates, int slots, int expectedCandId, int ownCandId,
                                MacAddress srcOfRts, long durationCts, MacMcExORMessage.Cts cts) {

      if (((ConfigurationRtsCts)config).useCtsCompression) {
        throw new RuntimeException("Not Yet implemented!!!!!!!!");
      } else { /** no compression - simple time division multiplexing */
        long waitTime = ownCandId * (config.getSifs() + durationCts) + config.getSifs() - config.getRxTxTurnaround();
        /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
        if (((ConfigurationRtsCts)config).useVariableCtsSize) {
          int addTime = 0;
          for (int i = slots - ownCandId; i < slots; i++)
            addTime += i;
          waitTime += addTime * config.getSifs();
        }

        JistAPI.sleepBlock(waitTime);
      }

      // Note: the h.p.cand. field could be modified during the sleep phase.
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): CTS handling: cand/slot " + ownCandId + "/" + ownCandId + " sending cts");
      /** the time has come to send our ack packet. */
      sendCts(srcOfRts, candidates, cts);

      /** schedule upcall */
      long acksToWait = slots - ownCandId - 1;

//      if (config.useCompression) {
//        /** due to the slotted ack we will return to early. wait additional time. */
//        acksToWait += missedAcks;
//      }

      Util.assertion (0 <= acksToWait && acksToWait < slots);

      /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
      int addTime = 0;
      if (((ConfigurationRtsCts)config).useVariableCtsSize) {
        for (int i = ownCandId + 1; i < slots; i++)
          addTime += (slots - i - 1);
      }
      JistAPI.sleepBlock(config.getRxTxTurnaround() + durationCts + (durationCts + config.getSifs())
          * acksToWait + Constants.EPSILON_DELAY + addTime * config.getSifs());

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): cand " + ownCandId + " slotted cts completed.");
    }


    private void sendCts(MacAddress srcOfRts, MacMcExORMessage.CSetAddress candidates,
                         MacMcExORMessage.Cts ctsRcvd) {

      // Do bit-rate selection according to algorithm.
      //adjustBitrates(rtsRcvd.getSrc(), false, msgFactory
      //    .getSize(Mac802_11Message.TYPE_CTS));

      //MacMcExORMessage.Rts rtsRcvd = ((ReceivingCtxRtsCts)rxCtx).rtsRcvd;
      int overallCandidates = candidates.getCandidateSetSize();
      int cId = candidates.getCandId(macCtx.getLocalAddr());

      Util.assertion(cId >= 0);

      // calculate ack NAV
      long msgDuration = 0;

      /** silence neighboring nodes by setting NAV in ACK packets. */
      if (((ConfigurationRtsCts)config).useNAVinRtsCts) {

        // create cts packet
        if (rtsRcvd != null) {
          msgDuration = rtsRcvd.getDuration()
            - (cId + 1) // for all previous cts + myself
              * (config.transmitTime(MacMcExORMessage.Cts.getSize(overallCandidates), config.controlRate)
                  + Constants.PROPAGATION + config.getSifs());
        } else {
          Util.assertion(ctsRcvd != null);

          int otherCtsCandId = candidates.getCandId(ctsRcvd.getSrc());
          if (otherCtsCandId < cId) { // we received an cts of a higher candidate
            msgDuration = ctsRcvd.getDuration()
              - (cId - otherCtsCandId) // for all previous cts + myself
                * (config.transmitTime(MacMcExORMessage.Cts.getSize(overallCandidates), config.controlRate)
                    + Constants.PROPAGATION + config.getSifs());
          } else {
            throw new RuntimeException("This should not be possible!!!");
          }
        }

        /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
        if (((ConfigurationRtsCts)config).useVariableCtsSize) {
          int subTime = 0;
          for (int i = 0; i < cId; i++)
            subTime += (overallCandidates - i - 1);
          msgDuration -= subTime * config.getSifs();
        }
      }
      // create cts
      MacMcExORMessage.Cts cts;

      if (rtsAnno == null) {
        cts = new MacMcExORMessage.Cts(macCtx.getLocalAddr(), srcOfRts, (int)msgDuration, candidates);
      } else {
        double rtsRecvPwr = ((Double)rtsAnno.get(MessageAnno.ANNO_RADIO_SIGNAL_POWER)).doubleValue();
        double rtsNoisePwr = ((Double)rtsAnno.get(MessageAnno.ANNO_RADIO_NOISE_LEVEL)).doubleValue();
        double SNR = jist.swans.misc.Util.toDB(rtsRecvPwr / rtsNoisePwr);
        cts = new MacMcExORMessage.Cts(macCtx.getLocalAddr(),
            srcOfRts, (int)msgDuration, candidates, SNR);
      }
      // put the set of known candidates which receive the RTS packet
      cts.setCandRecv(rtsReceived, true);

      int expectedAcks = candidates.getCandidateSetSize();

      // rts packet not needed anymore
      rtsRcvd = null;
      rtsAnno = null;

      long delay = config.getRxTxTurnaround();

      // transmit with no delay, we waited for the right time slot in receiveData
      Util.assertion (MAC_MODE_XCTS == macCtx.getMode());

      // Add PHY layer information to the MAC message
      PhyMessage phyMessage = new PhyMessage(cts, config.controlRate, config.phy, rxCtx.channel);
      long duration = phyMessage.txDuration();

      // AZu: to avoid collisions between candidate 0 and 2 acks have different sizes
      if (((ConfigurationRtsCts)config).useVariableCtsSize && overallCandidates > 1)
        duration  += (overallCandidates - cId - 1) * config.getSifs();

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

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): sendCts() - duration = " + duration);

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

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

      // wait for EOT, schedule DATA wait timer
      JistAPI.sleep(delay + duration);

      long ackTime = Constants.PROPAGATION
          + expectedAcks // for all acks
          * (config.transmitTime(MacMcExORMessage.Ack.getSize(expectedAcks), config.controlRate) // rate-dep part
              + Constants.PROPAGATION + config.getSifs()); // rate-undep part


      // TODO Why + slot time?
      long endTimeOfDataRcpt = cts.getDuration()
          - ackTime
          + config.getSlotTime()
          + Constants.EPSILON_DELAY;
      self.startTimer(endTimeOfDataRcpt, MAC_MODE_SWFDATA);
    }

    public void receiveCts(MacMcExORMessage.Cts cts, MessageAnno ctsAnno) {
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): received cts from " + cts.getSrc());

      if (receiveEvent.isActive()) {
        long duration = ((Long)ctsAnno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();
        receiveEvent.handle(cts, ctsAnno, duration);
      }

      /** retrieve the list of candidates which receive the rts packet from the incoming cts packet and
       * put them into our internal set */
      rtsReceived.addAll(cts.getAllCandRecv());
    }
  }

  /**
   * Instantiates a new Multi-Channel Opportunistic Protocol with supports RTS/CTS.
   *
   * @param addr              local mac address
   * @param radioInfo         radio properties
   * @param homeChannel       the home channel of this multi-channel mac.
   * @param channels          the number of available rf channels.
   * @param useCompression    indicates whether we want to use the compressed slotted ack
   * @param usePreferencedAck Whether to use preferences in ack packets (or send max all the time)
   * @param useAppSeqNumbers  Whether to use sequence numbers to detect duplicates
   */
  public MacMcExORwRCts(MacAddress addr, RadioInfo radioInfo, RadioInterface.RFChannel homeChannel,
                        int channels, boolean useCompression, boolean usePreferencedAck,
                        boolean useAppSeqNumbers, long delayChannelSwitch, double refNoiseFactor,
                        boolean updatePrioOnNoiseDetection, boolean useVariableAckSize, boolean useNAVinAcks,
                        boolean useNAVinData, int candidateSetSize, boolean useNAVinRtsCts, boolean useVariableCtsSize,
                        boolean useCtsCompression, boolean useRtsCtsFeedback, boolean testRun) {
    super(addr, radioInfo, homeChannel, channels, useCompression, usePreferencedAck,
        useAppSeqNumbers, delayChannelSwitch, refNoiseFactor, updatePrioOnNoiseDetection,
        useVariableAckSize, useNAVinAcks, useNAVinData, candidateSetSize, testRun);
    // set additional params
    ((ConfigurationRtsCts)config).useNAVinRtsCts      = useNAVinRtsCts;
    ((ConfigurationRtsCts)config).useVariableCtsSize  = useVariableCtsSize;
    ((ConfigurationRtsCts)config).useCtsCompression   = useCtsCompression;
    ((ConfigurationRtsCts)config).useRtsCtsFeedback   = useRtsCtsFeedback;
  }

  /** overwritten */
  protected void initCtx(long delayChannelSwitch, MacAddress addr, RadioInterface.RFChannel homeChannel,
                         int channels, boolean useCompression, boolean usePreferencedAck, boolean useAppSeqNumbers,
                         RadioInfo radioInfo, Phy802_11 phy, double refNoiseFactor, boolean updatePrioOnNoiseDetection,
                         boolean useVariableAckSize, boolean useNAVinAcks, boolean useNAVinData,
                         int candidateSetSize, boolean testRun) {
    config = new ConfigurationRtsCts(delayChannelSwitch, addr, homeChannel, channels, useCompression,
        usePreferencedAck, useAppSeqNumbers, true, false, radioInfo, phy, refNoiseFactor,
        updatePrioOnNoiseDetection, useVariableAckSize, useNAVinAcks, useNAVinData,
        candidateSetSize, testRun);

    macCtx  = new MacCtxRtsCts();
    txCtx   = null;
    rxCtx   = null;
  }

  /** overwritten */
  protected void initRxCtx(final MacAddress addrExorSrc, final byte highestPrioCandidate,
                           final byte highestPref, final RadioInterface.RFChannel rfChannel, final MessageAnno anno) {
    rxCtx = new ReceivingCtxRtsCts(addrExorSrc, highestPrioCandidate, highestPref, rfChannel, anno);
  }

  /** overwritten */
  protected void initTxCtx(Message msg, MacMcExORMessage.CSetAddress nextHops,
                           RadioInterface.RFChannel channel, MessageAnno anno) {
    txCtx = new TransmissionCtxRtsCts(msg, nextHops, channel, anno);
  }

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

  /**
   * MacInterface; called by NET
   *
   * Overwritten
   */
  public void send(Message msg, MacAddress nextHop, MessageAnno anno) {
    super.send(msg, nextHop, anno);
  }

  // ////////////////////////////////////////////////
  // receive-related functions
  //

  // MacInterface
  public void peek(Message msg, MessageAnno anno) {
    super.peek(msg, anno);
    if (macCtx.mode == MAC_MODE_SNAV_RTS) {
      idle();
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.mac.MacDcf#receivePacket(jist.swans.mac.MacDcfMessage, jist.swans.misc.MessageAnno)
   */
  protected void receivePacket(MacMcExORMessage mcExORMsg, MessageAnno anno) {

    //needEifs = false;
    switch (mcExORMsg.getType()) {
      case MacMcExORMessage.TYPE_RTS: {
        MacMcExORMessage.Rts rtsMsg = (MacMcExORMessage.Rts) mcExORMsg;
        /** handle rts reception */
        receiveRts(rtsMsg, anno);
        break;
      }
      case MacMcExORMessage.TYPE_CTS: {
        MacMcExORMessage.Cts ctsMsg = (MacMcExORMessage.Cts) mcExORMsg;
        /** handle cts reception */
        receiveCts(txCtx, ctsMsg, anno);
        break;
      }
      default:
        super.receivePacket(mcExORMsg, anno);
    }
  }

  protected void receiveRts(MacMcExORMessage.Rts rts, MessageAnno anno) {
    // receiving RTS in wrong state: NAV waiting or waiting for ack
    if (macCtx.waitingNav() || macCtx.mode == MAC_MODE_SWFACK)
      return;

    /* if the cts is lost, resend the rts packet. */
    if (macCtx.mode == MAC_MODE_SWFDATA
        && ((ReceivingCtxRtsCts)rxCtx).rtsRcvd != null
        && !((ReceivingCtxRtsCts)rxCtx).rtsRcvd.getSrc().equals(rts.getSrc()))
      return;

    if (!rts.getDsts().contains(macCtx.getLocalAddr())) {
      /** rts packet and I am not enlisted in the list of candidates */
      receiveForeign(rts, anno);
      return;
    }

    log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): receiveRts() from "
        + rts.getSrc() + " to " + BrnUtil.listToString(rts.getCandidates()));
    if (receiveEvent.isActive()) {
      long duration = ((Long)anno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();
      receiveEvent.handle(rts, anno, duration);
    }

    /** case anycast (+unicast) */
    RadioInterface.RFChannel rxChannel
            = (RadioInterface.RFChannel)anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);
    if (rxChannel == null)
      throw new RuntimeException(getNode().getNodeId() + "(" + JistAPI.getTime() + "): missing channel annotation.");
    /** my id within the cset; reverse order: 0 means highest priority */
    final byte ownCandId  = rts.getDsts().getCandId(macCtx.getLocalAddr());
    /** create new reception context */
    initRxCtx(rts.getSrc(), ownCandId, brn.swans.Constants.PREFERENCE_MIN, rxChannel, anno);
    /** save rts packet for later processing */
    ((ReceivingCtxRtsCts)rxCtx).rtsRcvd = rts;
    ((ReceivingCtxRtsCts)rxCtx).rtsAnno = anno;

    /** initiate the slotted ack process */
    ((ReceivingCtxRtsCts)rxCtx).receiveRts(rts, ownCandId, anno);
  }

  protected void receiveCts(TransmissionCtx txCtx, MacMcExORMessage.Cts cts, MessageAnno anno) {

    MacAddress dst = cts.getDst();

    /** case 1: we sent the RTS packet and wait for cts */
    if (macCtx.getMode() == MAC_MODE_SWFCTS && macCtx.getLocalAddr().equals(dst)) {
      Util.assertion(null != txCtx);

      ((TransmissionCtxRtsCts)txCtx).receiveCts(cts, anno);
    }
    /** case 2: we received the RTS packet and are one of the candidates which have to send their CTS packets */
    else if (MAC_MODE_XCTS == macCtx.getMode() && rxCtx.addrExorSrc.equals(dst)) {

      ((ReceivingCtxRtsCts)rxCtx).receiveCts(cts, anno);
    }
    /** case 3: we receive cts and are candidate, but missed the rts packet */
/*
    else
    if (cts.getCandidates().contains(macCtx.getLocalAddr())) {
      if (macCtx.getMode() != MAC_MODE_SIDLE
          && macCtx.getMode() != MAC_MODE_DIFS
          && macCtx.getMode() != MAC_MODE_SBO
          && macCtx.getMode() != MAC_MODE_SNAV)
        return;

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): received cts from " + cts.getSrc() + " and we are candidate, but missed the rts packet.");

      if (receiveCtsEvent.isActive()) {
        long duration = ((Long)anno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();
        receiveCtsEvent.handle(cts, anno, duration);
      }

      int currCandId = 1 + (int) cts.getCandidates().getCandId(cts.getSrc());
      byte ownCandId = cts.getCandidates().getCandId(macCtx.getLocalAddr());

      // Did we miss our slot?
      if (currCandId > ownCandId)
        return;

      cancelTimer();
      // switch to TX ack mode
      macCtx.setMode(MAC_MODE_XCTS);

      RadioInterface.RFChannel rxChannel
              = (RadioInterface.RFChannel)anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);
      if (rxChannel == null)
        throw new RuntimeException(getNode().getNodeId() + "(" + JistAPI.getTime() + "): missing channel annotation.");
      // create new reception ctx
      initRxCtx(dst, cts.getCandidateId(), brn.swans.Constants.PREFERENCE_MAX, rxChannel, anno);

      // join the slotted ack at a later point in time
      ((ReceivingCtxRtsCts)rxCtx).JoinSlottedCts((MacMcExORMessage.CSetAddress)cts.getCandidates().clone(),
          cts.getCandidates().getCandidateSetSize(), currCandId, ownCandId,
          dst, config.transmitTime(cts, controlRate), cts);

      // release the mac layer
      self.cfDone(false, false);
    }
*/
    /** otherwise: ack does not belong to us */
    else {
      receiveForeign(cts, anno);
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.mac.MacDcf#receiveData(jist.swans.mac.MacDcfMessage.Data, jist.swans.misc.MessageAnno)
   */
  protected void receiveData(MacMcExORMessage.Data msg, MessageAnno anno)
  {
    // not in standard, but ns-2 does.
    // decCW();
    // shortRetry = 0;
    if (macCtx.mode != MAC_MODE_SWFDATA && macCtx.isAwaitingResponse())
      return;

    if (macCtx.mode == MAC_MODE_SWFDATA) {
      // CTS successful:
      //rca.reportPacketTx(msg.getSrc(), Mac802_11Message.TYPE_CTS,
      //    msgFactory.getSize(Mac802_11Message.TYPE_CTS), controlRate,
      //    getRetry() + 1, Constants.RADIO_RECV_STATUS_OK.byteValue());
    }

    super.receiveData(msg, anno);
  }

  // ////////////////////////////////////////////////
  // radio mode
  //


  protected void radioBusy() {
    switch (macCtx.mode) {
      case MAC_MODE_SWFCTS:
      /* AZu: support of RTS/CTS */
      case MAC_MODE_XCTS:
      case MAC_MODE_SWFDATA:
      case MAC_MODE_XRTS:
        // don't care
        break;
      default:
        super.radioBusy();
    }
  }

  protected void radioIdle() {
    switch (macCtx.mode) {
      case MAC_MODE_SWFCTS:
      /* AZu: support of RTS/CTS */
      case MAC_MODE_XCTS:
      case MAC_MODE_XRTS:
      case MAC_MODE_SWFDATA:
        // don't care
        break;
      default:
        super.radioIdle();
    }
  }

  // MacInterface
  public void timeout(int timerId) {

    if (timerId != macCtx.timerId)
      return;

    switch (macCtx.getMode()) {
      case MAC_MODE_SNAV_RTS:
        macCtx.resetNav();
        doDifs();
        break;
      case MAC_MODE_SWFCTS:
        // RTS failed:
        //rca.reportPacketTx(packetNextHop, Mac802_11Message.TYPE_RTS,
        //    msgFactory.getSize(Mac802_11Message.TYPE_RTS), controlRate, shortRetry + 1,
        //    Constants.RADIO_RECV_STATUS_LOST.byteValue());
        txCtx.retry();
        break;
      case MAC_MODE_SWFDATA:
        // CTS failed: But don't know receiver address for CTS packet sent!
//        rca.reportPacketTx(packetNextHop, MacDcfMessage.TYPE_CTS, -1,
//            MacDcfMessage.Cts.SIZE, controlRate, 1,
//            Constants.RADIO_RECV_STATUS_LOST.byteValue());
        macCtx.setBackoff();
        doDifs();
        break;

      // OLD - TODO refactor ... use super()
      case MAC_MODE_SIDLE:
        idle();
        break;
      case MAC_MODE_DIFS:
        if (macCtx.hasBackoff()) {
          macCtx.backoff();
        } else if (null != txCtx) {
          if (!macCtx.channel.equals(txCtx.channel)) {
            /** we are on the wrong RF channel; initiate channel switch. */
            doChannelSwitch(txCtx.channel);
          } else {
            txCtx.sendData();
          }
        } else {
          idle();
        }
        break;
      case MAC_MODE_SBO:
        Util.assertion(macCtx.boStart + macCtx.bo == JistAPI.getTime());
        macCtx.clearBackoff();
        if (null != txCtx) {
          if (!macCtx.channel.equals(txCtx.channel)) {
            /** we are on the wrong RF channel; initiate channel switch. */
            doChannelSwitch(txCtx.channel);
          } else {
            txCtx.sendData();
          }
        } else {
          idle();
        }
        break;
      case MAC_MODE_SNAV:
        macCtx.resetNav();
        doDifs();
        break;
      case MAC_MODE_SWFACK:
        txCtx.retry();
        break;
      case MAC_MODE_SWITCH:
        if (txCtx == null) {
          /** we are returning to our home channel (txCtx is null) */
          macCtx.setChannel(config.homeChannel);
          if (log.isDebugEnabled())
            log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
                + "): Switch to channel " + macCtx.channel + " complete; pump new packet.");

          doDifs();
          netEntity.endSend(null, macCtx.netId, null);
        } else {
          /** we finished the channel switch and now we can send the packet. */
          /** update/reset the mac state */
          macCtx.setChannel(txCtx.channel);
          macCtx.setMode(MAC_MODE_SIDLE);

          if (log.isDebugEnabled())
            log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
                + "): Switch to channel " + macCtx.channel + " complete.");

          if (!macCtx.isCarrierIdle()) {
            /** MAC is not idle; process a backoff */
            macCtx.setBackoff();
          }
          /** make DIFS and start TX */
          doDifs();

          //processEvent(Event.MAC_CHANNEL, macCtx.channel);
          /*
          if (null != txCtx) {
            txCtx.sendData();
          } else {
            idle();
          }
          */
        }
        break;
      default:
        throw new RuntimeException("unexpected mode: " + macCtx.getMode());
    }
  }
}
