package jist.swans.mac;

import jist.runtime.Main;
import jist.swans.Constants;
import jist.swans.misc.Util;
import jist.swans.phy.Phy802_11;

public class MacTimes {

	protected MacInfo macInfo;

//	protected Phy802_11 phy;

	/** cache for average backoffs [bitrate][] */
	protected long[][] avgBackoff;

	/** cache for average tx durations [bitrate][][] */
	protected long[][][] durations;

	// /////////////////////////////////////////
	// Constructor
	// 

	public MacTimes(MacInfo macInfo) {
		this.macInfo = macInfo;
//		this.phy = phy;
		avgBackoff = new long[54/*macInfo.bitrates.length*/ + 1][];
		durations = new long[54/*macInfo.bitrates.length*/ + 1][][];
	}

	// /////////////////////////////////////////
	// Calculation methods
	// 

	/** Calculate the avg. backoff for the given try. */
	public long calcAvgBackoff(int payloadRate, int tryN) {
		try {
			return avgBackoff[payloadRate / Constants.BANDWIDTH_1Mbps][tryN];
		} catch (NullPointerException e) {
			avgBackoff[payloadRate / Constants.BANDWIDTH_1Mbps] = new long[macInfo
					.getRetryLimitShort() + 1];
			for (int i = 0; i < macInfo.getRetryLimitShort(); i++) {
				int cw = macInfo.cwMin();

				/* there is backoff, even for the first packet */
				for (int x = 0; x < i; x++) {
					cw = Math.min(macInfo.cwMax, cw * 2 + 1);
				}
				avgBackoff[payloadRate / Constants.BANDWIDTH_1Mbps][i] = macInfo
						.getSlotTime()
						* cw / 2;
			}

			return avgBackoff[payloadRate / Constants.BANDWIDTH_1Mbps][tryN];
		}
	}

	/**
	 * The John C. Bicket version of the transmission time calculation.
	 * 
	 * @param macInfo
	 *            802.11 configuration information
	 * @param length
	 *            length in bytes of the packet to transmit
	 * @param dataRate
	 *            transmission bit-rate of the payload of the DATA frame
	 * @param controlRate
	 *            transmission bit-rate of the payload of RTS/CTS/ACK frames
	 * @param useRtsCts
	 *            true if the duration of an RTS/CTS exchange is to be added to
	 *            the total transmission time; else false
	 * @param shortRetries
	 *            if shouldRTS=true, then this is the number of transmission
	 *            tries of the RTS frame; else it is the number of tx tries of
	 *            the DATA frame
	 * @param longRetries
	 *            if useRtsCts=true, then this is the number of transmission
	 *            tries of the DATA frame; else it must be 0
	 * @param includeBackoff
	 *            TODO
	 * @return The time of an atomic unicast data packet transmission, possibly
	 *         including CTS or RTS/CTS frames, and including an averaged
	 *         backoff time based on the minimum contention window size
	 *         specified in macInfo.
	 */
	public static long calc_usecs_unicast_packet(MacInfo macInfo, int length,
			int dataRate, int controlRate, boolean useRtsCts, int shortRetries,
			int longRetries, boolean includeBackoff) {

		MacMessageFactory msgFactory = new MacMessageFactory.M802_11();
		boolean rts = false;
		boolean cts = false;
		long txTime = 0;
		int cw = macInfo.cwMin();
		// tx rate for RTS/CTS/ACK frames
		int rtsCtsRate = controlRate;
		long ackDuration = Phy802_11.transmitTime(msgFactory
				.getSize(MacDcfMessage.TYPE_ACK), rtsCtsRate, macInfo
				.getMacType());

		// check wether to take into consideration RTS/CTS or CTS-to-Self
		if (useRtsCts) {
			rts = true;
			cts = true;
		}
		// TODO
		// if(shouldCtsToSelf()) {
		// cts = true;
		// }

		if (rts || cts) {
			int rtsCtsDuration = 0;

			if (rts) /* SIFS + CTS */
				rtsCtsDuration += ackDuration + macInfo.getSifs();

			// Oliver: Which packet is meant here?
			rtsCtsDuration += Phy802_11.transmitTime(length, dataRate, macInfo
					.getMacType());

			if (cts) /* SIFS + ACK */
				rtsCtsDuration += ackDuration + macInfo.getSifs();

			txTime += (shortRetries + 1) * rtsCtsDuration;
		}

		txTime += macInfo.getDifs();
		txTime += (longRetries + 1) * (macInfo.getSifs() + ackDuration);
		txTime += (longRetries + 1)
				* Phy802_11
						.transmitTime(length, dataRate, macInfo.getMacType());

		for (int i = 0; i <= shortRetries + longRetries; ++i) {
			cw = Math.min(macInfo.cwMax, (cw + 1) * 2);
			long bo = (macInfo.getSlotTime() * cw / 2);
			txTime += bo;
		}

		return txTime;
	}

