package test.click;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.app.AppJava;
import jist.swans.field.Field;
import jist.swans.field.Mobility;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDumb;
import jist.swans.mac.MacInfo;
import jist.swans.misc.Location;
import jist.swans.misc.Mapper;
import jist.swans.net.NetAddress;
import jist.swans.net.PacketLoss;
import jist.swans.radio.RadioFactory;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioNoise;
import jist.swans.radio.RadioNoiseIndep;
import jist.swans.trans.TransTcp;
import click.runtime.ClickAdapter;
import click.runtime.ClickException;
import click.runtime.ClickInterface;
import click.swans.mac.MacClick;
import click.swans.mac.MacClickMessageFactory;
import click.swans.net.ClickRouter;

public class ClickTest {

  private static final long START_TIME = 45 * Constants.SECOND;

  public static final int CLIENT_ID = 3;
  public static final int SERVER_ID = 2;
  public static final int INTERM_ID1 = 4;
  public static final int INTERM_ID2 = 5;
  public static final int INTERM_ID3 = 6;

  /** default server address. */
  public static final String HOST = "0.0.0.2";
  /** default client-server port. */
  public static final int PORT = 3002;
  /** bulk message to transfer. */
  public static byte[] msg_bulk;
  /** number of bytes in bulk message to transfer. */
  public static int NUM_BYTES = 10000;

  /**
   * Simple TCP server.
   */
  public static class Server
  {
    /**
     * TCP server entry point: open TCP socket and receive message from client.
     *
     * @param args command-line parameters
     */
    public static void main(String[] args)
    {

      System.out.println("tcp server: starting at t="+JistAPI.getTime());

      ServerSocket ss = null;
      Socket s = null;
      InputStream in = null;
      OutputStream out = null;

      try
      {
        ss = new ServerSocket(PORT);
      }
      catch (IOException e)
      {
        System.err.println("tcp server: Could not listen on port " + PORT);
        System.exit(-1);
      }

      try
      {
        s = ss.accept();
      }
      catch (IOException e)
      {
        System.err.println("tcp server: Accept failed: " + PORT);
        System.exit(-1);
      }

      try
      {
        in = s.getInputStream ();
      }
      catch (IOException e)
      {
        System.err.println("tcp server: cannot get input stream");
        System.exit(-1);
      }

      try
      {
        out = s.getOutputStream ();
      }
      catch (IOException e)
      {
        System.err.println("tcp server: cannot get input stream");

        System.exit(-1);
      }
      try
      {
        byte a = 0;
        System.out.println ("calling read at t = " + JistAPI.getTime());
        int i = 0;
        while ((a = (byte)in.read()) > -1)
        {
          if (a != msg_bulk[i])
          {
            System.out.println ("Bytes received are different!!!");
            break;
          }

          i++;
          if (i == msg_bulk.length)
          {
            System.out.println ("\n####### DONE #######");
            System.out.println ("All " + i + " bytes match.\n");
            break;
          }
        }

      }
      catch (IOException e)
      {
        e.printStackTrace ();
      }
      catch (Exception e2)
      {
        e2.printStackTrace ();
      }

      try
      {
        JistAPI.sleep (10);
        in.close ();
        out.close ();
        s.close ();
      }
      catch (IOException e)
      {
        System.err.println("tcp server: failed while closing");
        System.exit(-1);
      }
      JistAPI.sleep (25);
      JistAPI.endAt(JistAPI.getTime() + Constants.SECOND);
    }

  } // class: Server


  /**
   * Simple TCP client.
   */
  public static class Client
  {
    /**
     * TCP client entry point: open TCP socket and send off message to server.
     *
     * @param args command-line parameters
     */
    public static void main(String[] args)
    {

      System.out.println("tcp client: starting at t="+JistAPI.getTime());

      Socket s = null;
      InputStream in = null;
      OutputStream out = null;

      try
      {
        JistAPI.sleep (START_TIME);

        s = new Socket();
        s.bind(new InetSocketAddress(PORT+1));
        s.connect(new InetSocketAddress(HOST, PORT));
        JistAPI.sleep (10);

        in = s.getInputStream ();
        out = s.getOutputStream ();
        out.write (msg_bulk, 0, msg_bulk.length);
        System.out.println("tcp client: sent at t="+JistAPI.getTime() + "(" + msg_bulk.length + " bytes)");

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

        System.out.println("tcp client: finished at t="+JistAPI.getTime());
      }
      catch (UnknownHostException e)
      {
        System.err.println("tcp client: Unknown host: " + HOST);
        System.exit(1);
      }
      catch (IOException e)
      {
        System.err.println("tcp client: No I/O");
        System.exit(1);
      }
      JistAPI.sleep (25);
    }

  } // class: Client

