//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <aodvsim.java Tue 2004/04/06 11:57:32 barr pompom.cs.cornell.edu>
//

// Copyright (C) 2004 by Cornell University
// All rights reserved.
// Refer to LICENSE for terms and conditions of use.

package driver;

import jist.swans.field.Field;
import jist.swans.field.Mobility;
import jist.swans.field.Placement;
import jist.swans.field.Fading;
import jist.swans.field.Spatial;
import jist.swans.field.PathLoss;
import jist.swans.radio.RadioFactory;
import jist.swans.radio.RadioNoise;
import jist.swans.radio.RadioNoiseIndep;
import jist.swans.radio.RadioInfo;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDumb;
import jist.swans.net.NetAddress;
import jist.swans.net.NetMessage;
import jist.swans.net.NetIp;
import jist.swans.net.PacketLoss;
import jist.swans.trans.TransUdp;
import jist.swans.route.RouteInterface;
import jist.swans.route.RouteAodv;
import jist.swans.misc.Util;
import jist.swans.misc.Mapper;
import jist.swans.misc.Location;
import jist.swans.misc.Message;
import jist.swans.Constants;

import jist.runtime.JistAPI;

import jargs.gnu.*;

import java.util.Date;
import java.util.Random;
import java.util.Vector;


/**
 * AODV simulation.  Derived from bordercast
 *
 * @author Clifton
 *
 */
public class aodvsim
{

  /** Default port number to send and receive packets. */
  private static final int PORT = 3001;

