package brn.swans.route.metric;

import java.util.List;

import jist.swans.Constants;
import jist.swans.mac.MacInfo;
import jist.swans.mac.MacTimes;
import jist.swans.misc.Util;
import jist.swans.net.NetAddress;

import brn.swans.route.metric.LinkStat.LinkInfo;
import brn.swans.route.metric.LinkTable.LinkData;

/**
 * The Estimated Transmission Time (ETT) metric.
 * Taken from MIT RoofNet.
 *
 * @author Zubow
 * @author kurth
 */
public class ETTMetric implements LinkStat.Metric {

  public static class EttData extends LinkData {
    public Integer dataRate;
    public Integer controlRate;

    protected EttData() {
    }

    public void update(int seq, int age, int metric, int etx) {
      throw new RuntimeException("use the other update!!");
    }

    public void update(long seq, int age, long metric, int etx,
        Integer dataRate, Integer controlRate) {
      // TODO metric as long??
      this.dataRate = dataRate;
      this.controlRate = controlRate;
      super.update(seq, age, (int)metric, etx);
    }
  }

  public final static int INFINITE_METRIC = 9999999;

  protected LinkTable linkTable;

//  private Phy802_11 phy;

//  private MacInfo   macInfo;

  private MacTimes  macTimes;

  private Integer dataRate;

  public ETTMetric(NetAddress id, LinkTable linkTable) {
    this(id,linkTable, 0);
  }

  public ETTMetric(NetAddress id, LinkTable linkTable, int dataRate) {
//  this.id = id;
    this.linkTable = linkTable;
    this.linkTable.setPrototypeLink(new EttData());
    if (dataRate > 0)
      this.dataRate = Integer.valueOf(dataRate);
  }

  public LinkTable getLinkTable() {
    return linkTable;
  }

//  public void setPhy(Phy802_11 phy) {
//    this.phy = phy;
//    if (this.macInfo != null)
//    	this.macTimes = new MacTimes(this.macInfo);
//  }

  public void setMacInfo(MacInfo macInfo) {
//    this.macInfo = macInfo;
//    if (this.phy != null)
    	this.macTimes = new MacTimes(macInfo);
  }

  public long ett(int etx, Integer dataRate, Integer controlRate) {
    int tries = etx/100;
    long low_usecs = macTimes.avgTransmitTimeUnicastPacket(1500, dataRate.intValue(),
        controlRate.intValue(), false, false, 0, tries, true) / Constants.MICRO_SECOND;
    long high_usecs = macTimes.avgTransmitTimeUnicastPacket(1500, dataRate.intValue(),
        controlRate.intValue(), false, false, 0, tries+1, true) / Constants.MICRO_SECOND;

//    if (log.isDebugEnabled())
//      log.debug(JistAPI.getTime() + "( low_usecs = " + low_usecs + ", high_usecs = " + high_usecs);

    // TODO calculate exponential here, not linear!!
    int diff = etx % 100;
    return (diff * high_usecs + (100 - diff) * low_usecs) / 100;
  }

  private long ett2_metric(int ack_prob, int data_prob, Integer dataRate, Integer controlRate) {
    if (ack_prob == 0 || data_prob == 0)
      return 0;

    return ett(100 * 100 * 100 / (ack_prob * data_prob), dataRate, controlRate);
  }


  public void updateLink(NetAddress from, NetAddress to, long seq, List linkinfos) {
    LinkInfo oneAck = null;
    LinkInfo sixAck = null;

    /*
    * if we don't have a few probes going out, just pick
    * the smallest size for fwd rate
    *
    * TODO consider actual basic rate!!
    */

    for (int x = 0; x < linkinfos.size(); x++) {
      LinkInfo linkInfo = (LinkInfo) linkinfos.get(x);
      if (linkInfo.rate.intValue() == Constants.BANDWIDTH_1Mbps
          && (oneAck == null || oneAck.size > linkInfo.size)) {
        oneAck = linkInfo;
      } else if (linkInfo.rate.intValue() == Constants.BANDWIDTH_6Mbps
          && (sixAck == null || sixAck.size > linkInfo.size)) {
        sixAck = linkInfo;
      }
    }

    if (oneAck == null && sixAck == null)
      return;

    for (int x = 0; x < linkinfos.size(); x++) {
      LinkInfo linkInfo = (LinkInfo) linkinfos.get(x);

      // TODO dataRate
      if (dataRate != null && !linkInfo.rate.equals(dataRate))
        continue;

      LinkInfo ack;
      if (-1 != Util.isInArray(Constants.BITRATES_DSSS, linkInfo.rate.intValue())) {
        ack = oneAck;
      } else {
        ack = sixAck;
      }

      long ettMetric = ett2_metric(ack.rev, linkInfo.fwd, linkInfo.rate, ack.rate);
      EttData fwdInfo = (EttData) linkTable.getLinkInfo(from, to);
      if (ettMetric > 0) {
        if (null == fwdInfo)
          fwdInfo = (EttData) linkTable.getLink(from, to);
        if (fwdInfo.metric <= 0 || ettMetric < fwdInfo.metric)
          fwdInfo.update(seq, 0, ettMetric, ETXMetric.etxMetric(linkinfos,
              linkInfo.rate), linkInfo.rate, ack.rate);
      }

      ettMetric = ett2_metric(ack.fwd, linkInfo.rev, linkInfo.rate, ack.rate);
      EttData revInfo = (EttData) linkTable.getLinkInfo(to, from);
      if (ettMetric > 0) {
        if (null == revInfo)
          revInfo = (EttData) linkTable.getLink(to, from);
        if (revInfo.metric <= 0 || ettMetric < revInfo.metric)
          revInfo.update(seq, 0, ettMetric, ETXMetric.etxMetric(linkinfos,
              linkInfo.rate), linkInfo.rate, ack.rate);
      }
    }
  }

}