  /**
   * Create node at location (x,y)
   *
   * @param i node id
   * @param field
   * @param x node's x-coordinate
   * @param y node's y-coordinate
   * @param protMap protocol mapper
   * @param appClass class of application to run at this node
   * @throws ClickException
   * @throws UnknownHostException
   */
  public static void createNode(
    int i,
    Field field,
    float x,
    float y,
    RadioInfo radioInfo,
    Mapper protMap,
    Class appClass,
    PacketLoss plIn, PacketLoss plOut) throws ClickException, UnknownHostException
  {
    //create entities
    RadioNoise radio = new RadioNoiseIndep(radioInfo);
    MacAddress macAddr = new MacAddress(i);
    MacInfo macInfo = MacInfo.create(radioInfo.getMacType());
    MacClick mac = new MacClick(macAddr, radio.getRadioInfo(), macInfo, false);
    mac.setMsgFactory(new MacClickMessageFactory());
    MacAddress macAddrEth0 = new MacAddress(i + 100);
    MacDumb macEth0 = new MacDumb(macAddrEth0, radio.getRadioInfo());
    NetAddress netAddr = new NetAddress(InetAddress.getByAddress(new byte[]{0, 0, 0, (byte)i}));
    ClickRouter net = new ClickRouter(netAddr, protMap, plIn, plOut);
    net.setMsgFactory(new MacClickMessageFactory());
    TransTcp tcp = new TransTcp();

    //hookup entities
    Location location = new Location.Location2D(x, y);
    field.addRadio(radio.getRadioInfo(), radio.getProxy(), location);
    field.startMobility(new Integer(radio.getRadioInfo().getId()));

    //radio hookup
    radio.setFieldEntity(field.getProxy());
    radio.setMacEntity(mac.getProxy());
    radio.setUseAnnotations(true);

    //mac hookup
    mac.setRadioEntity(radio.getProxy());
    byte intId = net.addInterface(mac.getProxy(), macAddr, "ath0",
        ClickInterface.SIMCLICK_PTYPE_WIFI_EXTRA, true);
    mac.setNetEntity(net.getProxy(), intId);
    mac.setUseAnnotations(true);

    // eth0 hookup
    net.addInterface(macEth0.getProxy(), macAddrEth0, "eth0",
        ClickInterface.SIMCLICK_PTYPE_ETHER, false);

    //net hookup
    net.setProtocolHandler(Constants.NET_PROTOCOL_TCP, tcp.getProxy());

    //trans hookup
    tcp.setNetEntity(net.getProxy());

    // start click
    net.createClick("res/click/meshnode.click");
    net.getProtocolProxy().start();

    //application
    AppJava app = null;
    if (appClass != null)
    {
      try
      {
        app = new AppJava(appClass);
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
      JistAPI.sleep(1);
      app.setTcpEntity(tcp.getProxy());
      app.getProxy().run(null);
    }

  }

  public static void createSim() throws ClickException, UnknownHostException
  {
    Location.Location2D bounds = new Location.Location2D(2000, 2000);
    //Placement placement = new Placement.Random(bounds);
    Mobility mobility = new Mobility.Static();
    //Mobility mobility = new Mobility.RandomWaypoint(bounds, PAUSE_TIME, GRANULARITY, MAX_SPEED, MIN_SPEED);
    Field field = new Field(bounds);
    field.setMobility(mobility);
    RadioInfo radioInfo = RadioFactory.createRadioInfoDefault80211g();

    // protocol mapper
    Mapper protMap = new Mapper(Constants.NET_PROTOCOL_MAX);
    protMap.mapToNext(Constants.NET_PROTOCOL_TCP);
    protMap.mapToNext(Constants.NET_PROTOCOL_AODV);

    // packet loss
    PacketLoss plIn = new PacketLoss.Zero();
    PacketLoss plOut = new PacketLoss.Zero();
    
    //create client node
    System.out.println("Starting client node...");
    createNode(
      CLIENT_ID,
      field,
      0,
      400,
      radioInfo,
      protMap,
      Client.class,
      plIn, plOut);

    // create intermediate node
    System.out.println("Starting intermediate node...");
    createNode(INTERM_ID1, field, 400, 400, radioInfo, protMap, null, plIn, plOut);

    //create intermediate node
    System.out.println("attempting to start another intermediate node ");
    createNode(INTERM_ID2, field, 800, 400, radioInfo, protMap, null, plIn, plOut);

    //create intermediate node
    System.out.println("attempting to start another intermediate node ");
    createNode(INTERM_ID3, field, 1200, 400, radioInfo, protMap, null, plIn, plOut);

    //create server node

    System.out.println("Startin server node...");
    createNode(
      SERVER_ID,
      field,
      1600,
      400,
      radioInfo,
      protMap,
      Server.class,
      plIn, plOut);

    System.out.println("done creating sim");
    JistAPI.endAt(10000 * Constants.SECOND);

  }

  /**
   * @param args
   */
  public static void main(String[] args) {
    if (args.length > 0)
    {
      Integer numbytes = new Integer (args[0]);
      NUM_BYTES = numbytes.intValue ();
    }

    ClickAdapter.useGui();

    // fill bulk array
    msg_bulk = new byte [NUM_BYTES];
    for (int i = 0; i < msg_bulk.length; i++)
    {
      msg_bulk [i] = (byte)(i % 26 + 65);
    }

    try {
      createSim();
    } catch (ClickException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (UnknownHostException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}
