package jist.swans.mac;

import java.util.Arrays;

import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;
import jist.swans.net.NetInterface;
import jist.swans.rate.RateControlAlgorithmIF;
import jist.swans.rate.ConstantRate;
import jist.swans.phy.Phy802_11;
import jist.swans.phy.PhyMessage;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.runtime.JistAPI;
import jist.runtime.Main;
import org.apache.log4j.Logger;

/**
 * This is a MAC which realizes the Distributed Coordination Function (DCF).
 * Basically it is 802.11 without RTS/CTS and without support of infrastructure.
 *
 * @author Zubow
 */
public class MacDcf extends AbstractMac implements MacInterface.MacDcf {

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

	public static final Logger logBo = Logger
			.getLogger("jist.swans.mac.MacDcf.Bo");

	public static interface NetworkAllocationVector {

		/**
		 * Clear the virtual carrier sense (network allocation vector).
		 */
		void resetNav();

		/**
		 * Return current network allocation vector.
		 *
		 * @return current network allocation vector (absolute).
		 */
		long getNav();

		/**
		 * Update/Set the nav.
		 *
		 * @param nav
		 *            the nav as absolute point in time.
		 * @param initiator
		 * @return whether the nav was changed
		 */
		boolean setNav(long nav, MacAddress initiator);

		/**
		 * Return whether the virtual carrier sense (network allocation vector)
		 * indicates that the channel is reserved.
		 *
		 * @return virtual carrier sense
		 */
		boolean waitingNav();

		/**
		 * Remove the nav set by the given initiator and shorten it, if
		 * necessary
		 *
		 * @param initiator
		 * @return whether the resulting nav was changed
		 */
		boolean shortenNav(long nav, MacAddress initiator);
	}

	/**
	 * Simple, one-component nav implementation
	 *
	 * @author kurth
	 */
	public class SimpleNav implements NetworkAllocationVector {
		/** virtual carrier sense; next time when network available. */
		protected long nav;

