package brn.swans.app;

import jist.runtime.JistAPI;
import jist.swans.app.AppInterface;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;
import jist.swans.net.NetAddress;

import org.apache.log4j.Logger;


/**
 * Udp application with congestion avoidance. Both client and server application
 * exchange the number of received packets. This way the sender throttles the
 * send rate in a way that only a limited number of packets are in the air and
 * no queue will overflow.
 *
 * TOO what happens if more than the given number of packets get dropped on
 * their way? How to prevent this stall?
 *
 * @author kurth
 */
public class UdpCaApp extends UdpApplication
  implements UdpCaAppInterface, AppInterface {

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

  public static final Logger log = Logger.getLogger(UdpCaApp.class
      .getName());

  protected UdpCaAppInterface self;

  protected UdpCaAppInterface otherApp;

  protected int packetsArrived;

  protected final int maxOutstandingPackets;

  /**
   * click consumes every packet it gets so it's no use querying the queue length
   * rather try to keep maxOutStandingPackets on their way all the time.
   */
  protected boolean queueLess;


  // ////////////////////////////////////////////////
  // initialization
  //

  public UdpCaApp(NetAddress dst, int dstPort, long start, long end,
      int maxOutstandingPackets) {
    super(dst, dstPort, start, end);
    this.maxOutstandingPackets = maxOutstandingPackets;
  }

  public UdpCaApp(NetAddress dst, int dstPort, long start, long end,
      int maxOutstandingPackets, boolean queueLess) {
    super(dst, dstPort, start, end);
    this.queueLess = queueLess;
    this.maxOutstandingPackets = maxOutstandingPackets;
  }

  public UdpCaApp(NetAddress src, int srcPort, NetAddress dst, int dstPort,
      long start, long end, byte[] payload, MessageAnno payloadAnno,
      int maxOutstandingPackets) {
    super(src, srcPort, dst, dstPort, start, end, payload, payloadAnno);
    this.maxOutstandingPackets = maxOutstandingPackets;
  }

  public UdpCaApp(NetAddress src, int srcPort, NetAddress dst, int dstPort,
      long start, long end, byte[] payload, MessageAnno payloadAnno,
      int maxOutstandingPackets, boolean queueLess) {
    super(src, srcPort, dst, dstPort, start, end, payload, payloadAnno);
    this.queueLess = queueLess;
    this.maxOutstandingPackets = maxOutstandingPackets;
  }

  public UdpCaApp(NetAddress src, int srcPort, NetAddress dst, int dstPort,
      long start, long end, int txRate, byte[] payload, MessageAnno payloadAnno) {
    super(src, srcPort, dst, dstPort, start, end, txRate, payload, payloadAnno);
    this.maxOutstandingPackets = -1;
  }


  // ////////////////////////////////////////////////
  // accessors
  //

  /**
   * @return the otherApp
   */
  public UdpCaAppInterface getOtherApp() {
    return otherApp;
  }

  /**
   * @param otherApp the otherApp to set
   */
  public void setOtherApp(UdpCaAppInterface otherApp) {
    if(!JistAPI.isEntity(otherApp)) throw new IllegalArgumentException("expected entity");
    this.otherApp = otherApp;
  }

  public UdpCaAppInterface getUdpCaProxy() {
    if (null == self) {
      self = (UdpCaAppInterface) JistAPI.proxy(new UdpCaAppInterface.Dlg(this),
          UdpCaAppInterface.class);
    }
    return self;
  }

  protected boolean canSend() {
    return start <= JistAPI.getTime() && end > JistAPI.getTime()
        && txRate <= 0
        && packetId < maxNumberOfPackets
        && ((packetId - packetsArrived) < maxOutstandingPackets ||
            maxOutstandingPackets < 0);
  }

  // ////////////////////////////////////////////////
  // implementation
  //

  public void packetArrived(int recPacketId) {
    if (queueLess) {
      if (recPacketId <= packetId && recPacketId >= packetsArrived) {
        packetsArrived = recPacketId;
      } else {
        // TODO: sometimes this happens, yet most times the ids are correct.
        // I don't know what causes it and I don't have the time to debug it,
        // so I worked around it.
        log.warn("received packet with bogus ID: " + recPacketId + " Last sent ID was: "
            + packetId + " Last received ID was: " + packetsArrived);
        packetsArrived++;
      }
      while (canSend())
        runSender();
    } else {
      packetsArrived++;
      if (canSend())
        runSender();
    }
  }

  /*
   * (non-Javadoc)
   * @see brn.swans.app.UdpApplication#receive(jist.swans.misc.Message, jist.swans.misc.MessageAnno, jist.swans.net.NetAddress, int)
   */
  public void receive(Message packet, MessageAnno anno, NetAddress src, int srcPort) {
    super.receive(packet, anno, src, srcPort);

    if (otherApp != null) {
      Integer packetId = (Integer) anno.get(MessageAnno.ANNO_RTG_PACKETID);
      otherApp.packetArrived(packetId);
    }
  }

  /* (non-Javadoc)
   * @see brn.swans.app.UdpApplication#queueEmpty(int)
   */
  public void queueEmpty(int interfaceId) {
    if (canSend())
      super.queueEmpty(interfaceId);
  }

}
