package brn.swans.app;

import java.io.IOException;
import java.net.InetSocketAddress;

import jist.runtime.JistAPI;
import jist.runtime.JistAPI.Continuable;
import jist.swans.app.AbstractApplication;
import jist.swans.app.AppInterface;
import jist.swans.app.io.InputStream;
import jist.swans.app.io.OutputStream;
import jist.swans.net.NetAddress;
import jist.swans.trans.TcpServerSocket;
import jist.swans.trans.TcpSocket;
import jist.swans.trans.TransInterface.TransMessage;
import jist.swans.trans.TransInterface.TransTcpInterface;

public class TcpApplication extends AbstractApplication
    implements AppInterface, JistAPI.Proxiable {

  private int numBytes;
  private byte[] msgBytes;
  private int port;
  private TransTcpInterface tcpEntity;
  private NetAddress receiver;
  private AppInterface self;
  private long duration;
  private long startTime;
  private int flowId;
  private NetAddress sender;

  /**
   * Creates a server application and waits for incoming connections. It reads
   * until the connection is closed.
   *
   * @param port
   */
  public TcpApplication(long startTime, int port) {
    this.port = port;
    this.numBytes = 0;
    this.msgBytes = null;
    this.receiver = null;
    this.duration = -1;
    this.startTime = startTime;

    self = (AppInterface)JistAPI.proxy(this, AppInterface.class);
  }

  /**
   * Creates a server application. It creates a server socket and waits for
   * incoming connections. It receives the given number of bytes.
   *
   * @param port
   * @param numBytes
   */
  public TcpApplication(long startTime, int port, int numBytes) {
    this.port = port;
    this.numBytes = -1;
    this.msgBytes = null;
    this.receiver = null;
    this.duration = -1;
    this.startTime = startTime;

    self = (AppInterface)JistAPI.proxy(this, AppInterface.class);
  }

  /**
   * Creates a server application. It creates a server socket and waits for
   * incoming connections. It receives the given number of bytes.
   *
   * @param port
   */
  public TcpApplication(long startTime, int port, byte[] msgBytes) {
    this.port = port;
    this.numBytes = msgBytes.length;
    this.msgBytes = msgBytes;
    this.receiver = null;
    this.duration = -1;
    this.startTime = startTime;

    self = (AppInterface)JistAPI.proxy(this, AppInterface.class);
  }

  /**
   * Creates a client application. It sends the given number of bytes to the
   * receiver (server) and shuts down.
   *
   * @param receiver
   * @param port
   * @param numBytes
   * @param msgBytes
   */
  public TcpApplication(long startTime, NetAddress receiver, int port, int numBytes,
      byte[] msgBytes) {
    this.receiver = receiver;
    this.port = port;
    this.numBytes = numBytes;
    this.msgBytes = msgBytes;
    this.duration = -1;
    this.startTime = startTime;

    self = (AppInterface)JistAPI.proxy(this, AppInterface.class);

    if (null == this.msgBytes) {
      this.msgBytes = new byte [this.numBytes];
      for (int i = 0; i < this.msgBytes.length; i++) {
        this.msgBytes[i] = (byte)(i % 26 + 65);
      }
    }
  }

  /**
   * Creates a client application and sends the given number of bytes to the
   * server and shuts down.
   *
   * @param receiver
   * @param port
   * @param numBytes
   */
  public TcpApplication(long startTime, NetAddress receiver, int port, int numBytes) {
    this(startTime, receiver, port, numBytes, null);
  }


//  /**
//   * Creates a client which sends for at least for the specified duration.
//   * TODO we need a server-client synchronization to realize this.
//   *
//   * @param receiver
//   * @param port
//   * @param duration
//   * @param chunkBytes
//   */
//  public TcpApplication(NetAddress receiver, int port, long duration, int chunkBytes) {
//    this.receiver = receiver;
//    this.port = port;
//    this.numBytes = chunkBytes;
//    this.duration = duration;
//
//    self = (AppInterface)JistAPI.proxy(this, AppInterface.class);
//
//    this.msgBytes = new byte [this.numBytes];
//    for (int i = 0; i < this.msgBytes.length; i++) {
//      this.msgBytes[i] = (byte)(i % 26 + 65);
//    }
//  }

  public TcpApplication(long l, NetAddress serverAddr, int serverPort, int tcpBytes, int flowId, NetAddress sender) {
	  this(l, serverAddr, serverPort, tcpBytes);
	  this.flowId = flowId;
	  this.sender = sender;
  }

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

  public void run() {
    JistAPI.sleepBlock(startTime);
    if (null == receiver)
      runServer();
    else
      runClient();
  }

  /**
   * Run the server code.
   *
   */
  private void runServer() {
    TcpServerSocket ss;
    TcpSocket s;
    InputStream in;
    OutputStream out;

    ss = new TcpServerSocket(port);
    ss.setTcpEntity(tcpEntity);
    ss.bind(new InetSocketAddress(port));
    s = ss.getProxy().accept();

    in = s.getProxy().getInputStream();
    out = s.getProxy().getOutputStream();

    byte a = 0;
    int i = 0;
    try {
      while ((a = (byte)in.read()) > -1)
      {
        if (null != msgBytes
          && a != msgBytes[i]) {
          System.out.println ("Bytes received are different!!!");
          break;
        }

        i++;
        if (i == numBytes)
        {
          System.out.println ("\n####### DONE #######");
          if (null != msgBytes)
            System.out.println ("All " + i + " bytes match.\n");
          break;
        }
      }

      JistAPI.sleep (10);
      in.close ();
      out.close ();
      s.getProxy().close ();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      throw new RuntimeException(e);
    } catch (Continuable e) {
      // TODO Auto-generated catch block
      throw new RuntimeException(e);
    }
  }

  /**
   * Run the client code.
   *
   */
  private void runClient() {
    TcpSocket s;
    InputStream in;
    OutputStream out;

//    TransMessage.registerFlow(sender, receiver, flowId);

    s = new TcpSocket();
    s.setTcpEntity(tcpEntity);
    s.bind(new InetSocketAddress(port));

    s.getProxy().connect(new InetSocketAddress(receiver.toIP(), port));

    try {
      in = s.getProxy().getInputStream ();
      out = s.getProxy().getOutputStream ();
      long start = JistAPI.getTime();

      do {
        out.write (msgBytes, 0, msgBytes.length);
      } while (start + duration > JistAPI.getTime());

      JistAPI.sleep (10);
      in.close ();
      out.close ();
      s.getProxy().close ();

      s.getProxy().close ();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      throw new RuntimeException(e);
    } catch (Continuable e) {
      // TODO Auto-generated catch block
      throw new RuntimeException(e);
    }
  }

  public TransTcpInterface getTcpEntity() {
    return tcpEntity;
  }

  public void setTcpEntity(TransTcpInterface tcpEntity) {
    if(!JistAPI.isEntity(tcpEntity)) throw new IllegalArgumentException("expected entity");
    this.tcpEntity = tcpEntity;
  }

  public byte[] getMsgBytes() {
    return msgBytes;
  }

  public AppInterface getProxy() {
    return self;
  }

}