		public SimpleNav() {
			resetNav();
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.NetworkAllocationVector#resetNav()
		 */
		public void resetNav() {
			this.nav = -1;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.NetworkAllocationVector#setNav(long,
		 *      jist.swans.mac.MacAddress)
		 */
		public boolean setNav(long nav, MacAddress initiator) {
			if (nav > getNav()) {
				this.nav = nav;

				if (navChangedEvent != null && navChangedEvent.isActive())
					navChangedEvent.handle(nav, initiator);

				return true;
			}

			return false;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.NetworkAllocationVector#getNav()
		 */
		public long getNav() {
			return nav;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.NetworkAllocationVector#waitingNav()
		 */
		public boolean waitingNav() {
			return nav > JistAPI.getTime();
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.MacDcf.NetworkAllocationVector#shortenNav(jist.swans.mac.MacAddress)
		 */
		public boolean shortenNav(long nav, MacAddress initiator) {
			// not supported
			return false;
		}
	}

	/**
	 * Nav implementation that supports shortening.
	 *
	 * @author kurth
	 */
	public class ShorteningNav implements NetworkAllocationVector {
		/** nav for each station, index is the mac id */
		protected long[] nav;

		/**
		 * cached max of
		 *
		 * {@link #nav}
		 */
		protected long max;

		public ShorteningNav() {
			nav = new long[31];
			resetNav();
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.MacDcf.NetworkAllocationVector#getNav()
		 */
		public long getNav() {
			return max;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.MacDcf.NetworkAllocationVector#resetNav()
		 */
		public void resetNav() {
			max = -1;
			Arrays.fill(nav, -1);
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.MacDcf.NetworkAllocationVector#setNav(long,
		 *      jist.swans.mac.MacAddress)
		 */
		public boolean setNav(long nav, MacAddress initiator) {
			int idx = (int) initiator.getId();
			try {
				this.nav[idx] = nav;
			} catch (ArrayIndexOutOfBoundsException e) {
				long[] tmp = new long[idx + 31];
				Arrays.fill(tmp, -1);
				System.arraycopy(this.nav, 0, tmp, 0, this.nav.length);
				this.nav = tmp;
				return this.setNav(nav, initiator);
			}

			if (nav > max) {
				max = nav;

				if (navChangedEvent != null && navChangedEvent.isActive())
					navChangedEvent.handle(nav, initiator);

				return true;
			}

			return false;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.MacDcf.NetworkAllocationVector#waitingNav()
		 */
		public boolean waitingNav() {
			return max > JistAPI.getTime();
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see jist.swans.mac.MacDcf.NetworkAllocationVector#shortenNav(jist.swans.mac.MacAddress)
		 */
		public boolean shortenNav(long nav, MacAddress initiator) {
			try {
				int idx = (int) initiator.getId();
				long oldNav = this.nav[idx];

				// allow only shortening
				if (nav >= oldNav)
					return false;
				this.nav[idx] = nav;

				// if it was not the max, there is no need to shorten ...
				if (oldNav < max)
					return false;

				// find new max
				max = -1;
				for (int i = 0; i < this.nav.length; i++)
					max = (this.nav[i] > max ? this.nav[i] : max);

				return (oldNav > max ? true : false);
			} catch (ArrayIndexOutOfBoundsException e) {
				return false;
			}
		}
	}

	/** Invalid sequence number. */
	public static final short SEQ_INVALID = -1;

	/** Sequence number cache size. */
	public static final short SEQ_CACHE_SIZE = 5;

	// mac modes

	/** mac mode: idle. */
	public static final byte MAC_MODE_SIDLE = 0;

	/** mac mode: waiting for difs or eifs timer. */
	public static final byte MAC_MODE_DIFS = 1;

	/** mac mode: waiting for backoff. */
	public static final byte MAC_MODE_SBO = 2;

	/** mac mode: waiting for virtual carrier sense. */
	public static final byte MAC_MODE_SNAV = 3;

	/** mac mode: waiting for ACK packet. */
	public static final byte MAC_MODE_SWFACK = 7;

	/** mac mode: transmitting unicast DATA packet. */
	public static final byte MAC_MODE_XUNICAST = 10;

	/** mac mode: transmitting broadcast DATA packet. */
	public static final byte MAC_MODE_XBROADCAST = 11;

	/** mac mode: transmitting ACK packet. */
	public static final byte MAC_MODE_XACK = 12;

	/** mac mode: waiting for sifs timer. */
	public static final byte MAC_MODE_SIFS = 13;

	// //////////////////////////////////////////////////
	// short 802.11 lexicon:
	// slot - minimum time to sense medium
	// nav - network allocation vector (virtual carrier sense)
	// sifs - short inter frame space
	// pifs - point coordination inter frame space
	// difs - distributed inter frame space
	// eifs - extended inter frame space
	// cw - collision (avoidance) window
	// DSSS - Direct Sequence Spread spectrum
	// FHSS - Frequency Hopping Spread Spectrum
	//

	/** @return Slot time for the current MAC (unit: us). */
	public long getSlotTime() {
		return macInfo.getSlotTime();
	}

	/**
	 * @return SIFS time for the current MAC (unit: us). Includes
	 *         Rx-Tx-Turnaround time.
	 */
	public long getSifs() {
		return macInfo.getSifs();
	}

	/**
	 * @return The SIFS minus the Rx/Tx turnaround time. During this time the
	 *         radio is not deaf.
	 */
	public long getTxSifs() {
		return macInfo.getTxSifs();
	}

	/** @return PIFS time for the current MAC (unit: us). */
	public long getPifs() {
		return macInfo.getPifs();
	}

	/**
	 * @return DIFS time for the current MAC (unit: us). Contains SIFS, thus
	 *         includes Rx-Tx-Turnaround time.
	 */
	public long getDifs() {
		return macInfo.getDifs();
	}

	/**
	 * @return The DIFS time minus the Rx/Tx turnaround time. During this time
	 *         the radio is not deaf.
	 */
	public long getTxDifs() {
		return macInfo.getTxDifs();
	}

	/**
	 * 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() {
		return macInfo.getEifs(msgFactory.getSize(MacDcfMessage.TYPE_ACK));
	}

	/**
	 * @return The EIFS time minus the Rx/Tx turnaround time. During this time
	 *         the radio is not deaf.
	 */
	public long getTxEifs() {
		return macInfo.getTxEifs(msgFactory.getSize(MacDcfMessage.TYPE_ACK));
	}

	/**
	 * Get RX-TX-turnaround time depending on MAC.
	 *
	 * A wlan client is not able to send and receive (snoop on the medium) at
	 * the same point in time. When the radio switches from receiving to sending
	 * mode or vice versa, there is a small time interval where both sending and
	 * receiving are not possible (RX/TX turnaround time).
	 */
	public long getRxTxTurnaround() {
		return macInfo.getRxTxTurnaround();
	}

	/** Minimum collision window (for backoff) for the current MAC. */
	public final short cwMin() {
		return macInfo.cwMin();
	}

	/** Maximum collision window (for backoff) for the current MAC. */
	public final short cwMax() {
		return macInfo.cwMax();
	}

	// ////////////////////////////////////////////////
	// accessors
	//
	public String getModeString(byte mode) {
		switch (mode) {
			case MAC_MODE_SIDLE:
				return "IDLE";
			case MAC_MODE_DIFS:
				return "DIFS";
			case MAC_MODE_SBO:
				return "BO";
			case MAC_MODE_SNAV:
				return "NAV";
			case MAC_MODE_SWFACK:
				return "WF_ACK";
			case MAC_MODE_XUNICAST:
				return "X_UNICAST";
			case MAC_MODE_XBROADCAST:
				return "X_BROADCAST";
			case MAC_MODE_XACK:
				return "X_ACK";
			case MAC_MODE_SIFS:
				return "SIFS";
			default:
				throw new RuntimeException("unknown mode: " + mode);
		}
	}

	public static String getRadioModeString(byte mode) {
		switch (mode) {
			case Constants.RADIO_MODE_SLEEP:
				return "SLEEP";
			case Constants.RADIO_MODE_SENSING:
				return "SENS";
			case Constants.RADIO_MODE_RECEIVING:
				return "RX";
			case Constants.RADIO_MODE_TRANSMITTING:
				return "TX";
			case Constants.RADIO_MODE_IDLE:
				return "IDLE";
			default:
				throw new RuntimeException("unknown radio mode: " + mode);
		}
	}

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

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

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

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

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

	// properties

	/** MAC properties */
	protected MacInfo macInfo;

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

	/**
	 * Radio information of this station. Shortcut to the encapsulated radio
	 * info in the phy.
	 */
	protected RadioInfo radioInfo;

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

	/** rate to use with data packets (units: bytes/second). */
	protected int dataRate = Constants.BANDWIDTH_INVALID;

	/** rate to use with control packets (rts/cts/ack) (units: bytes/second). */
	protected int controlRate = Constants.BANDWIDTH_INVALID;

	/** mac address of this interface. */
	protected MacAddress localAddr;

	/** whether mac is in promiscuous mode. */
	protected boolean promisc;

	// status

	/** current mac mode. */
	protected byte mode;

	/** radio mode used for carrier sense. */
	protected byte radioMode;

	/** whether last reception had an error. */
	protected boolean needEifs;

	// timer

	/** timer identifier. */
	protected short timerId;

	// backoff

	/** backoff time remaining. */
	protected long bo;

	/** backoff start time. */
	protected long boStart;

	/** current contention window size. */
	protected short cw;

	// nav

	protected NetworkAllocationVector nav;

	// sequence numbers

	/** sequence number counter. */
	protected short seq;

	/** received sequence number cache list. */
	protected SeqEntry seqCache;

	/** size of received sequence number cache list. */
	protected byte seqCacheSize;

	/**
	 * The short (packet) retry counter is the retransmission counter for
	 * packets with a size smaller than or equal to the RTS threshold of this
	 * station.
	 */
	protected byte shortRetry;

	// current packet

	/** packet currently being transmitted. */
	protected Message packet;

	/** Message annotations */
	protected MessageAnno anno;

	/** next hop of packet currently being transmitted. */
	protected MacAddress packetNextHop;

	/** Whether to use the message annotations. */
	protected boolean useAnnotations;

	/** whether to use EIFS */
	protected boolean useEifs; 
	
	// currently received packets


  /**
	 * a Data packet, if one arrived, or null the packet is stored in
	 * {@link #receiveData} and released in {@link #sendAck}
	 */
	protected MacDcfMessage.Data dataRcvd;

	/** annotation belonging to the received data frame */
	protected MessageAnno dataRcvdAnno;

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

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

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

	/** @deprecated */
	public MacDcf(MacAddress addr, RadioInfo radioInfo,
			RateControlAlgorithmIF rateSelection) {
		this(addr, radioInfo, MacInfo.create(radioInfo.getMacType()),
				rateSelection, new Phy802_11(radioInfo));
	}

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

	public MacDcf(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo,
			RateControlAlgorithmIF rateSelection, Phy802_11 phy) {
		// properties
		this.phy = phy;
		this.rca = rateSelection;
		this.radioInfo = radioInfo;
		this.macInfo = macInfo;
		localAddr = addr;
		promisc = Constants.MAC_PROMISCUOUS_DEFAULT;
		// status
		mode = MAC_MODE_SIDLE;
		radioMode = Constants.RADIO_MODE_IDLE;
		needEifs = false;
		// timer identifier
		timerId = 0;
		// backoff
		bo = 0;
		boStart = 0;
		// assign mac type before calling cw_min()
		cw = cwMin();
		// virtual carrier sense
		nav = new SimpleNav();
		// sequence numbers
		seq = 1;
		seqCache = null;
		seqCacheSize = 0;
		// retry counts
		shortRetry = 0;
		// current packet
		packet = null;
		packetNextHop = null;
		// received packet short term memory
		dataRcvd = null;
		dataRcvdAnno = null;

		useAnnotations = true;

		if (rca == null)
			rca = new ConstantRate(Constants.BANDWIDTH_1Mbps == radioInfo
					.getBitratesSupported()[0] ? Constants.BANDWIDTH_1Mbps
					: Constants.BANDWIDTH_6Mbps);

		// proxy
		self = (MacInterface.MacDcf) JistAPI.proxy(new MacInterface.MacDcf.Dlg(
				this), MacInterface.MacDcf.class);
	}

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

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

	/**
	 * @return The local MAC address.
	 */
	public MacAddress getAddress() {
		return localAddr;
	}

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

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

	// ////////////////////////////////////////////////
	// accessors
	//

	public MacMessageFactory getMsgFactory() {
		return this.msgFactory;
	}

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

	/**
	 * @return The type of this MAC instance. See Constants.java for details.
	 */
	public short getMacType() {
		return macInfo.getMacType();
	}

	/**
	 * @return The control rate for the current (or last) transmission.
	 */
	public int getControlRate() {
		return controlRate;
	}

	/**
	 * @return The data rate for the current (or last) transmission.
	 */
	public int getDataRate() {
		return dataRate;
	}

	/** Returns the basic rate set for the current MAC in ascending order. */
	public int[] getBasicRateSet() {
		return radioInfo.getBasicRateSet();
	}

	/**
	 * Get the currently active rate selection algorithm.
	 */
	public RateControlAlgorithmIF getRateControlAlgorithm() {
		return this.rca;
	}

	/**
	 * Set the rate selection algorithm to rca.
	 */
	public void setRateControlAlgorithm(RateControlAlgorithmIF rca) {
		this.rca = rca;
	}

  /**
   * @return the useEifs
   */
  public boolean isUseEifs() {
    return useEifs;
  }

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

	/**
	 * Set promiscuous mode (whether to pass all packets through).
	 *
	 * @param promisc
	 *            promiscuous flag
	 */
	public void setPromiscuous(boolean promisc) {
		this.promisc = promisc;
	}

	public Phy802_11 getPhy() {
		return phy;
	}

	public MacInfo getMacInfo() {
		return macInfo;
	}

	public boolean getUseAnnotations() {
		return useAnnotations;
	}

	public void setUseAnnotations(boolean useAnnotations) {
		this.useAnnotations = useAnnotations;
	}

	protected void setAnno(MessageAnno anno, Object key, Object value) {
		if (!useAnnotations)
			return;

		anno.put(key, value);
	}

	/**
	 * @return the network allocation vector implementation
	 */
	public NetworkAllocationVector getNav() {
		return nav;
	}

	/**
	 * @param nav
	 *            the new network allocation vector implementation
	 */
	public void setNav(NetworkAllocationVector nav) {
		this.nav = nav;
	}

	protected byte getRetry() {
		return shortRetry;
	}

	protected void resetRetry() {
		shortRetry = 0;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return localAddr.toString();
	}

	// ////////////////////////////////////////////////
	// mac states
	//

	/**
	 * Set the current mac mode.
	 *
	 * @param mode
	 *            new mac mode
	 */
	protected void setMode(byte mode) {
		if (this.mode != mode && macModeChanged.isActive())
			macModeChanged.handle(this.mode, mode);
		
		this.mode = mode;
	}

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

	/**
	 * Return whether the mac is currently transmitting.
	 *
	 * @return whether mac is currently transmitting
	 */
	protected boolean isTransmitting() {
		switch (mode) {
			case MAC_MODE_XUNICAST:
			case MAC_MODE_XBROADCAST:
			case MAC_MODE_XACK:
				return true;
			default:
				return false;
		}
	}

	// ////////////////////////////////////////////////
	// packet queries
	//

	/**
	 * Return whether mac currently has a packet to send.
	 *
	 * @return whether mac has packet to send.
	 */
	protected boolean hasPacket() {
		return packet != null;
	}

	/**
	 * Return whether current packet is to be broadcast.
	 *
	 * @return whether current packet is to be broadcast.
	 */
	protected boolean isBroadcast() {
		return isBroadcast(packetNextHop);
	}

	/**
	 * Return whether the given packet is to be broadcast.
	 *
	 * @param nextHop
	 *            MacAddress to check
	 * @return whether current packet is to be broadcast.
	 */
	protected boolean isBroadcast(MacAddress nextHop) {
		return MacAddress.ANY.equals(nextHop);
	}

	/**
	 * Compute packet transmission time at current bandwidth.
	 *
	 * @param msg
	 *            packet to transmit
	 * @return time to transmit given packet at current bandwidth
	 */
	protected long transmitTime(Message msg, int rate) {
		return transmitTime(msg.getSize(), rate);
	}

	protected long transmitTime(int size, int rate) {
		// delegate to MacInfo, but first offer a method there to calculate tx
		// time w/o bo
		if (size == Constants.ZERO_WIRE_SIZE) {
			return Constants.EPSILON_DELAY;
		}
		return Phy802_11.transmitTime(size, rate, getMacType());
	}

	// ////////////////////////////////////////////////
	// backoff
	//

	/**
	 * Return whether there is a backoff.
	 *
	 * @return whether backoff non-zero.
	 */
	protected boolean hasBackoff() {
		return bo > 0;
	}

	/**
	 * Reset backoff counter to zero.
	 */
	protected void clearBackoff() {
		if (backoffEvent.isActive())
			backoffEvent.handle(JistAPI.getTime() - boStart);

		boStart = 0;
		bo = 0;
	}

	/**
	 * Set new random backoff, if current backoff timer has elapsed.
	 */
	protected void setBackoff() {
		if (!hasBackoff()) {
			// get a pseudorandom number in the intervall [0; cw] -->
			// see 802.11, ch. 9.2.4;
			// nextInt(n) returns a pseudorandom number in [0; n)
			bo = Constants.random.nextInt(cw + 1) * getSlotTime();
			if (Main.ASSERT)
				Util.assertion(boStart == 0);
			if (Main.ASSERT)
				Util.assertion(bo >= 0);
			if (Main.ASSERT)
				Util.assertion(bo < 1 * Constants.SECOND);
		}
		if (this.bo > cwMax() * getSlotTime())
			log.error(this + "(" + JistAPI.getTime() + "): bo=" + bo + ", cw="
					+ cw + ", slottime=" + getSlotTime() + ", hasBackoff="
					+ hasBackoff());
	}

	/**
	 * Pause the current backoff (invoked when the channel becomes busy).
	 */
	protected void pauseBackoff() {
		if (Main.ASSERT)
			Util.assertion(boStart > 0);
		if (Main.ASSERT)
			Util.assertion(bo > 0);

		long boDone = JistAPI.getTime() - boStart;
		// Only reduce backoff by a multiple of SLOT_TIME
		bo -= (boDone - boDone % getSlotTime());
		if (0 == bo) {
			// timeout and setRadioMode called at the same point in time
			bo = Constants.EPSILON_DELAY;
		}
		if (Main.ASSERT)
			Util.assertion(bo >= 0);
		if (Main.ASSERT)
			Util.assertion(bo < 1 * Constants.SECOND);

		// Show a potentially longer time of backoff waiting than what is
		// substracted from the whole bo
		if (backoffEvent.isActive())
			backoffEvent.handle(boDone);

		if (Main.ASSERT)
			Util.assertion(bo > 0);
		// Assert that bo is a multiple of the slot time
		// if (Main.ASSERT) Util.assertion(bo % getSlotTime() == 0);
		boStart = 0;
	}

	/**
	 * Perform backoff.
	 */
	protected void backoff() {
		if (Main.ASSERT)
			Util.assertion(boStart == 0);
		if (Main.ASSERT)
			Util.assertion(bo > 0);
		if (Main.ASSERT)
			Util.assertion(bo < 1 * Constants.SECOND);

		boStart = JistAPI.getTime();
		if (logBo.isDebugEnabled())
			logBo.debug("BO: " + getAddress() + ";" + boStart + ";" + bo + ";"
					+ cw);
		startTimer(bo, MAC_MODE_SBO);
	}

	/**
	 * Increase Collision Window.
	 */
	private void incCW() {
		cw = (short) Math.min(2 * cw + 1, cwMax());
		if (cwChangedEvent.isActive())
			cwChangedEvent.handle(cw);
	}

	/**
	 * Decrease Collision Windows.
	 */
	protected void decCW() {
		cw = cwMin();
		if (cwChangedEvent.isActive())
			cwChangedEvent.handle(cw);
	}

	// ////////////////////////////////////////////////
	// nav
	//

	/**
	 * Determine whether channel is idle according to both physical and virtual
	 * carrier sense.
	 *
	 * @return physical and virtual carrier sense
	 */
	protected boolean isCarrierIdle() {
		return !nav.waitingNav() && isRadioIdle();
	}

	// ////////////////////////////////////////////////
	// sequence numbers
	//

	/**
	 * Local class used to manage sequence number records.
	 */
	private class SeqEntry {
		/** src node address. */
		public MacAddress from = MacAddress.NULL;

		/** sequence number. */
		public short seq = 0;

		/** next record. */
		public SeqEntry next = null;
	}

	/**
	 * Increment local sequence counter.
	 *
	 * @return new sequence number.
	 */
	protected short incSeq() {
		seq = (short) ((seq + 1) % MacDcfMessage.Data.MAX_SEQ);
		return seq;
	}

	/**
	 * Return latest seen sequence number from given address.
	 *
	 * @param from
	 *            source address
	 * @return latest sequence number from given address
	 */
	protected short getSeqEntry(MacAddress from) {
		SeqEntry curr = seqCache, prev = null;
		short seq = SEQ_INVALID;
		while (curr != null) {
			if (from.equals(curr.from)) {
				seq = curr.seq;
				if (prev != null) {
					prev.next = curr.next;
					curr.next = seqCache;
					seqCache = curr;
				}
				break;
			}
			prev = curr;
			curr = curr.next;
		}
		return seq;
	}

	/**
	 * Update latest sequence number entry for given address.
	 *
	 * @param from
	 *            source address
	 * @param seq
	 *            latest sequence number
	 */
	protected void updateSeqEntry(MacAddress from, short seq) {
		SeqEntry curr = seqCache, prev = null;
		while (curr != null) {
			if (seqCacheSize == SEQ_CACHE_SIZE && curr.next == null) {
				curr.from = from;
				curr.seq = seq;
				break;
			}
			if (from.equals(curr.from)) {
				curr.seq = seq;
				if (prev != null) {
					prev.next = curr.next;
					curr.next = seqCache;
					seqCache = curr;
				}
				break;
			}
			prev = curr;
			curr = curr.next;
		}
		if (curr == null) {
			if (Main.ASSERT)
				Util.assertion(seqCacheSize < SEQ_CACHE_SIZE);
			curr = new SeqEntry();
			curr.from = from;
			curr.seq = seq;
			curr.next = seqCache;
			seqCache = curr;
			seqCacheSize++;
		}
	}

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

	protected void adjustBitrates(Message packet, MessageAnno anno,
			MacAddress dest, boolean bufferedPacket, int packetSize) {
		/*
		 * Bit-rate selection algorithm chooses bitrate(s) for next packet. Pass
		 * the total count of tries, but at least 0. Only shortRetry OR
		 * longRetry is used during packet transmission.
		 */
		dataRate = rca.getNextDataRate(packet, anno, dest, packetSize,
				getRetry());
		controlRate = rca.getControlRate(dest, dataRate);
		if(log.isDebugEnabled())
		  log.debug("Bitrates: " + dataRate + " / " + controlRate);
	}

	// MacInterface
	public void send(Message msg, MacAddress nextHop, MessageAnno anno) {
		// If the following assert fires, try to use setUseAnnotations()
		// Hint: ClickRouter uses annos, so setUseAnnotations(true) during
		// bootstrap
		if (Main.ASSERT) {
      Util.assertion(nextHop != null);
//      if (log.isDebugEnabled())
//        log.debug("useAnnotations:" + useAnnotations + " anno:" + anno);
      Util.assertion(useAnnotations ? (null != anno) : (null == anno));
      Util.assertion(!hasPacket());
      // TODO why does this assert fire with MacEDCF?
//      Util.assertion(Constants.RADIO_MODE_TRANSMITTING != this.radioMode
//          || MAC_MODE_XACK == this.mode);
    }
		this.packet = msg;
		this.anno = anno;
		this.packetNextHop = nextHop;

		if (mode == MAC_MODE_SIDLE) {
			if (!isCarrierIdle()) {
				setBackoff();
			}
			doDifs();
		}
	}

	protected void doDifs() {
        // function completly overriden in MacEDCF, please propagate changes
		if (isRadioIdle()) {
			if (nav.waitingNav()) {
				startTimer(nav.getNav() - JistAPI.getTime(), MAC_MODE_SNAV);
			} else {
			  // TODO event for eifs
				startTimer(needEifs ? getTxEifs() : getTxDifs(), MAC_MODE_DIFS);
			}
		} else {
			idle();
		}
	}

	/*
	 * (non-Javadoc)
	 * @see jist.swans.mac.MacInterface.MacDcf#cfDone(boolean, boolean)
	 */
	public void cfDone(boolean backoff, boolean delPacket) {
	    // function completely overridden in MacEDCF, please propagate changes
		if (delPacket) {
			if (netEntity != null)
				netEntity.endSend(packet, netId, anno);

			// 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;
		}
		if (backoff) {
			setBackoff();
		}
		doDifs();
	}

	protected void sendPacket() {
	  sendData();
	}

	protected void sendData() {
		if (isBroadcast()) {
			sendDataBroadcast();
		} else {
			sendDataUnicast();
		}
	}

	/**
   * TODO Read this:
   *
   * Send a broadcast packet. In 802.11, clause 9.6, it is demanded that all
   * broadcast packets be sent at a rate contained in the BSS basic rate set. As
   * of now we are still missing a concept of BSS in the simulator. The basic
   * rate set (in <code>RadioInfo</code>) is usually set to the minimal set
   * possible. This could be changed. Problem 1: routing algos that want to do
   * link probing at all rates would trip over castrating them here... Problem
   * 2: the control rate returned by the RCA can be based on stats and could
   * always return 24 Mbps, for example, even if packets at that rate (always)
   * fail - the RCA will not know bc there is no feedback to broadcast packets.
   *
   * TODO Send broadcast msg at a basic rate (control rate). See note above.
   */
	protected void sendDataBroadcast() {
		// Do bit-rate selection according to algorithm.
		adjustBitrates(packet, anno, packetNextHop, true, packet.getSize());
		int basicRate = dataRate; // macInfo.getHighestBasicRate(dataRate);

		// create data packet
		MacDcfMessage data = msgFactory.createDataBroadcast(packetNextHop,
				localAddr, 0, 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, basicRate, phy);
		long duration = phyMessage.txDuration();
		if (null != anno)
			anno.put(MessageAnno.ANNO_MAC_BITRATE, Integer.valueOf(basicRate));

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

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

		if (sendEvent.isActive())
			sendEvent.handle(data, anno, duration + Constants.EPSILON_DELAY,
			    basicRate, false);

		JistAPI.sleep(duration + Constants.TX_RX_TURNAROUND);

		setAnno(anno, MessageAnno.ANNO_MAC_SUCCESS, Boolean.TRUE);
		setAnno(anno, MessageAnno.ANNO_MAC_RETRIES, Byte.valueOf((byte) 0));
		self.cfDone(true, true);
	}

	protected void sendDataUnicast() {
	    // function completely overridden in MacEDCF, please propagate changes
		sendDataUnicast(true);
	}

	protected void sendDataUnicast(boolean waitForAck) {
        // function completely overridden in MacEDCF, please propagate changes

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

		// create data packet
		// duration is for NAV = max. duration of ACK after end of data frame
		// that's why use lowest basic rate to calculate the NAV!
		int duration = (int) (getSifs() + Constants.PROPAGATION + transmitTime(
				msgFactory.getSize(MacDcfMessage.TYPE_ACK), macInfo
						.getBasicRateSet()[0]));
		MacDcfMessage data = msgFactory.createDataUnicast(packetNextHop,
				localAddr, duration, (getRetry() > 0) ? seq : incSeq(),
				getRetry() > 0, 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 phyDuration = phyMessage.txDuration();
		if (null != anno)
			anno.put(MessageAnno.ANNO_MAC_BITRATE, Integer.valueOf(dataRate));

		// if (sendEvent.isActive())
		// sendEvent.handle(data, anno, delay + phyDuration +
		// Constants.EPSILON_DELAY, dataRate, getRetry() > 0);

		if (log.isDebugEnabled())
			log.debug(getAddress().addr + "->" + packetNextHop.addr + ";"
					+ JistAPI.getTime() + ";" + dataRate + ";(" + getRetry()
					+ ")\n");

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

		// wait for EOT, and schedule ACK timer
		JistAPI.sleep(delay);
		setMode(MAC_MODE_XUNICAST);

		if (sendEvent.isActive())
			sendEvent.handle(data, anno, phyDuration + Constants.EPSILON_DELAY,
					dataRate, getRetry() > 0);

		JistAPI.sleep(phyDuration + Constants.TX_RX_TURNAROUND);
		if (waitForAck)
			self.startTimer(duration + getSlotTime(), MAC_MODE_SWFACK);
	}

	protected void sendAck() {
		// 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(), 0,
				dataRcvd, dataRcvdAnno);
		// not needed anymore
		dataRcvd = null;
		dataRcvdAnno = null;
    MessageAnno ackAnno = new MessageAnno();

		// 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, ackAnno, delay, duration, Constants.TX_RX_TURNAROUND);

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

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

		JistAPI.sleep(duration + Constants.TX_RX_TURNAROUND);

		self.cfDone(false, false);
	}

	// ////////////////////////////////////////////////
	// retry
	//

	protected void retry() {
		if (log.isDebugEnabled())
			log.debug(localAddr.addr + ": short retry:" + shortRetry + " (of "
					+ macInfo.getRetryLimitShort() + ")");
		if (shortRetry < macInfo.getRetryLimitShort()) {
			shortRetry++;
			if (retryEvent.isActive())
				retryEvent.handle(this.packet, this.anno, shortRetry, true);
			retryYes();
		} else {
			setAnno(anno, MessageAnno.ANNO_MAC_RETRIES, Byte
					.valueOf(shortRetry));
			if (macTxFinished.isActive())
				macTxFinished.handle(packet, anno, false, shortRetry);
			// Report failed transmission to RCA
			reportDataTxToRca(Constants.RADIO_RECV_STATUS_LOST.byteValue());
			resetRetry();
			retryNo();
		}
	}

	protected void retryYes() {
		incCW();
		setBackoff();
		doDifs();
	}

	protected void retryNo() {
		setAnno(anno, MessageAnno.ANNO_MAC_SUCCESS, Boolean.FALSE);
		if (discardEvent.isActive())
			discardEvent.handle(packet, anno);

		decCW();
		cfDone(true, true);
	}

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

	// 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()
		if (useEifs)
		  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);
			}
		}
	}

	// MacInterface
	public void receive(Message msg, MessageAnno anno) {
		if (Main.ASSERT) {
			Util.assertion(null != msg);
			Util.assertion(useAnnotations ? (null != anno) : (null == anno));
			Util.assertion(msg instanceof PhyMessage);
			Util.assertion(Constants.RADIO_MODE_IDLE != this.radioMode);
		}

		PhyMessage phyMsg = (PhyMessage) msg;

		// ///////////////////////////////////////////////////////
		//
		// The received message is now a PhyMessage.
		// Use its header information and then process the MacDcfMessage.
		//
		rca.reportPacketRx(phyMsg);
		//
		// ///////////////////////////////////////////////////////

		if (null != anno) {
			anno.put(MessageAnno.ANNO_MAC_BITRATE, phyMsg.getPayloadRateObj());
			anno.put(MessageAnno.ANNO_MAC_DURATION, Long.valueOf(phyMsg
					.txDuration()));
      anno.put(MessageAnno.ANNO_MSG_ID, phyMsg.getId());
		}

		receivePacket((MacDcfMessage) phyMsg.getPayload(), anno);
	}

	protected void receivePacket(MacDcfMessage msg, MessageAnno anno) {
		needEifs = false;
		MacAddress dst = msg.getDst();

		if (localAddr.equals(dst)) {
			switch (msg.getType()) {
				case MacDcfMessage.TYPE_ACK:
					receiveAck((MacDcfMessage.Ack) msg, anno);
					break;
				case MacDcfMessage.TYPE_DATA:
					receiveData((MacDcfMessage.Data) msg, anno);
					break;
				default:
					throw new RuntimeException("illegal frame type");
			}
		} else if (MacAddress.ANY.equals(dst)) {
			switch (msg.getType()) {
				case MacDcfMessage.TYPE_DATA:
					receiveData((MacDcfMessage.Data) msg, anno);
					break;
				default:
					throw new RuntimeException("illegal frame type");
			}
		} else {
			// does not belong to node
			receiveForeign(msg, anno);
		}
	}

	protected void reportDataTxToRca(byte status) {
		rca.reportPacketTx(packetNextHop, MacDcfMessage.TYPE_DATA, packet
				.getSize(), dataRate, shortRetry + 1, 0, status);
	}

	protected void receiveAck(MacDcfMessage.Ack ack, MessageAnno anno) {
		if (mode == MAC_MODE_SWFACK) {

			// DATA successful:
			// Use an extra method to override it in Mac802_11!
			reportDataTxToRca(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, Byte
					.valueOf(getRetry()));
			if (macTxFinished.isActive())
				macTxFinished.handle(packet, anno, true, getRetry());

			resetRetry();
			decCW();
			cfDone(true, true);
		}
	}

	protected void receiveData(MacDcfMessage.Data msg, MessageAnno anno) {
		// not in standard, but ns-2 does.
		// decCW();
		// shortRetry = 0;
		if (isAwaitingResponse())
			return;

		if (MacAddress.ANY.equals(msg.getDst())) {
			if (receiveEvent.isActive())
				receiveEvent.handle(msg, anno, null == anno ? -1 : ((Long) anno
						.get(MessageAnno.ANNO_MAC_DURATION)).longValue()
						+ getRxTxTurnaround());

			netEntity.receive(msgFactory.getPayload(msg), msg.getSrc(), netId,
					false, anno);
			cfDone(false, false);
		} else {
			cancelTimer();
			dataRcvd = msg;
			dataRcvdAnno = anno;

			// duplicate suppression
			if (msg.getRetry() && getSeqEntry(msg.getSrc()) == msg.getSeq()) {
				if (duplicateEvent.isActive())
					duplicateEvent.handle(msg, anno, localAddr);
			} else {
				if (forwarderEvent.isActive())
					forwarderEvent.handle(msg, anno, localAddr);
				if (receiveEvent.isActive())
					receiveEvent.handle(msg, anno, null == anno ? -1
							: ((Long) anno.get(MessageAnno.ANNO_MAC_DURATION))
									.longValue()
									+ getRxTxTurnaround());

				updateSeqEntry(msg.getSrc(), msg.getSeq());
				if (netEntity != null) {
					netEntity.receive(msgFactory.getPayload(msg), msg.getSrc(),
							netId, false, anno);
				}
			}

			endReceiveData(msg);
		}
	}

	/**
	 * Defined, to be able to override behavior for different ack policies.
	 */
	protected void endReceiveData(Message msg) {
		/*
		 * start the timer only after handling the events, else they are delayed
		 */
		startTimer(getTxSifs(), MAC_MODE_SIFS);
	}

	protected void receiveForeign(MacDcfMessage msg, MessageAnno anno) {
		updateNav(msg, anno);

		// e.g. used by Javis event handler
		if (msg.getType() == MacDcfMessage.TYPE_DATA) {
			if (receiveForeignEvent.isActive())
				receiveForeignEvent.handle(msg, anno, null == anno ? -1
						: ((Long) anno.get(MessageAnno.ANNO_MAC_DURATION))
								.longValue()
								+ getRxTxTurnaround());

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

				MacDcfMessage.Data macMsg = (MacDcfMessage.Data) msg;
				netEntity.receive(msgFactory.getPayload(msg), macMsg.getSrc(),
						netId, true, anno);
			}
		}
	}

	protected void updateNav(MacDcfMessage msg, MessageAnno anno) {
		MacAddress initiator = null;

		if (msg.getType() == MacDcfMessage.TYPE_DATA)
			initiator = ((MacDcfMessage.Data) msg).getSrc();
		else
			initiator = ((MacDcfMessage.Ack) msg).getDst();

		boolean changed = nav.setNav(JistAPI.getTime() + msg.getDuration()
				+ Constants.EPSILON_DELAY, initiator);

		if (changed && isRadioIdle() && hasPacket()) {
			startTimer(nav.getNav() - JistAPI.getTime(), MAC_MODE_SNAV);
		}
	}

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

	// MacInterface
	public void setRadioMode(byte mode) {
		if (this.radioMode != mode && radioModeChanged.isActive())
			radioModeChanged.handle(this.radioMode, mode);

		this.radioMode = mode;
		switch (mode) {
			case Constants.RADIO_MODE_IDLE:
				radioIdle();
				break;
			case Constants.RADIO_MODE_SENSING:
				radioBusy();
				break;
			case Constants.RADIO_MODE_RECEIVING:
				radioBusy();
				/*
				 * todo: ExaminePotentialIncomingMessage(newMode, receiveDuration,
				 * potentialIncomingPacket);
				 */
				break;
			case Constants.RADIO_MODE_SLEEP:
				radioBusy();
				break;
		}
	}

	/** @return Whether radio is in idle mode. */
	protected boolean isRadioIdle() {
		return radioMode == Constants.RADIO_MODE_IDLE;
	}

	protected void radioBusy() {
		switch (mode) {
			case MAC_MODE_SBO:
				pauseBackoff();
				idle();
				break;
			case MAC_MODE_DIFS:
			case MAC_MODE_SNAV:
				idle();
				break;
			case MAC_MODE_SIDLE:
			case MAC_MODE_SIFS:
			case MAC_MODE_SWFACK:
			case MAC_MODE_XBROADCAST:
			case MAC_MODE_XUNICAST:
			case MAC_MODE_XACK:
				break;
			default:
				// todo: really unexpected? check the states above as well
				throw new RuntimeException("unexpected mode: "
						+ getModeString(mode));
		}
	}

	protected void radioIdle() {
		switch (mode) {
			case MAC_MODE_SIDLE:
			case MAC_MODE_SNAV:
				// TODO why wait a DIFS after reception of a frame?
				doDifs();
				break;
			case MAC_MODE_SWFACK:
			case MAC_MODE_SIFS:
			case MAC_MODE_DIFS:
			case MAC_MODE_SBO:
			case MAC_MODE_XUNICAST:
			case MAC_MODE_XBROADCAST:
			case MAC_MODE_XACK:
				// don't care
				break;
			default:
				// todo: really unexpected? check the states above as well
				throw new RuntimeException("unexpected mode: "
						+ getModeString(mode));
		}
	}

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

	// MacInterface
	public void startTimer(long delay, byte mode) {
		if (Main.ASSERT)
			Util.assertion(delay >= 0);
		cancelTimer();
		setMode(mode);
		JistAPI.sleep(delay);
		self.timeout(timerId);
	}

	/**
	 * Cancel timer event, by incrementing the timer identifer.
	 */
	protected void cancelTimer() {
		timerId++;
	}

	protected void idle() {
		cancelTimer();
		setMode(MAC_MODE_SIDLE);
	}

	// MacInterface
	public void timeout(int timerId) {
		if (timerId != this.timerId)
			return;
		switch (mode) {
			case MAC_MODE_SIDLE:
				idle();
				break;
			case MAC_MODE_SIFS:
				if (null != dataRcvd) {
					sendAck();
				} else if (hasPacket()) {
					if (Main.ASSERT)
						Util.assertion(null == dataRcvd);
					sendData();
				} else
					throw new RuntimeException("inconsistent state in SIFS!");
				break;
			case MAC_MODE_DIFS:
				if (hasPacket()) {
					if (hasBackoff()) {
						backoff();
					} else {
						sendPacket();
					}
				} else {
					// Finish DIFS interval before going into BO or IDLE
					JistAPI.sleep(getRxTxTurnaround());
					if (hasBackoff()) {
						backoff();
					} else {
						idle();
					}
				}
				break;
			case MAC_MODE_SBO:
				if (Main.ASSERT)
					Util.assertion(boStart + bo == JistAPI.getTime());
				clearBackoff();
				// TODO: is this OK???
				// setMode(MAC_MODE_SIDLE);
				if (hasPacket()) {
					sendPacket();
				} else {
					idle();
				}
				break;
			case MAC_MODE_SNAV:
				nav.resetNav();
				doDifs();
				break;
			case MAC_MODE_SWFACK:
				// DATA failed:
				// reportDataTxToRca(Constants.RADIO_RECV_STATUS_LOST.byteValue());
				// Commented because: Only report failures once for now
				retry();
				break;
			default:
				throw new RuntimeException("unexpected mode: " + mode);
		}
	}

}
