package test.brn.dht;

import jargs.gnu.CmdLineParser;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Random;
import java.util.Vector;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.field.Fading;
import jist.swans.field.Field;
import jist.swans.field.Mobility;
import jist.swans.field.PathLoss;
import jist.swans.field.Placement;
import jist.swans.field.Spatial;
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.misc.Util;
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.TransUdp;
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 DhtDhcpTest {

  // ////////////////////////////////////////////////
  // command-line options
  //

  /** Simulation parameters with default values. */
  private static class CommandLineOptions {
    /** Whether to print a usage statement. */
    private boolean help = false;

    // /** Time to end simulation. */
    // private int endTime = -1;
    /** Number of nodes. */
    private int nodes = 100;

    /** Field dimensions (in meters). */
    private Location.Location2D field = new Location.Location2D(1000, 1000);

    /** Field wrap-around. */
    private boolean wrapField = false;

    /** Node placement model. */
    private int placement = Constants.PLACEMENT_RANDOM;

    /** Node placement options. */
    private String placementOpts = null;

    /** Node mobility model. */
    private int mobility = Constants.MOBILITY_STATIC;

    /** Node mobility options. */
    private String mobilityOpts = null;

    /** Packet loss model. */
    private int loss = Constants.NET_LOSS_NONE;

    /** Packet loss options. */
    private String lossOpts = "";

    /** end of the simulation (seconds). */
    private int endTime = 180;

    /** Name of the click file to use for each node */
    private String clickFile = "res/click/meshnode.click";

    /** Random seed. */
    private int seed = 0;

//    /** number of clients per node */
//    private int clients = 1;

    /** binning mode. */
    public int spatial_mode = Constants.SPATIAL_LINEAR;// SPATIAL_HIER;

    /** binning degree. */
    public int spatial_div = 5;

    public boolean gui;
  } // class: CommandLineOptions

  /** Prints a usage statement. */
  private static void showUsage() {
    System.out.println("Usage: java driver.aodvsim [options]");
    System.out.println();
    System.out.println("  -h, --help           print this message");
    System.out.println("  -n, --nodes          number of nodes: n [100] ");
    System.out.println("  -f, --field          field dimensions: x,y [100,100]");
    System.out.println("  -a, --arrange        placement model: [random],grid:ixj");
    System.out.println("  -m, --mobility       mobility: [static],waypoint:opts,teleport:p,walk:opts");
    System.out.println("  -l, --loss           packet loss model: [none],uniform:p");
    System.out.println("  -k, --click          click file [res/click/meshnode.click]");
    System.out.println("  -c, --clients        clients per node [1]");
    System.out.println("  -e, --end            end if the simulation [180]");
    System.out.println("  -r, --randomseed     random seed: [0]");
    System.out.println("  -g, --gui            use click sim controller gui");
    System.out.println();
    System.out.println("e.g.");
    System.out.println("  swans test.brn.dht.DhtDhcpTest -n 25 -f 2000x2000 -a grid:5x5");
    System.out.println();
  }

  /**
   * Parses command-line arguments.
   *
   * @param args
   *          command-line arguments
   * @return parsed command-line options
   * @throws CmdLineParser.OptionException
   *           if the command-line arguments are not well-formed.
   */
  private static CommandLineOptions parseCommandLineOptions(String[] args)
      throws CmdLineParser.OptionException {
    if (args.length == 0) {
      args = new String[] { "-h" };
    }
    CmdLineParser parser = new CmdLineParser();
    CmdLineParser.Option opt_help = parser.addBooleanOption('h', "help");
    CmdLineParser.Option opt_nodes = parser.addIntegerOption('n', "nodes");
    CmdLineParser.Option opt_field = parser.addStringOption('f', "field");
    CmdLineParser.Option opt_placement = parser.addStringOption('a', "arrange");
    CmdLineParser.Option opt_mobility = parser.addStringOption('m', "mobility");
    CmdLineParser.Option opt_loss = parser.addStringOption('l', "loss");
    CmdLineParser.Option opt_click = parser.addStringOption('k', "click");
    CmdLineParser.Option opt_clients = parser.addIntegerOption('c', "clients");
    CmdLineParser.Option opt_end = parser.addIntegerOption('e', "end");
    CmdLineParser.Option opt_randseed = parser.addIntegerOption('r',
        "randomseed");
    CmdLineParser.Option opt_gui = parser.addBooleanOption('g', "gui");
    parser.parse(args);

    CommandLineOptions cmdOpts = new CommandLineOptions();
    // help
    if (parser.getOptionValue(opt_help) != null) {
      cmdOpts.help = true;
    }
    // // endat
    // if (parser.getOptionValue(opt_endat) != null)
    // {
    // cmdOpts.endTime = ((Integer)parser.getOptionValue(opt_endat)).intValue();
    // }
    // nodes
    if (parser.getOptionValue(opt_nodes) != null) {
      cmdOpts.nodes = ((Integer) parser.getOptionValue(opt_nodes)).intValue();
    }
    // field
    if (parser.getOptionValue(opt_field) != null) {
      cmdOpts.field = (Location.Location2D) Location.parse((String) parser
          .getOptionValue(opt_field));
    }
    // placement
    if (parser.getOptionValue(opt_placement) != null) {
      String placementString = ((String) parser.getOptionValue(opt_placement))
          .split(":")[0];
      if (placementString != null) {
        if (placementString.equalsIgnoreCase("random")) {
          cmdOpts.placement = Constants.PLACEMENT_RANDOM;
        } else if (placementString.equalsIgnoreCase("grid")) {
          cmdOpts.placement = Constants.PLACEMENT_GRID;
        } else {
          throw new CmdLineParser.IllegalOptionValueException(opt_placement,
              "unrecognized placement model");
        }
      }
      cmdOpts.placementOpts = Util.stringJoin((String[]) Util
          .rest(((String) parser.getOptionValue(opt_placement)).split(":")),
          ":");
    }
    // mobility
    if (parser.getOptionValue(opt_mobility) != null) {
      String mobilityString = ((String) parser.getOptionValue(opt_mobility))
          .split(":")[0];
      if (mobilityString != null) {
        if (mobilityString.equalsIgnoreCase("static")) {
          cmdOpts.mobility = Constants.MOBILITY_STATIC;
        } else if (mobilityString.equalsIgnoreCase("waypoint")) {
          cmdOpts.mobility = Constants.MOBILITY_WAYPOINT;
        } else if (mobilityString.equalsIgnoreCase("teleport")) {
          cmdOpts.mobility = Constants.MOBILITY_TELEPORT;
        } else if (mobilityString.equalsIgnoreCase("walk")) {
          cmdOpts.mobility = Constants.MOBILITY_RANDOMWALK;
        } else {
          throw new CmdLineParser.IllegalOptionValueException(opt_mobility,
              "unrecognized mobility model");
        }
      }
      cmdOpts.mobilityOpts = Util
          .stringJoin((String[]) Util.rest(((String) parser
              .getOptionValue(opt_mobility)).split(":")), ":");
    }
    // loss
    if (parser.getOptionValue(opt_loss) != null) {
      String lossString = ((String) parser.getOptionValue(opt_loss)).split(":")[0];
      if (lossString != null) {
        if (lossString.equalsIgnoreCase("none")) {
          cmdOpts.loss = Constants.NET_LOSS_NONE;
        } else if (lossString.equalsIgnoreCase("uniform")) {
          cmdOpts.loss = Constants.NET_LOSS_UNIFORM;
        } else {
          throw new CmdLineParser.IllegalOptionValueException(opt_loss,
              "unrecognized mobility model");
        }
      }
      cmdOpts.lossOpts = Util.stringJoin((String[]) Util.rest(((String) parser
          .getOptionValue(opt_loss)).split(":")), ":");
    }

    // click file
    if (parser.getOptionValue(opt_click) != null) {
      cmdOpts.clickFile = (String) parser.getOptionValue(opt_click);
    }

    // end time
    if (parser.getOptionValue(opt_end) != null) {
      cmdOpts.endTime =  ((Integer) parser.getOptionValue(opt_end)).intValue();
    }

    // random seed
    if (parser.getOptionValue(opt_randseed) != null) {
      cmdOpts.seed = ((Integer) parser.getOptionValue(opt_randseed)).intValue();
    }

    // clients
    if (parser.getOptionValue(opt_clients) != null) {
//      cmdOpts.clients = ((Integer) parser.getOptionValue(opt_clients)).intValue();
    }

    // random seed
    if (parser.getOptionValue(opt_gui) != null) {
      cmdOpts.gui = ((Boolean) parser.getOptionValue(opt_gui)).booleanValue();
    }

    return cmdOpts;

  } // parseCommandLineOptions

  // ////////////////////////////////////////////////
  // simulation setup
  //

  /**
   * Add node to the field and start it.
   *
   * @param opts
   *          command-line options
   * @param i
   *          node number, which also serves as its address
   * @param field
   *          simulation field
   * @param place
   *          node placement model
   * @param radioInfo
   *          shared radio information
   * @param protMap
   *          registered protocol map
   * @param inLoss
   *          packet incoming loss model
   * @param outLoss
   *          packet outgoing loss model
   * @throws ClickException
   * @throws UnknownHostException
   */
  public static void addNode(CommandLineOptions opts, int i, Vector udps,
      Field field, Placement place, RadioInfo radioInfo,
      Mapper protMap, PacketLoss inLoss, PacketLoss outLoss)
      throws ClickException, UnknownHostException {

	Node node = new Node(i);
	node.addField(field);

	// radio
    RadioNoise radio = new RadioNoiseIndep(radioInfo);
    node.addRadio(radio);

    // mac ath0
    MacAddress macAddr = new MacAddress(i);
    MacInfo macInfo = MacInfo.create(radioInfo.getMacType());
    MacClick mac = new MacClick(macAddr, radio.getRadioInfo(), macInfo, false);
    mac.setMsgFactory(new MacClickMessageFactory());
    node.addMac(mac);

    // mac eth0
    MacAddress macAddrEth0 = new MacAddress(i + 10000);
    MacDumb macEth0 = new MacDumb(macAddrEth0, radio.getRadioInfo());
    node.addMac(macEth0);

    // network
    NetAddress netAddr = new NetAddress(InetAddress.getByAddress(new byte[] {
        10, 9, (byte) (i >> 8), (byte) (i & 0xFF) }));
    ClickRouter net = new ClickRouter(netAddr, protMap, inLoss, outLoss);
    net.setMsgFactory(new MacClickMessageFactory());
    node.setNet(net);

    // transport
    TransUdp udp = new TransUdp();
    udps.add(udp);
    node.addTransport(udp);

    // placement
    Location location = place.getNextLocation();
    field.addRadio(radio.getRadioInfo(), radio.getProxy(), location);
    field.startMobility(new Integer(radio.getRadioInfo().getId()));

    // node entity hookup
    radio.setFieldEntity(field.getProxy());
    radio.setMacEntity(mac.getProxy());
    radio.setUseAnnotations(true);
    byte intId = net.addInterface(mac.getProxy(), macAddr, "ath0",
        ClickInterface.SIMCLICK_PTYPE_WIFI_EXTRA, true);
    mac.setRadioEntity(radio.getProxy());
    mac.setNetEntity(net.getProxy(), intId);
    intId = net.addInterface(macEth0.getProxy(), macAddrEth0, "eth0",
        ClickInterface.SIMCLICK_PTYPE_ETHER, false);
    macEth0.setNetEntity(net.getProxy(), intId);
    udp.setNetEntity(net.getProxy());
    net.setProtocolHandler(Constants.NET_PROTOCOL_UDP, udp.getProxy());

    // start click
    net.createClick(opts.clickFile);
    net.getProtocolProxy().start();

    // configure dht
    String startup_time = Integer.toString(30000 + 100*i);
    // TODO set dhcp net mask
    net.writeHandler("MeshNode/dht/dht", "startup_time", startup_time);

    // configure dhcp client
//    String starttime = Integer.toString(40000 + 100*i);
//    String s_ip_range = Integer.toString(opts.clients);
//    int ip_offset = ((i-2)*opts.clients+2);
//    byte[] addr = new byte[] { 10, 9, (byte)(ip_offset >> 8), (byte)ip_offset };
//    String ip_addr = InetAddress.getByAddress(addr).getHostAddress();

    // not needed node get there IPs themselves
    /*
    net.writeHandler("MeshNode/services/local_device/from_host/dhcp_client/client", "debug", "3");
    //net.click_write_handler("MeshNode/services/local_device/dhcp_client", "hw_addr", "16");
    net.writeHandler("MeshNode/services/local_device/from_host/dhcp_client/client", "ip_addr", ip_addr);
    net.writeHandler("MeshNode/services/local_device/from_host/dhcp_client/client", "ip_range", s_ip_range);
    net.writeHandler("MeshNode/services/local_device/from_host/dhcp_client/client", "start_time", starttime);
    net.writeHandler("MeshNode/services/local_device/from_host/dhcp_client/client", "interval", "400");
    net.writeHandler("MeshNode/services/local_device/from_host/dhcp_client/client", "active", "true");
  	*/
  } // method: addNode

  /**
   * Constructs field and nodes with given command-line options, establishes
   * client/server pairs and starts them.
   *
   * @param opts
   *          command-line parameters
   * @throws ClickException
   * @throws UnknownHostException
   */
  private static void buildField(CommandLineOptions opts, final Vector udps)
      throws UnknownHostException, ClickException {
    // initialize node mobility model
    Mobility mobility = null;
    switch (opts.mobility) {
    case Constants.MOBILITY_STATIC:
      mobility = new Mobility.Static();
      break;
    case Constants.MOBILITY_WAYPOINT:
      mobility = new Mobility.RandomWaypoint(opts.field, opts.mobilityOpts);
      break;
    case Constants.MOBILITY_TELEPORT:
      mobility = new Mobility.Teleport(opts.field, Long
          .parseLong(opts.mobilityOpts));
      break;
    case Constants.MOBILITY_RANDOMWALK:
      mobility = new Mobility.RandomWalk(opts.field, opts.mobilityOpts);
      break;
    default:
      throw new RuntimeException("unknown node mobility model");
    }

    // initialize spatial binning
    Spatial spatial = null;
    switch (opts.spatial_mode) {
    case Constants.SPATIAL_LINEAR:
      spatial = new Spatial.LinearList(opts.field);
      break;
    case Constants.SPATIAL_GRID:
      spatial = new Spatial.Grid(opts.field, opts.spatial_div);
      break;
    case Constants.SPATIAL_HIER:
      spatial = new Spatial.HierGrid(opts.field, opts.spatial_div);
      break;
    default:
      throw new RuntimeException("unknown spatial binning model");
    }
    if (opts.wrapField)
      spatial = new Spatial.TiledWraparound(spatial);

    // initialize field
    Field field = new Field(spatial, new Fading.None(),
        new PathLoss.FreeSpace(), mobility, Constants.PROPAGATION_LIMIT_DEFAULT);

    // initialize shared radio information
    RadioInfo radioInfo = RadioFactory.createRadioInfoDefault80211g();

    // initialize shared protocol mapper
    Mapper protMap = new Mapper(new int[] { Constants.NET_PROTOCOL_UDP });

    // initialize packet loss models
    PacketLoss outLoss = new PacketLoss.Zero();
    PacketLoss inLoss = null;
    switch (opts.loss) {
    case Constants.NET_LOSS_NONE:
      inLoss = new PacketLoss.Zero();
      break;
    case Constants.NET_LOSS_UNIFORM:
      inLoss = new PacketLoss.Uniform(Double.parseDouble(opts.lossOpts));
    default:
      throw new RuntimeException("unknown packet loss model");
    }

    
    
    // initialize node placement model
    Placement place = null;
    switch (opts.placement) {
    case Constants.PLACEMENT_RANDOM:
      place = new Placement.Random(opts.field);
      break;
    case Constants.PLACEMENT_GRID:
      place = new Placement.Grid(opts.field, opts.placementOpts);
      break;
    default:
      throw new RuntimeException("unknown node placement model");
    }
    // create each node
    for (int i = 2; i < opts.nodes + 2; i++) {
      addNode(opts, i, udps, field, place, radioInfo, protMap, inLoss, outLoss);
    }

  } // buildField

  /**
   * Display statistics at end of simulation.
   *
   * @param routers
   *          vectors to place zrp objects into
   */
  public static void showStats(Vector routers, CommandLineOptions opt,
      Date startTime) {
    Date endTime = new Date();
    long elapsedTime = endTime.getTime() - startTime.getTime();

    System.err.println("--------------");
    System.err.println("Overall stats:");
    System.err.println("--------------");

    System.gc();
    System.err.println("freemem:  " + Runtime.getRuntime().freeMemory());
    System.err.println("maxmem:   " + Runtime.getRuntime().maxMemory());
    System.err.println("totalmem: " + Runtime.getRuntime().totalMemory());
    long usedMem = Runtime.getRuntime().totalMemory()
        - Runtime.getRuntime().freeMemory();
    System.err.println("used:     " + usedMem);

    System.err.println("start time  : " + startTime);
    System.err.println("end time    : " + endTime);
    System.err.println("elapsed time: " + elapsedTime);
    System.err.flush();

    // clear memory
    routers = null;
  }

  /**
   * Main entry point.
   *
   * @param args
   *          command-line arguments
   */
  public static void main(String[] args) {
    try {
      final CommandLineOptions options = parseCommandLineOptions(args);
      if (options.help) {
        showUsage();
        return;
      }
      if (options.endTime > 0)
        JistAPI.endAt(options.endTime * Constants.SECOND);
      if (options.gui)
        ClickAdapter.useGui();

      Constants.random = new Random(options.seed);
      final Vector routers = new Vector();
      final Date startTime = new Date();
      buildField(options, routers);
      JistAPI.runAt(new Runnable() {
        public void run() {
          showStats(routers, options, startTime);
        }
      }, JistAPI.END);

    } catch (CmdLineParser.OptionException e) {
      System.out.println(e.getMessage());
      e.printStackTrace();
    } catch (UnknownHostException e) {
      System.out.println(e.getMessage());
      e.printStackTrace();
    } catch (ClickException e) {
      System.out.println(e.getMessage());
      e.printStackTrace();
    }

  }

}