	 public long avgTransmitTimeUnicastPacket(
	      int dataLength, int dataRate, int controlRate, boolean useRtsCts,
	      boolean useCtsToSelf, int rtsCtsTries, int dataTries,
	      boolean includeBackoff) {
	   return avgTransmitTimeUnicastPacket(this.macInfo,
	       dataLength, dataRate, controlRate, useRtsCts,
	       useCtsToSelf, rtsCtsTries, dataTries,
	       includeBackoff);
	 }
	/**
	 * TODO use caching for performance!!
	 * 
	 * @param macInfo
	 *            802.11 configuration information
	 * @param dataLength
	 *            length in bytes of the packet to transmit including all MAC
	 *            headers (LLC and MAC)
	 * @param dataRate
	 *            transmission bit-rate of the payload of the DATA frame
	 * @param controlRate
	 *            transmission bit-rate of the payload of RTS/CTS/ACK frames
	 * @param useRtsCts
	 *            true if the duration of an RTS/CTS exchange is to be added to
	 *            the total transmission time; else false
	 * @param useCtsToSelf
	 *            true if CTS-to-self (ptotection) should be used; else false
	 * @param rtsCtsTries
	 *            if shouldRTS=true, then this is the number of transmission
	 *            tries of the RTS frame; else it is the number of tx tries of
	 *            the DATA frame
	 * @param dataTries
	 *            if useRtsCts=true, then this is the number of transmission
	 *            tries of the DATA frame; else it must be 0
	 * @param includeBackoff
	 *            whether or not to include the average backoff time per
	 *            transmission try in the overall transmission time
	 * @return The time of an atomic unicast data packet transmission, possibly
	 *         including CTS or RTS/CTS frames, and/or an averaged backoff time
	 *         based on the minimum contention window (CW) size specified in
	 *         macInfo.
	 */
	public static long avgTransmitTimeUnicastPacket(MacInfo macInfo,
			int dataLength, int dataRate, int controlRate, boolean useRtsCts,
			boolean useCtsToSelf, int rtsCtsTries, int dataTries,
			boolean includeBackoff) {

		// Sanity checks
		if (Main.ASSERT) {
			Util.assertion(dataTries > 0);
			Util.assertion(!(useRtsCts && useCtsToSelf));
			Util.assertion((dataLength <= macInfo.thresholdRts) || useRtsCts);
			Util.assertion(!useRtsCts || (rtsCtsTries > 0));
		}

		MacMessageFactory msgFactory = new MacMessageFactory.M802_11();
		boolean rts = false;
		boolean cts = false;
		long txTime = 0;

		// check wether to take into consideration RTS/CTS or CTS-to-Self
		if (useRtsCts) {
			rts = true;
			cts = true;
		}
		if (useCtsToSelf) {
			cts = true;
		}

		if (rts || cts) {
			long rtsCtsDuration = 0;
			if (rts)
				rtsCtsDuration += macInfo.getDifs()
						+ Phy802_11.transmitTime(msgFactory
								.getSize(Mac802_11Message.TYPE_RTS),
								controlRate, macInfo.getMacType());
			if (cts)
				rtsCtsDuration += macInfo.getSifs()
						+ Phy802_11.transmitTime(msgFactory
								.getSize(Mac802_11Message.TYPE_CTS),
								controlRate, macInfo.getMacType());
			txTime += rtsCtsTries * rtsCtsDuration;
		}

		long ackDuration = Phy802_11.transmitTime(msgFactory
				.getSize(MacDcfMessage.TYPE_ACK), controlRate, macInfo
				.getMacType());
		long dataDuration = Phy802_11.transmitTime(dataLength, dataRate,
				macInfo.getMacType());
		// after RTS/CTS use only SIFS; else DIFS
		long ifs = useRtsCts ? macInfo.getSifs() : macInfo.getDifs();
		txTime += dataTries
				* (ifs + dataDuration + macInfo.getSifs() + ackDuration);

		int cw = macInfo.cwMin();
		/*
		 * In the current implementation, a data frame retransmission for a
		 * frame with length > RTS threshold triggers a whole new frame exchange
		 * sequence, thus incl. RTS and CTS packets. In other words, one
		 * transmission try of a DATA frame includes one try for RTS/CTS.
		 * 
		 * Example: rtsCtsTries = 3, dataTries = 2
		 * 		   STA1      STA2
		 * 	[difs+backoff]
		 * 			RTS --->		[sifs]
		 * 				<-X- CTS
		 * 	[cts_timeout]
		 * 	[difs+backoff]
		 * 			RTS --->		[sifs]
		 * 	[sifs]		<--- CTS
		 * 			DAT --->		[sifs]
		 * 				<-X- ACK
		 * 	[ack_timeout]
		 * 	[difs+backoff]
		 * 			RTS --->		[sifs]
		 * 	[sifs]		<--- CTS
		 * 			DAT --->		[sifs]
		 * 				<--- ACK
		 * 
		 * Whether the RTS or CTS frame, or the DATA or ACK frame get lost,
		 * respectively, does not play a role.
		 */
		// either rtsCtsTries >= dataTries, or rtsCtsTries = 0 && dataTries > 0
		if (includeBackoff) {
			int backoffCt = Math.max(rtsCtsTries, dataTries);
			for (int i = 0; i < backoffCt; ++i) {
				long bo = ((macInfo.getSlotTime() * cw) / 2);
				txTime += bo;
				cw = Math.min(macInfo.cwMax(), cw * 2 + 1);
			}
		}
		return txTime;
	}

