package brn.sim.builder;

import java.util.Random;

import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;
import jist.swans.net.NetAddress;
import jist.swans.trans.TransUdp;

import org.apache.log4j.Logger;

import brn.swans.app.UdpApplication;
import brn.swans.net.NetIpNotify;

public abstract class AppBuilder extends Builder {

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

  public abstract static class AppParams extends Builder.Params {
    private static final long serialVersionUID = 1L;

    /** client node for the stream */
    public NetAddress clientAddr = null;
    /** client port for the stream */
    public int clientPort = 3000;
    /** server node for the stream */
    public NetAddress serverAddr = null;
    /** server port for the stream */
    public int serverPort = 3000;

    public boolean client = true;

    /** start of the udp flow in ms */
    public long start = 80000;

    /** id of this flow */
    public int flowId = 0;

    public NetAddress getClientAddr() {
      return clientAddr;
    }

    public void setClientAddr(NetAddress clientAddr) {
      this.clientAddr = clientAddr;
    }

    public int getClientPort() {
      return clientPort;
    }

    public void setClientPort(int clientPort) {
      this.clientPort = clientPort;
    }

    public NetAddress getServerAddr() {
      return serverAddr;
    }

    public void setServerAddr(NetAddress serverAddr) {
      this.serverAddr = serverAddr;
    }

    public int getServerPort() {
      return serverPort;
    }

    public void setServerPort(int serverPort) {
      this.serverPort = serverPort;
    }

    public boolean isClient() {
      return client;
    }

    public void setClient(boolean client) {
      this.client = client;
    }

    public long getStart() {
      return start;
    }

    public void setStart(long start) {
      this.start = start;
    }

    public int getFlowId() {
      return flowId;
    }

    public void setFlowId(int flowId) {
      this.flowId = flowId;
    }
  }

//  public static class TcpParams extends FlowParams {
//    private static final long serialVersionUID = 1L;
//
//    /** the maximum segment size for tcp */
//    public int tcpMSS = 1460;
//    /** number of bytes to send through tcp */
//    public int tcpBytes = tcpMSS * 100;
//    /** size of the initial tcp receiver window */
//    public int tcpRecvWnd = tcpMSS * 20;
//
//    public int getTcpBytes() {
//      return tcpBytes;
//    }
//    public void setTcpBytes(int tcpBytes) {
//      this.tcpBytes = tcpBytes;
//    }
//    public int getTcpMSS() {
//      return tcpMSS;
//    }
//    public void setTcpMSS(int tcpMSS) {
//      this.tcpMSS = tcpMSS;
//    }
//    public int getTcpRecvWnd() {
//      return tcpRecvWnd;
//    }
//    public void setTcpRecvWnd(int tcpRecvWnd) {
//      this.tcpRecvWnd = tcpRecvWnd;
//    }
//    public int hashCode() {
//      final int prime = 31;
//      int result = super.hashCode();
//      result = prime * result + tcpBytes;
//      result = prime * result + tcpMSS;
//      result = prime * result + tcpRecvWnd;
//      return result;
//    }
//    public boolean equals(Object obj) {
//      if (this == obj)
//        return true;
//      if (!super.equals(obj))
//        return false;
//      if (getClass() != obj.getClass())
//        return false;
//      final TcpParams other = (TcpParams) obj;
//      if (tcpBytes != other.tcpBytes)
//        return false;
//      if (tcpMSS != other.tcpMSS)
//        return false;
//      if (tcpRecvWnd != other.tcpRecvWnd)
//        return false;
//      return true;
//    }
//  }

  public static class UdpNotifyParams extends AppParams {
    private static final long serialVersionUID = 1L;

    /** number of bytes to transmit in a single packet */
    public int udpPayloadLen = 1460;

    /** end of the udp flow in ms */
    public long end = 90000;

    public boolean useAnnos = true;

    /** max number of packets (max = Integer.MAX_VALUE) */
    public int maxNumberOfPackets = Integer.MAX_VALUE;

    /** whether to use poisson or uniform inter-arrival times */
    public boolean poissonArrival = false;

