/**
 *
 */
package jist.swans.rate;

import java.util.Hashtable;

import org.apache.log4j.Logger;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDcfMessage;
import jist.swans.mac.MacInfo;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.phy.PhyMessage;

/**
 * Implementation of the ARF algorithm. Inspired by the implementation in
 * yans-0.9.0 (http://yans.inria.fr/).
 * 
 * @author Oliver
 * 
 */
public class AutoRateFallback extends AbstractRca implements
		RateControlAlgorithmIF {

	public static Logger log = Logger.getLogger(AutoRateFallback.class);

	protected Hashtable m_macToState = new Hashtable();

	/**
	 * Important helper method for polymorphy of ArfState and AarfState
	 */
	protected ArfState createState() {
		return new ArfState();
	}

	/**
	 * Returns an ArfState object for the given MAC address. ArfState objects are
	 * created as they are needed.
	 * 
	 * @param address
	 *          The MAC address for which to retrieve the current state of the ARF
	 *          algorithm
	 * 
	 * @return the state of the ARF algorithm for the given MAC address.
	 */
	protected ArfState getArfState(MacAddress address) {
		ArfState as = (ArfState) m_macToState.get(address);
		if (as == null) {
			if (s_macRateInfo.getMacInfo(address) == null)
				throw new RuntimeException("Why don't I have a MacInfo for " + address
						+ "?");
			if (s_macRateInfo.getBitrates(address) == null
					|| s_macRateInfo.getBasicRates(address) == null)
				throw new RuntimeException("Why don't I know any bit-rates for "
						+ address + "?");

			as = createState();
			m_macToState.put(address, as);
		}
		return as;
	}

	/**
	 * Constructor
	 * 
	 * @param macInfo
	 */
	public AutoRateFallback(MacInfo macInfo) {
		super(macInfo);
	}

	/**
	 * Constructor with params to change behavior of ARF.
	 * 
	 * @param mac
	 * @param minTimerTimeout
	 *          Minimum number of tries of packet transmission before trying a
	 *          higher rate
	 * @param minSuccessThreshold
	 *          Minimum threshold of successfully transmitted data packets before
	 *          trying a higher rate
	 */
	public AutoRateFallback(MacInfo macInfo, int minTimerTimeout,
			int minSuccessThreshold) {
		super(macInfo);
		ArfState.s_min_timer_timeout = minTimerTimeout;
		ArfState.s_min_success_threshold = minSuccessThreshold;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jist.swans.rate.RateControlAlgorithmIF#getControlRate(jist.swans.mac.MacAddress,
	 *      int)
	 * 
	 * Considerations: In the often quoted paper "WaveLAN-II: A High-Performance
	 * Wireless LAN for the Unlicensed Band - Ad Kamerman and Leo Monteban" which
	 * introduced the ARF algorithm, there is not much information on specific
	 * behavior of the algorithm. It can be assumed that in 802.11 ALL CONTROL
	 * FRAMES were transmitted at 1 Mbps, the support of which is mandatory for
	 * all 802.11 stations. The same holds true for 802.11b stations. In 802.11g
	 * stations, that are backwards compatible with 802.11b, all rates of 802.11b
	 * are mandatory, i.e. 1, 2, 5.5, and 11 Mbps. Therefore, the ARF algorithm
	 * could in theory choose any of these rates for transmission of control
	 * frames (as long as the rate is smaller than or equal to the tx rate of the
	 * immediately previous frame in the frame exchange sequence). Unfortunately,
	 * I have not found any literature touching this topic.
	 * 
	 * The choice for the control rate is thus partly arbitrary. For the time
	 * being I'm choosing a conservative solution: to send all control frames at
	 * the lowest basic rate supported by the receiver of the frame.
	 * 
	 * @author: Oliver Friedrich
	 * 
	 */
	public int getControlRate(MacAddress nextHop, int dataRate) {
		int lowestControlRateReceiver = s_macRateInfo.getBasicRates(nextHop)[0];
		if (jist.swans.misc.Util.isInArray(m_macInfo.getBitrateSet(),
				lowestControlRateReceiver) >= 0)
			return lowestControlRateReceiver;
		throw new RuntimeException(
				"Sending MAC instance does not support control rate "
						+ lowestControlRateReceiver + "bps to " + nextHop);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jist.swans.rate.RateControlAlgorithmIF#getNextDataRate(jist.swans.mac.MacAddress,
	 *      int, int)
	 * 
	 * The data rate for the next packet has already been set by reportPacketTx().
	 * Simply return it. If no packets have yet been sent to the given nextHop,
	 * use the lowest bit-rate that is supported both by the sender and the
	 * receiver.
	 */
	public int getNextDataRate(Message packet, MessageAnno anno, Object nextHop,
			int frameLen, int triesBefore) {
		ArfState as = getArfState((MacAddress) nextHop);

		/* initial data rate is the lowest supported rate */
		if (as.m_rate == Constants.BANDWIDTH_INVALID) {
			int[] suppRates = s_macRateInfo.getBitrates((MacAddress) nextHop);
			as.m_rate = getSupportedBitrate((MacAddress) nextHop, suppRates[0]);
		}
		return as.m_rate;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jist.swans.rate.RateControlAlgorithmIF#reportPacketRx(jist.swans.phy.PhyMessage)
	 */
	public void reportPacketRx(PhyMessage msg) {
		// do nothing
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jist.swans.rate.RateControlAlgorithmIF#reportPacketTx(jist.swans.mac.MacAddress,
	 *      int, int, int, int, int, int)
	 */
	public void reportPacketTx(MacAddress nextHop, int type, int size,
			int bitrate, int shortTries, int longTries, int status) {

		ArfState as = getArfState(nextHop);
		int[] suppRates = s_macRateInfo.getBitrates(nextHop);

		if (log.isDebugEnabled())
			log.debug(JistAPI.getTime() + " ARF: " + nextHop + ": " + suppRates[0]
					+ " - " + suppRates[suppRates.length - 1]);

		// Only data packets have an influence
		if (type == MacDcfMessage.TYPE_DATA) {
			// Transmission ok
			if (status == Constants.RADIO_RECV_STATUS_OK.byteValue()) {
				as.m_timer++;
				as.m_success++;
				as.m_failed = 0;
				as.m_recovery = false;
				as.m_retry = 0;
				int tmp = getNextHigherRate(suppRates, as.m_rate);
				if ((as.m_success == as.m_success_threshold || as.m_timer == as.m_timer_timeout)
						&& (as.m_rate < tmp)) {
					as.m_rate = tmp;
					as.m_timer = 0;
					as.m_success = 0;
					as.m_recovery = true;
				}
			}
			// Transmission failed
			else {
				as.m_timer++;
				as.m_failed++;
				as.m_retry++;
				as.m_success = 0;

				if (as.m_recovery) {
					if (Main.ASSERT)
						Util.assertion(as.m_retry >= 1);
					if (as.need_recovery_fallback()) {
						as.report_recovery_failure();
						as.m_rate = getNextLowerRate(suppRates, as.m_rate);
					}
					as.m_timer = 0;
				} else {
					if (Main.ASSERT)
						Util.assertion(as.m_retry >= 1);
					if (as.need_normal_fallback()) {
						as.report_failure();
						as.m_rate = getNextLowerRate(suppRates, as.m_rate);
					}
					if (as.m_retry >= 2) {
						as.m_timer = 0;
					}
				}
			}
		}
		if (log.isDebugEnabled())
			log.debug(JistAPI.getTime() + " ARF " + nextHop + ": " + as);
	}

	/**
	 * Controls the ARF state per station.
	 */
	static protected class ArfState {
		// statics
		static int s_min_timer_timeout = 15;

		static int s_min_success_threshold = 10;

		// locals
		int m_timer = 0;

		int m_success = 0;

		int m_failed = 0;

		boolean m_recovery = false;

		int m_retry = 0;

		int m_timer_timeout = 15;

		int m_success_threshold = 10;

		int m_rate = Constants.BANDWIDTH_INVALID;

		// Constructors
		public ArfState() {
		}

		public ArfState(int initialRate) {
			m_rate = initialRate;
		}

		// Helper methods
		protected boolean need_recovery_fallback() {
			if (m_retry == 1) {
				return true;
			} else {
				return false;
			}
		}

		protected boolean need_normal_fallback() {
			int retry_mod = (m_retry - 1) % 2;
			if (retry_mod == 1) {
				return true;
			} else {
				return false;
			}
		}

		protected void report_recovery_failure() {
		}

		protected void report_failure() {
		}

		protected void set_timer_timeout(int timer_timeout) {
			if (Main.ASSERT)
				Util.assertion(timer_timeout >= s_min_timer_timeout);
			m_timer_timeout = timer_timeout;
		}

		protected void set_success_threshold(int success_threshold) {
			if (Main.ASSERT)
				Util.assertion(success_threshold >= s_min_success_threshold);
			m_success_threshold = success_threshold;
		}

		public String toString() {
			String msg = "timer:" + m_timer + "/" + m_timer_timeout + " succ:"
					+ m_success + "/" + m_success_threshold + " fail:" + m_failed
					+ " retry:" + m_retry + " rate:" + m_rate + " recov:" + m_recovery;
			return msg;
		}
	}

	/**
	 * Get the next lower bitrate in the array, if there is one.
	 */
	protected int getNextLowerRate(int[] rates, int oldRate) {
		if (rates == null || rates.length <= 0)
			return Constants.BANDWIDTH_INVALID;

		int i = rates.length - 1;
		while (i >= 0 && oldRate != rates[i])
			i--;

		// at least one rate has to be lower
		if (i > 0)
			return rates[i - 1];

		return oldRate;
	}

	/**
	 * Get the next higher bitrate in the array, if there is one.
	 */
	protected int getNextHigherRate(int[] rates, int oldRate) {
		if (rates == null || rates.length <= 0)
			return Constants.BANDWIDTH_INVALID;

		int i = 0;
		while (i < rates.length && oldRate != rates[i])
			i++;

		// at least one rate has to be higher
		if (i + 1 < rates.length)
			return rates[i + 1];

		return oldRate;
	}

	/**
	 * @return The highest bit-rate smaller than or equal to the given one, that
	 *         is supported by both the sender and the given receiver.
	 */
	protected int getSupportedBitrate(MacAddress nextHop, int wantedBitrate) {
		int[] suppSndr = m_macInfo.getBitrateSet();
		int[] suppRcvr = s_macRateInfo.getBitrates(nextHop);

		/* The wanted bit-rate should at least be supported by the receiver! */
		int idx = jist.swans.misc.Util.isInArray(suppRcvr, wantedBitrate);
		if (idx == -1)
			throw new RuntimeException(wantedBitrate / Constants.BANDWIDTH_1Mbps
					+ "Mbps is not supported by " + nextHop + "! (ARF)");

		if (jist.swans.misc.Util.isInArray(suppSndr, wantedBitrate) >= 0)
			return wantedBitrate;

		for (int i = suppSndr.length - 1; i >= 0; i--)
			if ((suppSndr[i] < wantedBitrate)
					&& (jist.swans.misc.Util.isInArray(suppRcvr, suppSndr[i]) >= 0))
				return suppSndr[i];

		return Constants.BANDWIDTH_INVALID;
	}

}