  //////////////////////////////////////////////////
  // 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;
    /** Routing protocol to use. */
    private int protocol = Constants.NET_PROTOCOL_AODV;
    /** 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 = "";
    /** Number of messages sent per minute per node. */
    private double sendRate = 1.0;
    /** Start of sending (seconds). */
    private int startTime = 60;
    /** Number of seconds to send messages. */
    private int duration = 3600;
    /** Number of seconds after messages stop sending to end simulation. */
    private int resolutionTime = 30;
    /** Random seed. */
    private int seed = 0;
    /** binning mode. */
    public int spatial_mode = Constants.SPATIAL_HIER;
    /** binning degree. */
    public int spatial_div = 5;
  } // 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("  -s, --send rate      send rate per-minute: [1.0]");
    System.out.println("  -t, --timing         node activity timing: start,duration,resolution [60,3600,30]");
    System.out.println("  -r, --randomseed     random seed: [0]");
    System.out.println();
    System.out.println("e.g.");
    System.out.println("  swans driver.aodvsim -n 25 -f 2000x2000 -a grid:5x5 -t 10,600,60");
    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_rate = parser.addDoubleOption('s', "send rate");
    CmdLineParser.Option opt_timing = parser.addStringOption('t', "timing");
    CmdLineParser.Option opt_randseed = parser.addIntegerOption('r', "randomseed");
    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(":")), ":");
    }

    //// bordercast
    //if(parser.getOptionValue(opt_bordercast) != null)
    //{
    //  String[] data = ((String)parser.getOptionValue(opt_bordercast)).split(",");
    //  if(data.length!=3) throw new CmdLineParser.IllegalOptionValueException(opt_bordercast, "bad format: num,start,delay");
    //  cmdOpts.bordercasts = Integer.parseInt(data[0]);
    //  cmdOpts.bordercastStart = Integer.parseInt(data[1]);
    //  cmdOpts.bordercastDelay = Integer.parseInt(data[2]);
    //}

    //send rate
    if (parser.getOptionValue(opt_rate) != null)
    {
      cmdOpts.sendRate = ((Double)parser.getOptionValue(opt_rate)).doubleValue();
    }

    //timing parameters
    if (parser.getOptionValue(opt_timing) != null)
    {
      String[] data = ((String)parser.getOptionValue(opt_timing)).split(",");
      if(data.length!=3) throw new CmdLineParser.IllegalOptionValueException(opt_timing, "bad format: start,duration,resolution");
      cmdOpts.startTime = Integer.parseInt(data[0]);
      cmdOpts.duration = Integer.parseInt(data[1]);
      cmdOpts.resolutionTime = Integer.parseInt(data[2]);
    }

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

    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 routers list of zrp entities to be appended to
   * @param stats statistics collector
   * @param field simulation field
   * @param place node placement model
   * @param radioInfo radio information
   * @param protMap registered protocol map
   * @param inLoss packet incoming loss model
   * @param outLoss packet outgoing loss model
   */
  public static void addNode(CommandLineOptions opts, int i, Vector routers, RouteAodv.AodvStats stats,
      Field field, Placement place, RadioInfo radioInfo, Mapper protMap,
      PacketLoss inLoss, PacketLoss outLoss)
  {
    // radio
    RadioNoise radio = new RadioNoiseIndep(radioInfo);

    // mac
    MacDumb mac = new MacDumb(new MacAddress(i), radio.getRadioInfo());

    // network
    final NetAddress address = new NetAddress(i);
    NetIp net = new NetIp(address, protMap, inLoss, outLoss);

    // routing
    RouteInterface route = null;
    switch(opts.protocol)
    {
      case Constants.NET_PROTOCOL_AODV:
        RouteAodv aodv = new RouteAodv(address);
        aodv.setNetEntity(net.getProxy());
        aodv.getProxy().start();
        route = aodv.getProxy();
        routers.add(aodv);
        // statistics
        aodv.setStats(stats);
        break;
      default:
        throw new RuntimeException("invalid routing protocol");
    }

    // transport
    TransUdp udp = new TransUdp();

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

    // node entity hookup
    radio.setFieldEntity(field.getProxy());
    radio.setMacEntity(mac.getProxy());
    byte intId = net.addInterface(mac.getProxy());
    net.setRouting(route);
    mac.setRadioEntity(radio.getProxy());
    mac.setNetEntity(net.getProxy(), intId);
    udp.setNetEntity(net.getProxy());
    net.setProtocolHandler(Constants.NET_PROTOCOL_UDP, udp.getProxy());
    net.setProtocolHandler(opts.protocol, route);
  }  //method: addNode




  /**
   * Constructs field and nodes with given command-line options, establishes
   * client/server pairs and starts them.
   *
   * @param opts command-line parameters
   * @param routers vectors to place zrp objects into
   * @param stats zrp statistics collection object
   */
  private static void buildField(CommandLineOptions opts, final Vector routers, final RouteAodv.AodvStats stats)
  {
    // 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 protocol mapper
    Mapper protMap = new Mapper(new int[] { Constants.NET_PROTOCOL_UDP, opts.protocol, });
    // 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 and initialize radio information
    for (int i=1; i<=opts.nodes; i++)
    {
      addNode(opts, i, routers, stats, field, place,
          RadioFactory.createRadioInfoDefault80211g(),
          protMap, inLoss, outLoss);
    }

    // set up message sending events
    JistAPI.sleep(opts.startTime*Constants.SECOND);
    //System.out.println("clear stats at t="+JistAPI.getTimeString());
    stats.clear();
    int numTotalMessages = (int)Math.floor(((double)opts.sendRate/60) * opts.nodes * opts.duration);
    long delayInterval = (long)Math.ceil((double)opts.duration * (double)Constants.SECOND / (double)numTotalMessages);
    for(int i=0; i<numTotalMessages; i++)
    {
      //pick random send node
      int srcIdx = Constants.random.nextInt(routers.size());
      int destIdx;
      do
      {
        //pick random dest node
        destIdx = Constants.random.nextInt(routers.size());
      } while (destIdx == srcIdx);
      RouteAodv srcAodv = (RouteAodv)routers.elementAt(srcIdx);
      RouteAodv destAodv = (RouteAodv)routers.elementAt(destIdx);
      TransUdp.UdpMessage udpMsg = new TransUdp.UdpMessage(PORT, PORT, Message.NULL);
      NetMessage msg = new NetMessage.Ip(udpMsg, srcAodv.getLocalAddr(), destAodv.getLocalAddr(),
          Constants.NET_PROTOCOL_UDP, Constants.NET_PRIORITY_D_BESTEFFORT, Constants.TTL_DEFAULT);
      srcAodv.getProxy().send(msg, null);
      //stats
      if (stats != null)
      {
        stats.netMsgs++;
      }
      JistAPI.sleep(delayInterval);
    }

  } // buildField


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

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

    System.err.println("Rreq packets sent = "+stats.send.rreqPackets);
    System.err.println("Rreq packets recv = "+stats.recv.rreqPackets);

    System.err.println("Rrep packets sent = "+stats.send.rrepPackets);
    System.err.println("Rrep packets recv = "+stats.recv.rrepPackets);

    System.err.println("Rerr packets sent = "+stats.send.rerrPackets);
    System.err.println("Rerr packets recv = "+stats.recv.rerrPackets);

    System.err.println("Hello packets sent = "+stats.send.helloPackets);
    System.err.println("Hello packets recv = "+stats.recv.helloPackets);

    System.err.println("Total aodv packets sent = "+stats.send.aodvPackets);
    System.err.println("Total aodv packets recv = "+stats.recv.aodvPackets);

    System.err.println("Non-hello packets sent = "+(stats.send.aodvPackets - stats.send.helloPackets));
    System.err.println("Non-hello packets recv = "+(stats.recv.aodvPackets - stats.recv.helloPackets));

    System.err.println("--------------");
    System.err.println("Overall stats:");
    System.err.println("--------------");
    System.err.println("Messages to deliver = "+stats.netMsgs);
    System.err.println("Route requests      = "+stats.rreqOrig);
    System.err.println("Route replies       = "+stats.rrepOrig);
    System.err.println("Routes added        = "+stats.rreqSucc);

    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();

    System.out.println(opt.nodes+"\t"
      +stats.send.rreqPackets+"\t"
      +stats.recv.rreqPackets+"\t"
      +stats.send.rrepPackets+"\t"
      +stats.recv.rrepPackets+"\t"
      +stats.send.rerrPackets+"\t"
      +stats.recv.rerrPackets+"\t"
      +stats.send.helloPackets+"\t"
      +stats.recv.helloPackets+"\t"
      +stats.send.aodvPackets+"\t"
      +stats.recv.aodvPackets+"\t"
      +(stats.send.aodvPackets - stats.send.helloPackets)+"\t"
      +(stats.recv.aodvPackets - stats.recv.helloPackets)+"\t"
      +usedMem+"\t"
      +elapsedTime);

    //clear memory
    routers = null;
    stats = 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;
      }
      long endTime = options.startTime+options.duration+options.resolutionTime;
      if(endTime>0)
      {
        JistAPI.endAt(endTime*Constants.SECOND);
      }
      //Constants.random = new Random(options.seed);
      Constants.random = new Random();
      final Vector routers = new Vector();
      final RouteAodv.AodvStats stats = new RouteAodv.AodvStats();
      final Date startTime = new Date();
      buildField(options, routers, stats);
      JistAPI.runAt(new Runnable()
          {
            public void run()
            {
              showStats(routers, stats, options, startTime);
            }
          }, JistAPI.END);

      //added
      /*
      JistAPI.runAt(new Runnable()
          {
            public void run()
            {
              Iterator itr = routers.iterator();
              while (itr.hasNext())
              {
                RouteAodv aodv = (RouteAodv)itr.next();
                aodv.printPrecursors();
                aodv.printOutgoing();
              }
            }
          }, JistAPI.END);
       */
    }
    catch(CmdLineParser.OptionException e)
    {
      System.out.println(e.getMessage());
    }

  }

}