	/**
	 * @param macInfo
	 *            802.11 configuration information
	 * @param length
	 *            length in bytes of the packet to transmit including all MAC
	 *            headers (LLC and MAC)
	 * @param dataRate
	 *            transmission bit-rate of the payload of the DATA frame
	 * @param controlRate
	 *            transmission bit-rate of the payload of RTS/CTS/ACK frames
	 * @param useRtsCts
	 *            true if the duration of an RTS/CTS exchange is to be added to
	 *            the total transmission time; else false
	 * @param useCtsToSelf
	 *            true if CTS-to-self (ptotection) should be used; else false
	 * @return The time of an atomic unicast data packet transmission, possibly
	 *         including a CTS or an RTS and a CTS frame, excluding backoff.
	 */
	public static long transmitTimeUnicastPacket(MacInfo macInfo, int length,
			int dataRate, int controlRate, boolean useRtsCts,
			boolean useCtsToSelf) {

		int shortTries = 1; // either for an RTS/CTS exchange or the DATA frame
		int longTries = useRtsCts || length > macInfo.thresholdRts ? 1 : 0;
		return avgTransmitTimeUnicastPacket(macInfo, length, dataRate,
				controlRate, useRtsCts, useCtsToSelf, shortTries, longTries,
				false);
	}

//	/**
//	 * @param length
//	 *            Packet length in bytes
//	 * @param dataRate
//	 *            Bit-rate at which packet is to be transmitted
//	 * @param shortRetries
//	 *            Number of retries for packet smaller than threshold
//	 * @param longRetries
//	 *            Number of retries for packet larger than threshold
//	 * 
//	 * @return The expected transmission time for a packet with the given
//	 *         params.
//	 */
//	public static long avgTransmitTimeUnicastPacket(Phy802_11 phy,
//			MacInfo macInfo, int length, int dataRate, int controlRate,
//			boolean shouldRTS, int shortRetries, int longRetries,
//			MacMessageFactory msgFactory) {
//
//		throw new RuntimeException("This version of "
//				+ "avgTransmitTimeUnicastPacket() is not implemented!");
//	}

//	/**
//	 * @deprecated use transmitTimeUnicastPacket
//	 */
//	public long calcDurationWithTries(Phy802_11 phy, int length,
//			int payloadRate, int ackSize, int try0, int tryN) {
//		if (payloadRate == 0 || length == 0 || try0 > tryN) {
//			return -1; // TODO hack
//		}
//
//		long tt = 0;
//		for (int x = try0; x <= tryN; x++) {
//			long delta = calcAvgBackoff(payloadRate, x)
//					+ PhyMessage.txDuration(phy, payloadRate, length)
//					+ macInfo.getSifs()
//					+ PhyMessage.txDuration(phy, payloadRate, ackSize);
//			tt += delta;
//		}
//		return tt;
//	}

//	/**
//	 * @deprecated use transmitTimeUnicastPacket
//	 */
//	public long calcDuration(Phy802_11 phy, int length, int payloadRate,
//			int retries) {
//		retries = (macInfo.getRetryLimitShort() <= retries ? macInfo
//				.getRetryLimitShort() - 1 : retries);
//		try {
//			return durations[payloadRate / Constants.BANDWIDTH_1Mbps][length][retries];
//		} catch (NullPointerException e) {
//			if (null == durations[payloadRate / Constants.BANDWIDTH_1Mbps])
//				durations[payloadRate / Constants.BANDWIDTH_1Mbps] = new long[2400][];
//
//			durations[payloadRate / Constants.BANDWIDTH_1Mbps][length] = new long[macInfo
//					.getRetryLimitShort()];
//
//			for (int i = 0; i < macInfo.getRetryLimitShort(); i++) {
//				// If no specific size for ACK packets is given, use the
//				// standard one
//				durations[payloadRate / Constants.BANDWIDTH_1Mbps][length][i] = calcDuration(
//						phy, length, payloadRate, MacDcfMessage.Ack
//								.getHeaderSize(), i);
//			}
//
//			return durations[payloadRate / Constants.BANDWIDTH_1Mbps][length][retries];
//		}
//	}

//	/**
//	 * @deprecated use transmitTimeUnicastPacket
//	 */
//	public long calcDuration(Phy802_11 phy, int length, int payloadRate,
//			int ackSize, int retries) {
//		return calcDurationWithTries(phy, length, payloadRate, ackSize, 0,
//				retries);
//	}

}
