//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <AppHeartbeat.java Tue 2004/04/06 11:59:55 barr pompom.cs.cornell.edu>
//

// Copyright (C) 2004 by Cornell University
// All rights reserved.
// Refer to LICENSE for terms and conditions of use.

package brn.swans.app;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.app.AppInterface;
import jist.swans.mac.MacAddress;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetInterface;

import org.apache.log4j.Logger;

import brn.swans.route.metric.AbstractRouteMetric;

/**
 * Link quality measurements application. The difference to the ETX link metric
 * is that the link is estimated in both directions separately. Furthermore,
 * it is not necessary that all nodes send probe packets.
 */
public class AppLqMeasurement extends AbstractRouteMetric
  implements AppInterface, NetInterface.NetHandler, JistAPI.Proxiable {
  public static final Logger log = Logger.getLogger(AppLqMeasurement.class.getName());


  //////////////////////////////////////////////////
  // messages
  //

  /**
   * Heartbeat packet.
   */
  private static class MessageLinkProbe implements Message {
    private int size;
    public MessageLinkProbe(int size) {
      this.size = size;
    }
    /** {@inheritDoc} */
    public int getSize() {
      return size;
    }
    /** {@inheritDoc} */
    public void getBytes(byte[] b, int offset) {
      throw new RuntimeException("not implemented");
    }
    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + size;
      return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final MessageLinkProbe other = (MessageLinkProbe) obj;
      if (size != other.size)
        return false;
      return true;
    }
  } // class: MessageHeartbeat

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

  /** minimum link probe period. */
  public long interval = 500 * Constants.MILLI_SECOND;
  /** maximum link probe jitter. */
  public long jitter = interval / 10;

  /** network entity. */
  private NetInterface netEntity;
  /** self-referencing proxy entity. */
  private Object self;
  /** list of neighbours. */
  private Map neighbours;

  private int currBitrate;
  private int probeSize;

  private int id;
  private int staticBitRate;


  //////////////////////////////////////////////////
  // initialize
  //

  /**
   * Create new heartbeat application instance.
   *
   * @param id node identifier
   * @param probeInterval the probe interval
   * @param probeSize the size of probe packets
   */
  public AppLqMeasurement(int id, long probeInterval, int probeSize) {
    this.self = JistAPI.proxyMany(
        this, new Class[] { AppInterface.class, NetInterface.NetHandler.class });
    neighbours = new HashMap();
    currBitrate = 0;
    this.interval = probeInterval;
    this.probeSize = probeSize;
    this.jitter = this.interval / 10;
    this.id = id;
  }


  //////////////////////////////////////////////////
  // entity
  //

  /**
   * Set network entity.
   *
   * @param netEntity network entity
   */
  public void setNetEntity(NetInterface netEntity) {
    this.netEntity = netEntity;
  }

  /**
   * Return self-referencing NETWORK proxy entity.
   *
   * @return self-referencing NETWORK proxy entity
   */
  public NetInterface.NetHandler getNetProxy() {
    return (NetInterface.NetHandler)self;
  }

  /**
   * Return self-referencing APPLICATION proxy entity.
   *
   * @return self-referencing APPLICATION proxy entity
   */
  public AppInterface getAppProxy() {
    return (AppInterface)self;
  }

  public Set getNodes() {
    return neighbours.keySet();
  }

  public void setStaticBitRate(int staticBitRate) {
    this.staticBitRate = staticBitRate;
  }

  //////////////////////////////////////////////////
  // NetHandler methods
  //

  /** {@inheritDoc} */
  public void receive(Message msg, NetAddress src, MacAddress lastHop,
      byte macId, NetAddress dst, byte priority, byte ttl, MessageAnno anno) {

    if (linkProbeArrivedEvent.isActive())
      linkProbeArrivedEvent.handle(msg, anno, lastHop);
  }

  //////////////////////////////////////////////////
  // AppInterface methods
  //

  /**
   * Compute random heartbeat delay.
   *
   * @return delay to next heartbeat
   */
  private long calcDelay() {
    return interval + Constants.random.nextLong() % (2*jitter+2) - jitter - 1;
  }

  private int getNextBitrate() {
    if (0 != staticBitRate)
      return staticBitRate;

    currBitrate = (currBitrate+1) % 12;
    switch (currBitrate) {
    case 0: return Constants.BANDWIDTH_1Mbps;
    case 1: return Constants.BANDWIDTH_2Mbps;
    case 2: return Constants.BANDWIDTH_5_5Mbps;
    case 3: return Constants.BANDWIDTH_11Mbps;
    case 4: return Constants.BANDWIDTH_6Mbps;
    case 5: return Constants.BANDWIDTH_9Mbps;
    case 6: return Constants.BANDWIDTH_12Mbps;
    case 7: return Constants.BANDWIDTH_18Mbps;
    case 8: return Constants.BANDWIDTH_24Mbps;
    case 9: return Constants.BANDWIDTH_36Mbps;
    case 10: return Constants.BANDWIDTH_48Mbps;
    case 11: return Constants.BANDWIDTH_54Mbps;
    }
    return 0;
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.app.AppInterface#run(java.lang.String[])
   */
  public void run(String[] args) {
    // staggered beginning
    if(JistAPI.getTime()==0) {
      JistAPI.sleep(calcDelay());
    }

    int bitRate = getNextBitrate();
    MessageAnno anno = new MessageAnno();
    anno.put(MessageAnno.ANNO_MAC_BITRATE, new Integer(bitRate));

    if (log.isDebugEnabled())
      log.debug(JistAPI.getTime()+" "+id+" sending frame at rate "+bitRate);

    // send heartbeat
    Message msg = new MessageLinkProbe(probeSize);
    netEntity.send(msg, NetAddress.ANY, Constants.NET_PROTOCOL_LINK_PROBE,
        Constants.NET_PRIORITY_D_BESTEFFORT, (byte)1, anno);

    // schedule next
    JistAPI.sleep(calcDelay());
    ((AppInterface)self).run();
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.app.AppInterface#run()
   */
  public void run() {
    run(null);
  }

}
