package brn.swans.app;

import org.apache.commons.math.random.AbstractRandomGenerator;
import org.apache.commons.math.random.RandomData;
import org.apache.commons.math.random.RandomDataImpl;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.app.AbstractApplication;
import jist.swans.app.AppInterface;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.MessageBytes;
import jist.swans.net.NetAddress;
import jist.swans.trans.TransInterface;
import jist.swans.trans.TransInterface.TransMessage;
import jist.swans.trans.TransInterface.TransUdpInterface;
import brn.swans.net.NetIpNotify;
import brn.swans.net.NetNotifyInterface;

public class UdpApplication extends AbstractApplication implements AppInterface,
  TransInterface.SocketHandler, AppInterface.UdpApp, NetNotifyInterface {


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

  private TransUdpInterface udpEntity;
  private NetAddress dst;
  private byte priority = Constants.NET_PRIORITY_D_BESTEFFORT;
  private int dstPort;
  private NetAddress src;
  private int srcPort;
  private AppInterface self;
  private NetNotifyInterface selfNotify;
  protected long start;
  protected long end;
  protected double txRate = -1;
  private byte[] payload;
  private MessageAnno payloadAnno;
  protected boolean useAnnos = true;
  protected int maxNumberOfPackets;

  protected int packetId;

  /** whether to use poisson or uniform inter-arrival times */
  private boolean poissonArrival;

  // ////////////////////////////////////////////////
  // initialization
  //
//  public UdpApplication(NetAddress dst, int dstPort, long start, long end, byte priority) {
//    this(dst, dstPort, start, end);
//    this.priority = priority;
//  }

  public UdpApplication(NetAddress dst, int dstPort, long start, long end) {
    this.dst = dst;
    this.src = null;
    this.dstPort = dstPort;
    this.start = start;
    this.end = end;
    this.packetId = 0;
    this.maxNumberOfPackets = Integer.MAX_VALUE;

    this.packetArrivedEvent = new PacketArrivedEvent(dst.getId());

    self = (AppInterface)JistAPI.proxy(new AppInterface.Dlg(this), AppInterface.class);
    selfNotify = (NetNotifyInterface) JistAPI.proxy(new NetNotifyInterface.Dlg(this),
        NetNotifyInterface.class);
  }

  /**
   * Creates the sender. It sends the given number of bytes to the
   * receiver (server) and shuts down.
   *
   * @param src sender address
   * @param srcPort sender port
   * @param dst receiver address
   * @param dstPort receiver port
   * @param start start time in ms
   * @param end end time in ms
   * @param txRate transmit rate
   * @param payload payload to send
   * @param payloadAnno annos to send with the payload
   */
  public UdpApplication(NetAddress src, int srcPort, NetAddress dst, int dstPort,
      long start, long end, double txRate, byte[] payload, MessageAnno payloadAnno) {
    this(src, srcPort, dst, dstPort, start, end, payload, payloadAnno);
    this.txRate = txRate;
  }

  /**
   * Creates the sender. It sends the given number of bytes to the
   * receiver (server) and shuts down.
   *
   * @param src sender address
   * @param srcPort sender port
   * @param dst receiver address
   * @param dstPort receiver port
   * @param start start time in ms
   * @param end end time in ms
   * @param txRate transmit rate
   * @param payload payload to send
   * @param payloadAnno annos to send with the payload
   */
  public UdpApplication(NetAddress src, int srcPort, NetAddress dst, int dstPort,
      long start, long end, double txRate, byte priority, byte[] payload, MessageAnno payloadAnno) {
    this(src, srcPort, dst, dstPort, start, end, priority, payload, payloadAnno);
    this.txRate = txRate;
  }

  /**
   * Creates the sender. It sends the given number of bytes to the
   * receiver (server) and shuts down. It does not use a fixed tx rate, instead
   * it must be notified by {@link NetIpNotify}.
   *
   * @param src sender address
   * @param srcPort sender port
   * @param dst receiver address
   * @param dstPort receiver port
   * @param start start time in ms
   * @param end end time in ms
   * @param payload payload to send
   * @param payloadAnno annos to send with the payload
   */
  public UdpApplication(NetAddress src, int srcPort, NetAddress dst,
      int dstPort, long start, long end, byte[] payload, MessageAnno payloadAnno) {
    this(dst, dstPort, start, end);
    this.src = src;
    this.srcPort = srcPort;
    this.payload = payload;
    this.payloadAnno = payloadAnno;

    this.packetSentEvent = new PacketSentEvent(src.getId());

    if (null == payload)
      payload = ("haha").getBytes();
  }

  /**
   * Creates the sender. It sends the given number of bytes to the
   * receiver (server) and shuts down. It does not use a fixed tx rate, instead
   * it must be notified by {@link NetIpNotify}.
   *
   * @param src sender address
   * @param srcPort sender port
   * @param dst receiver address
   * @param dstPort receiver port
   * @param start start time in ms
   * @param end end time in ms
   * @param payload payload to send
   * @param payloadAnno annos to send with the payload
   */
  public UdpApplication(NetAddress src, int srcPort, NetAddress dst,
      int dstPort, long start, long end, byte priority, byte[] payload, MessageAnno payloadAnno) {
    this(dst, dstPort, start, end);
    this.src = src;
    this.srcPort = srcPort;
    this.priority = priority;
    this.payload = payload;
    this.payloadAnno = payloadAnno;

    this.packetSentEvent = new PacketSentEvent(src.getId());

    if (null == payload)
      payload = ("haha").getBytes();
  }


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

  public TransUdpInterface getUdpEntity() {
    return udpEntity;
  }

  public void setUdpEntity(TransUdpInterface udpEntity) {
    if(!JistAPI.isEntity(udpEntity)) throw new IllegalArgumentException("expected entity");
    this.udpEntity = udpEntity;
  }

  public AppInterface getProxy() {
    return self;
  }

  public NetNotifyInterface getNotifyProxy() {
    return selfNotify;
  }


  public void setMaxNumberOfPackets(int maxNumberOfPackets) {
    this.maxNumberOfPackets = maxNumberOfPackets;
  }

  /**
   * @return the poissonArrival
   */
  public boolean isPoissonArrival() {
    return poissonArrival;
  }

  /**
   * @param poissonArrival the poissonArrival to set
   */
  public void setPoissonArrival(boolean poissonArrival) {
    this.poissonArrival = poissonArrival;
  }

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

  public void run(String[] args) {
    this.run();
  }

  public void run() {
    JistAPI.sleepBlock(start);
    //runReceiver();
    if (null == src)
      runReceiver();
    else
      runSender();
  }

  /**
   * Run the server code.
   *
   */
  private void runReceiver() {
    udpEntity.addSocketHandler(dstPort, this);
//    JistAPI.sleep(end);
//    udpEntity.delSocketHandler(dstPort);
  }

  public void receive(Message packet, MessageAnno anno, NetAddress src,
      int srcPort) {
    if (packet instanceof MessageBytes) {
      // TODO reassamble packet
    }

    if (packetArrivedEvent.isActive()) {
      Long txTime = (Long) anno.get(MessageAnno.ANNO_TRANS_TXTIME);
//      if (packet instanceof AnnotatedMessage) {
//        AnnotatedMessage msg = (AnnotatedMessage) packet;
        packetArrivedEvent.handle(packet, anno, src, srcPort, this.dst,
            this.dstPort, JistAPI.getTime() - txTime);
//      } else {
//        packetArrivedEvent.handle(packet, anno, src, srcPort, this.dst,
//            this.dstPort);
//      }
    }
  }

  /**
   * Run the client code.
   *
   */
  protected void runSender() {
    long delay = 0;
    if (txRate > 0)
      delay = (long)((double)Constants.SECOND / txRate);
//    Integer flowId = (Integer) payloadAnno.get(MessageAnno.ANNO_RTG_FLOWID);
//    TransMessage.registerFlow(src, dst, flowId.intValue());
    do {
      // Is done externally
      // payloadAnno.put(RouteDsrBrnInterface.ANNO_FLOWID, ...);
      Integer currPacketId = new Integer(this.packetId++);

      MessageAnno anno = null;
      if (useAnnos) {
        anno = (MessageAnno) payloadAnno.clone();
        // This id is not used for statistics
        anno.put(MessageAnno.ANNO_RTG_PACKETID, currPacketId);
      }

//      AnnotatedMessage msg = new AnnotatedMessage(new MessageBytes(payload),
//          flowId.intValue(), currPacketId.intValue(), JistAPI.getTime());
      MessageBytes msg = new MessageBytes(payload);
      udpEntity.send(msg, anno, dst, dstPort, srcPort,
          priority);
      if (packetSentEvent.isActive())
        packetSentEvent.handle(msg, anno, src, srcPort, dst, dstPort);

      if (delay > 0) {
        if (poissonArrival) {
          RandomData interArrival = new RandomDataImpl(new AbstractRandomGenerator() {
              public double nextDouble() {
                return Constants.random.nextDouble();
              }
              public void setSeed(long seed) {
                Constants.random.setSeed(seed);
              }
            });
          JistAPI.sleepBlock((long)interArrival.nextExponential(delay));
        }
        else
          JistAPI.sleepBlock(delay);
      }
    } while (delay > 0 && end > JistAPI.getTime() && packetId < maxNumberOfPackets);
  }

  // ////////////////////////////////////////////////
  // NetNotifyInterface impl
  //

  public void queueEmpty(int interfaceId) {
    if (start <= JistAPI.getTime() && end > JistAPI.getTime()
        && txRate <= 0
        && packetId < maxNumberOfPackets) {
      runSender();
    }
  }

  public boolean isUseAnnos() {
    return useAnnos;
  }

  public void setUseAnnos(boolean useAnnos) {
    this.useAnnos = useAnnos;
  }

}