    public int getUdpPayloadLen() {
      return udpPayloadLen;
    }
    public void setUdpPayloadLen(int udpPayloadLen) {
      this.udpPayloadLen = udpPayloadLen;
    }
    public long getEnd() {
      return end;
    }
    public void setEnd(long end) {
      this.end = end;
    }
    public boolean isUseAnnos() {
      return useAnnos;
    }
    public void setUseAnnos(boolean useAnnos) {
      this.useAnnos = useAnnos;
    }
    public int getMaxNumberOfPackets() {
      return maxNumberOfPackets;
    }
    public void setMaxNumberOfPackets(int maxNumberOfPackets) {
      this.maxNumberOfPackets = maxNumberOfPackets;
    }
    public boolean isPoissonArrival() {
      return poissonArrival;
    }
    public void setPoissonArrival(boolean poissonArrival) {
      this.poissonArrival = poissonArrival;
    }
  }

  public static class UdpParams extends UdpNotifyParams {
    private static final long serialVersionUID = 1L;

    /** transmit rate in packets per sec */
    public int udpTxRate = 30;

    public int getUdpTxRate() {
      return udpTxRate;
    }

    public void setUdpTxRate(int udpTxRate) {
      this.udpTxRate = udpTxRate;
    }

    public int hashCode() {
      final int prime = 31;
      int result = super.hashCode();
      result = prime * result + udpTxRate;
      return result;
    }

    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final UdpParams other = (UdpParams) obj;
      if (udpTxRate != other.udpTxRate)
        return false;
      return true;
    }

  }

//  public static class UdpCaParams extends UdpNotifyParams {
//    private static final long serialVersionUID = 1L;
//
//    public int maxOutstandingPackets = 100;
//
//    public int getMaxOutstandingPackets() {
//      return maxOutstandingPackets;
//    }
//    public void setMaxOutstandingPackets(int maxOutstandingPackets) {
//      this.maxOutstandingPackets = maxOutstandingPackets;
//    }
//    public int hashCode() {
//      final int prime = 31;
//      int result = super.hashCode();
//      result = prime * result + maxOutstandingPackets;
//      return result;
//    }
//    public boolean equals(Object obj) {
//      if (this == obj)
//        return true;
//      if (!super.equals(obj))
//        return false;
//      if (getClass() != obj.getClass())
//        return false;
//      final UdpCaParams other = (UdpCaParams) obj;
//      if (maxOutstandingPackets != other.maxOutstandingPackets)
//        return false;
//      return true;
//    }
//  }

