package brn.swans.rate;

import jist.swans.rate.RateControlAlgorithmIF;
import jist.swans.mac.MacAddress;
import jist.swans.phy.PhyMessage;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.radio.RadioInfo;
import jist.swans.Constants;
import jist.runtime.JistAPI;
import brn.swans.mac.MacMcExORMessage;

import java.util.*;

import org.apache.log4j.Logger;

/**
 * Opportunistic Receiver-based Auto Rate. Derived from RBAR and adopted to support opportunistic
 * protocols like Mc(-ExOR).
 *
 * Idea behind RBAR:
 *
 * The core idea of RBAR is to allow the receiver to select
 * the appropriate rate for the data packet during the
 * RTS/CTS packet exchange. In RBAR, instead of carrying
 * the duration of the reservation, the packets carry the
 * modulation rate and size of the data packet. This modification
 * serves the dual purpose of providing a mechanism
 * by which the receiver can communicate the chosen
 * rate to the sender, while still providing neighboring nodes
 * with enough information to calculate the duration of the
 * requested reservation.
 *
 * @author Zubow
 */
public abstract class OrRBAR implements RateControlAlgorithmIF {

  /**
   * Statistic chooses the minimum required metric to reach the safe neighbor.
   */
  public static class MinSafeNb extends OrRBAR {
    public MinSafeNb(RadioInfo radioInfo, MacAddress localAddr, int constantBitrate) {
      super(radioInfo, localAddr, constantBitrate);
    }

    public int getNextDataRate(Message packet, MessageAnno anno, Object nextHop, int frameLen, int triesBefore) {
      if (!(nextHop instanceof MacMcExORMessage.CSetAddress))
        throw new IllegalArgumentException("wrong dest type.");

      MacMcExORMessage.CSetAddress dest = (MacMcExORMessage.CSetAddress) nextHop;

      List cands = dest.getLstAddresses();
      MacAddress safeNb = (MacAddress)cands.get(cands.size() - 1);

      int nextRate = constantBitrate.intValue();
      if (rates.get(safeNb) != null)
        nextRate = ((Integer)rates.get(safeNb)).intValue();

      if (log.isInfoEnabled())
        log.info(localAddr.getId() + "(" + JistAPI.getTime() + "): next rate for cs "
            + dest + " is " + nextRate / Constants.BANDWIDTH_1Mbps + " Mbps");

      // clean-up
      rates.clear();

      return nextRate;
    }
  }

  public static class MinRate extends OrRBAR {
    public MinRate(RadioInfo radioInfo, MacAddress localAddr, int constantBitrate) {
      super(radioInfo, localAddr, constantBitrate);
    }

    public int getNextDataRate(Message packet, MessageAnno anno, Object nextHop, int frameLen, int triesBefore) {
      if (!(nextHop instanceof MacMcExORMessage.CSetAddress))
        throw new IllegalArgumentException("wrong dest type.");

      MacMcExORMessage.CSetAddress dest = (MacMcExORMessage.CSetAddress) nextHop;

      List cands = dest.getLstAddresses();

      List dstRates = new ArrayList();
      for (int i = 0; i < cands.size(); i++) {
        MacAddress dst = (MacAddress)cands.get(i);
        Integer lnkRate = (Integer)rates.get(dst);
        if (lnkRate != null)
          dstRates.add(lnkRate);
        else
          dstRates.add(constantBitrate);
      }

      int nextRate = ((Integer)Collections.min(dstRates)).intValue();

      if (log.isInfoEnabled())
        log.info(localAddr.getId() + "(" + JistAPI.getTime() + "): next rate for cs "
            + dest + " is " + nextRate / Constants.BANDWIDTH_1Mbps + " Mbps");

      // clean-up
      rates.clear();

      return nextRate;
    }
  }


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

  public final static double DELTA = 6.0;

  protected RadioInfo radioInfo;
  protected MacAddress localAddr;
  protected Hashtable rates;
  protected Integer constantBitrate;

  public OrRBAR(RadioInfo radioInfo, MacAddress localAddr, int constantBitrate) {
    this.radioInfo        = radioInfo;
    this.localAddr        = localAddr;
    this.rates            = new Hashtable();
    this.constantBitrate  = new Integer(constantBitrate);
  }

  public void reportPacketTx(MacAddress nextHop, int type, int size, int bitrate, int shortTries, int longTries, int status) {
    throw new RuntimeException("Not yet implemented");
  }

  /**
   * Called each time the MAC receives a new packet.
   * @param phyMsg the incoming phymessage.
   */
  public void reportPacketRx(PhyMessage phyMsg) {

    if (!(phyMsg.getPayload() instanceof MacMcExORMessage))
      throw new IllegalArgumentException("wrong payload type");

    MacMcExORMessage mcExORMsg = (MacMcExORMessage)phyMsg.getPayload();

    // we are considering only CTS packets
    if (mcExORMsg.getType() != MacMcExORMessage.TYPE_CTS)
      return;

    MacMcExORMessage.Cts cts = (MacMcExORMessage.Cts) mcExORMsg;

    MacAddress rtsAddr  = cts.getDst();
    MacAddress ctsAddr  = cts.getSrc();
    double rtsSnr       = cts.getRtsSnr();

    // brate -> threshold
    /**
     * Simple threshold-based algorithm:
     * Search for the highest bitrate which requires an SNR below the one of the RTS.
     */
    Hashtable brateSnrTh = radioInfo.getBitrateSnrThresholds();
    Enumeration rkeys = brateSnrTh.keys();

    int best_brate = 0;
    while(rkeys.hasMoreElements()) {
      Integer brate = (Integer)rkeys.nextElement();
      double threshold = ((Double)brateSnrTh.get(brate)).doubleValue();

      if (threshold <= rtsSnr && brate.intValue() > best_brate) {
        best_brate = brate.intValue();
      }
    }

    if (log.isInfoEnabled())
      log.info(localAddr.getId() + "(" + JistAPI.getTime() + "): highest possible bitrate for snr = "
        + rtsSnr + " is " + best_brate / Constants.BANDWIDTH_1Mbps + " Mbps");

    Integer oldRate = (Integer)rates.get(ctsAddr);
    rates.put(ctsAddr, new Integer(best_brate));

    if (log.isInfoEnabled())
      log.info(localAddr.getId() + "(" + JistAPI.getTime() + "): rate updated for " + ctsAddr
        + " from " + ((oldRate != null) ? oldRate.intValue() / Constants.BANDWIDTH_1Mbps : -1) + "/"
        + best_brate / Constants.BANDWIDTH_1Mbps + " Mbps");
  }

  public abstract int getNextDataRate(Message packet, MessageAnno anno, Object nextHop, int frameLen, int triesBefore);

  public int getControlRate(MacAddress nextHop, int dataRate) {
    return 0;  //To change body of implemented methods use File | Settings | File Templates.
  }
}