//  /**
//   * Builds a single tcp flow
//   *
//   * @author kurth
//   */
//  public static class Tcp extends AppBuilder {
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#getParamClass()
//     */
//    public Class getParamClass() {
//      return TcpParams.class;
//    }
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#build(brn.sim.builder.Builder.Params, jist.swans.Node)
//     */
//    public Object build(Params params, Node node) throws BuilderException {
//      TcpParams opts = (TcpParams) params;
//      Util.assertion(opts.clientAddr != null && opts.serverAddr != null);
//
//      TcpApplication tcpApp = null;
//      if (node.getNet().getAddress().equals(opts.serverAddr)) {
//        tcpApp = new TcpApplication(opts.start * Constants.MILLI_SECOND,
//            opts.serverPort, opts.tcpBytes);
//      }
//      else if (node.getNet().getAddress().equals(opts.clientAddr)) {
//        tcpApp = new TcpApplication(opts.start * Constants.MILLI_SECOND,
//            opts.serverAddr, opts.serverPort, opts.tcpBytes);
//      }
//
//      return tcpApp;
//    }
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#hookUp(brn.sim.builder.Builder.Params, jist.swans.Node, java.lang.Object)
//     */
//    public void hookUp(Params params, Node node, Object entity) throws BuilderException {
//      TcpParams opts = (TcpParams) params;
//      TcpApplication app = (TcpApplication) entity;
//
//      NetAddress addr;
//      int port;
//      if (node.getNet().getAddress().equals(opts.serverAddr)) {
//        addr = opts.clientAddr;
//        port = opts.clientPort;
//      } else if (node.getNet().getAddress().equals(opts.clientAddr)) {
//        addr = opts.serverAddr;
//        port = opts.serverPort;
//      } else
//        return;
//      node.getTransport(0).setFlow(opts.flowId, addr, port);
//
//      app.setTcpEntity((TransTcpInterface)node.getTransport(0).getTransProxy());
//      app.getProxy().run();
//    }
//  }

  /**
   * Builds a single udp flow
   *
   * @author kurth
   */
  public static class Udp extends AppBuilder {

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#getParamClass()
     */
    public Class getParamClass() {
      return UdpParams.class;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#build(brn.sim.builder.Builder.Params, jist.swans.Node)
     */
    public Object build(Params params, Node node) throws BuilderException {
      UdpParams opts = (UdpParams) params;
      Util.assertion(opts.clientAddr != null && opts.serverAddr != null);

      UdpApplication udpApp = null;
      if (!opts.client) {
        udpApp = new UdpApplication(node.getNet().getAddress(), opts.serverPort,
            opts.start * Constants.MILLI_SECOND, opts.end * Constants.MILLI_SECOND);
      }
      else {
        // create payload
        byte[] payload = new byte[opts.udpPayloadLen];
        Random random = new Random(1);
        random.nextBytes(payload);

        // create anno
        MessageAnno anno = new MessageAnno();
        anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(opts.flowId));

        // create client
        udpApp = new UdpApplication(opts.clientAddr, opts.clientPort,
            opts.serverAddr, opts.serverPort, opts.start * Constants.MILLI_SECOND,
            opts.end * Constants.MILLI_SECOND, opts.udpTxRate, payload, anno);
        udpApp.setMaxNumberOfPackets(opts.maxNumberOfPackets);
      }
      udpApp.setPoissonArrival(opts.poissonArrival);

      return udpApp;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#hookUp(brn.sim.builder.Builder.Params, jist.swans.Node, java.lang.Object)
     */
    public void hookUp(Params params, Node node, Object entity) throws BuilderException {
      UdpParams opts = (UdpParams) params;
      UdpApplication app = (UdpApplication) entity;
      TransUdp udp = (TransUdp) node.getTransport(0);

      NetAddress addr;
      int port;
      if (!opts.client) {
        addr = opts.clientAddr;
        port = opts.clientPort;
      } else {
        addr = opts.serverAddr;
        port = opts.serverPort;
      }
//      node.getTransport(0).setFlow(opts.flowId, addr, port);

      app.setUseAnnos(opts.useAnnos);
      app.setUdpEntity(udp.getProxy());
      app.getProxy().run();
    }
  }

  public static class UdpNotify extends AppBuilder {

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#getParamClass()
     */
    public Class getParamClass() {
      return UdpNotifyParams.class;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#build(brn.sim.builder.Builder.Params, jist.swans.Node)
     */
    public Object build(Params params, Node node) throws BuilderException {
      UdpNotifyParams opts = (UdpNotifyParams) params;
      Util.assertion(opts.clientAddr != null && opts.serverAddr != null);

      UdpApplication udpApp = null;
      if (!opts.client) {
        udpApp = new UdpApplication(node.getNet().getAddress(), opts.serverPort,
            opts.start * Constants.MILLI_SECOND, opts.end * Constants.MILLI_SECOND);
      }
      else {
        Node clientNode = node;

        // create payload
        byte[] payload = new byte[opts.udpPayloadLen];
        Random random = new Random(1);
        random.nextBytes(payload);

        // create anno
        MessageAnno anno = new MessageAnno();
        anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(opts.flowId));

        // create client
        udpApp = new UdpApplication(opts.clientAddr, opts.clientPort,
            opts.serverAddr, opts.serverPort, opts.start * Constants.MILLI_SECOND,
            opts.end * Constants.MILLI_SECOND, payload, anno);

        udpApp.setMaxNumberOfPackets(opts.maxNumberOfPackets);
        // client net must be net notify
        NetIpNotify net = (NetIpNotify) clientNode.getNet();
        net.setNetNotify(udpApp.getNotifyProxy());
      }
      udpApp.setPoissonArrival(opts.poissonArrival);

      return udpApp;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#hookUp(brn.sim.builder.Builder.Params, jist.swans.Node, java.lang.Object)
     */
    public void hookUp(Params params, Node node, Object entity) throws BuilderException {
      UdpNotifyParams opts = (UdpNotifyParams) params;
      UdpApplication app = (UdpApplication) entity;
      TransUdp udp = (TransUdp) node.getTransport(0);

      NetAddress addr;
      int port;
      if (!opts.client) {
        addr = opts.clientAddr;
        port = opts.clientPort;
      } else {
        addr = opts.serverAddr;
        port = opts.serverPort;
      }
//      node.getTransport(0).setFlow(opts.flowId, addr, port);

      app.setUseAnnos(opts.useAnnos);
      app.setUdpEntity(udp.getProxy());
      app.getProxy().run();
    }
  }

//  public static class UdpCa extends AppBuilder {
//
//    protected static class Data {
//      public UdpCaApp udpClient;
//      public UdpCaApp udpServer;
//    }
//
//    protected Map dataMap = new HashMap();
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#getParamClass()
//     */
//    public Class getParamClass() {
//      return UdpCaParams.class;
//    }
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#build(brn.sim.builder.Builder.Params, jist.swans.Node)
//     */
//    public Object build(Params params, Node node) throws BuilderException {
//      UdpCaParams opts = (UdpCaParams) params;
//      Util.assertion(opts.clientAddr != null && opts.serverAddr != null);
//
//      Data data = (Data) dataMap.get(opts);
//      if (null == data) {
//        data = new Data();
//        dataMap.put(opts, data);
//      }
//
//      UdpCaApp udpApp = null;
//      if (node.getNet().getAddress().equals(opts.serverAddr)) {
//        udpApp = new UdpCaApp(node.getNet().getAddress(), opts.serverPort,
//            opts.start * Constants.MILLI_SECOND, opts.end * Constants.MILLI_SECOND,
//            opts.maxOutstandingPackets);
//        data.udpClient = udpApp;
//      }
//      else if (node.getNet().getAddress().equals(opts.clientAddr)) {
//        Node clientNode = node;
//
//        // create payload
//        byte[] payload = new byte[opts.udpPayloadLen];
//        Random random = new Random(1);
//        random.nextBytes(payload);
//
//        // create anno
//        MessageAnno anno = new MessageAnno();
//        anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(opts.flowId));
//
//        // create client
//        udpApp = new UdpCaApp(opts.clientAddr, opts.clientPort,
//            opts.serverAddr, opts.serverPort, opts.start * Constants.MILLI_SECOND,
//            opts.end * Constants.MILLI_SECOND, payload, anno,
//            opts.maxOutstandingPackets);
//        data.udpServer = udpApp;
//
//        udpApp.setMaxNumberOfPackets(opts.maxNumberOfPackets);
//        // client net must be net notify
//        if (clientNode.getNet() instanceof NetIpNotify) {
//          NetIpNotify net = (NetIpNotify) clientNode.getNet();
//          net.setNetNotify(udpApp.getNotifyProxy());
//        }
//        else
//          log.warn(node + " does not have notify net layer");
//      }
//
//      return udpApp;
//    }
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#hookUp(brn.sim.builder.Builder.Params, jist.swans.Node, java.lang.Object)
//     */
//    public void hookUp(Params params, Node node, Object entity) throws BuilderException {
//      UdpCaParams opts = (UdpCaParams) params;
//      Data data = (Data) dataMap.get(params);
//      if (Main.ASSERT)
//        Util.assertion(null != data
//            && (null != data.udpServer || null != data.udpClient));
//
//      NetAddress addr;
//      int port;
//      if (node.getNet().getAddress().equals(opts.serverAddr)) {
//        addr = opts.clientAddr;
//        port = opts.clientPort;
//      } else if (node.getNet().getAddress().equals(opts.clientAddr)) {
//        addr = opts.serverAddr;
//        port = opts.serverPort;
//      } else
//        return;
//      node.getTransport(0).setFlow(opts.flowId, addr, port);
//
//      UdpCaApp app = (UdpCaApp) entity;
//      TransUdp udp = (TransUdp) node.getTransport(0);
//
//      if (null != data.udpServer && null != data.udpClient) {
//        if (data.udpServer.equals(app))
//          app.setOtherApp(data.udpClient.getUdpCaProxy());
//        else
//          app.setOtherApp(data.udpServer.getUdpCaProxy());
//      }
//
//      app.setUseAnnos(opts.useAnnos);
//      app.setUdpEntity(udp.getProxy());
//      app.getProxy().run();
//    }
//  }
}
