/**
 * C3 - Car to Car Cooperation - Project
 *
 * File:         GenericDriver.java
 * RCS:          $Id: GenericDriver.java,v 1.2 2006/02/21 19:21:51 drchoffnes Exp $
 * Description:  GenericDriver class (see below)
 * Author:       David Choffnes
 *               Aqualab (aqualab.cs.northwestern.edu)
 *               Northwestern Systems Research Group
 *               Northwestern University
 * Created:      Nov 17, 2004
 * Language:     Java
 * Package:      driver
 * Status:       Release
 *
 * (C) Copyright 2005, Northwestern University, all rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
package driver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Hashtable;
import java.util.Random;
import java.util.Vector;

import jist.runtime.JistAPI;
import jist.swans.Constants;
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.field.StreetMobility;
import jist.swans.field.StreetMobilityOD;
import jist.swans.field.StreetMobilityRandom;
import jist.swans.field.StreetPlacementRandom;
import jist.swans.mac.Mac802_11;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDumb;
import jist.swans.mac.MacInterface;
import jist.swans.misc.Location;
import jist.swans.misc.Mapper;
import jist.swans.misc.Message;
import jist.swans.misc.MessageBytes;
import jist.swans.misc.Util;
import jist.swans.net.MessageQueue;
import jist.swans.net.NetAddress;
import jist.swans.net.NetIp;
import jist.swans.net.NetMessage;
import jist.swans.net.PacketLoss;
import jist.swans.radio.RadioFactory;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioNoise;
import jist.swans.radio.RadioNoiseAdditive;
import jist.swans.radio.RadioNoiseIndep;
import jist.swans.route.RouteAodv;
import jist.swans.route.RouteDsr;
import jist.swans.route.RouteInterface;
import jist.swans.route.RouteZrp;
import jist.swans.route.RouteAodv.AodvStats;
import jist.swans.trans.TransUdp;

/**
 * Generic simulation configurator. Uses JistExperiment for settings. Derived
 * from aodvsim.
 *
 * @author David Choffnes
 *
 */
public class GenericDriver {

  private static Vector memoryConsumption = new Vector();

  private final static boolean VERBOSE = false;

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

  /**
   * Add node to the field and start it.
   *
   * @param je
   *          command-line options
   * @param i
   *          node number, which also serves as its address
   * @param nodes
   *          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
   * @param mobility
   * @param zrpStats
   * @param v
   */
  public static void addNode(JistExperiment je, int i, Vector nodes,
      RouteAodv.AodvStats stats, Field field, Placement place,
      RadioInfo radioInfo, Mapper protMap, PacketLoss inLoss,
      PacketLoss outLoss, Mobility mobility, RouteZrp.ZrpStats zrpStats,
      VisualizerInterface v) {
    RadioNoise radio;
    Location location;
    if (nodes != null) {
      // radio
      switch (je.radioNoiseType) {
      case Constants.RADIO_NOISE_INDEP:
        radio = new RadioNoiseIndep(radioInfo);
        break;
      case Constants.RADIO_NOISE_ADDITIVE:
        radio = new RadioNoiseAdditive(radioInfo);
        break;
      default:
        throw new RuntimeException("Invalid radio model!");

      }
      // placement
      location = place.getNextLocation();
      field.addRadio(radio.getRadioInfo(), radio.getProxy(), location);
      field.startMobility(radio.getRadioInfo().getIdInteger());
    } else // nodes that are not participating in transmission
    {
      // radio
      radio = new RadioNoiseIndep(radioInfo);
      // placement
      location = place.getNextLocation();
      field.addRadio(radio.getRadioInfo(), radio.getProxy(), location);
      field.startMobility(radio.getRadioInfo().getIdInteger());
      // add node to GUI
      if (v != null && je.useVisualizer) {
        v.addNode(location.getX(), location.getY(), i);
      }
      return;
    }

    // add node to GUI
    if (v != null && je.useVisualizer) {
      v.addNode(location.getX(), location.getY(), i);
    }

    MacInterface mac = new Mac802_11(new MacAddress(i), radio.getRadioInfo());
    MacInterface macProxy = null;
    switch (je.mac) {
    case Constants.MAC_802_11:
      mac = new Mac802_11(new MacAddress(i), radio.getRadioInfo());
      macProxy = ((Mac802_11) mac).getProxy();
      break;
    case Constants.MAC_DUMB:
      mac = new MacDumb(new MacAddress(i), radio.getRadioInfo());
      macProxy = ((MacDumb) mac).getProxy();
      break;

    }

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

    // routing
    RouteInterface route = null;
    switch (je.protocol) {
    case Constants.NET_PROTOCOL_AODV:
      RouteAodv aodv = new RouteAodv(address);
      aodv.setNetEntity(net.getProxy());
      aodv.getProxy().start();
      route = aodv.getProxy();
      nodes.add(aodv);
      // statistics
      aodv.setStats(stats);
      // net.setProtocolHandler(Constants.NET_PROTOCOL_AODV, myHandler);

      break;
    case Constants.NET_PROTOCOL_DSR:
      mac = new Mac802_11(new MacAddress(i), radio.getRadioInfo());
      macProxy = ((Mac802_11) mac).getProxy();
      RouteDsr dsr = new RouteDsr(address);
      dsr.setNetEntity(net.getProxy());
      // dsr.getProxy().start();
      route = dsr.getProxy();
      nodes.add(dsr);

      break;
    case Constants.NET_PROTOCOL_ZRP:
      RouteZrp zrp = new RouteZrp(address, je.radius);
      zrp.setSubProtocolsDefault();
      zrp.setNetEntity(net.getProxy());
      zrp.getProxy().start();
      route = zrp.getProxy();
      zrp.setStats(zrpStats);
      nodes.add(zrp);
      break;

    default:
      throw new RuntimeException("invalid routing protocol");
    }

    // transport
    TransUdp udp = new TransUdp();

    // node entity hookup
    radio.setFieldEntity(field.getProxy());
    radio.setMacEntity(macProxy);
    byte intId = net.addInterface(macProxy,
        new MessageQueue.NoDropMessageQueue(Constants.NET_PRIORITY_NUM,
            NetIp.MAX_QUEUE_LENGTH));
    net.setRouting(route);
    if (mac instanceof Mac802_11) {
      ((Mac802_11) mac).setRadioEntity(radio.getProxy());
      ((Mac802_11) mac).setNetEntity(net.getProxy(), intId);
    }
    udp.setNetEntity(net.getProxy());
    net.setProtocolHandler(Constants.NET_PROTOCOL_UDP, udp.getProxy());
    net.setProtocolHandler(je.protocol, route);
  } // method: addNode

  /**
   * Constructs field and nodes with given command-line options, establishes
   * client/server pairs and starts them.
   *
   * @param je
   *          command-line parameters
   * @param nodes
   *          vectors to place zrp objects into
   * @param stats
   *          aodv statistics collection object
   * @param zrpStats
   */
  private static void buildField(JistExperiment je, final Vector nodes,
      final RouteAodv.AodvStats stats, RouteZrp.ZrpStats zrpStats) {
    // initialize node mobility model
    Mobility mobility = null;
    Location.Location2D tr;
    Location.Location2D bl;

    // set the random seed, if necessary
    Random r;
    if (je.seed == -1) {
      r = new Random();
    } else {
      r = new Random(je.seed);
    }
    // set random object for use in other simulation objects
    je.random = r;

    switch (je.mobility) {
    case Constants.MOBILITY_STATIC:
      mobility = new Mobility.Static();
      break;
    case Constants.MOBILITY_WAYPOINT:
      mobility = new Mobility.RandomWaypoint(je.field, je.pause_time,
          je.granularity, je.max_speed, je.min_speed);
      // mobility = new Mobility.RandomWaypoint(je.field, je.mobilityOpts);
      break;
    case Constants.MOBILITY_TELEPORT:
      mobility = new Mobility.Teleport(je.field, Long
          .parseLong(je.mobilityOpts));
      break;
    case Constants.MOBILITY_RANDOMWALK:
      mobility = new Mobility.RandomWalk(je.field, je.mobilityOpts);
      break;
    case Constants.MOBILITY_STRAW_SIMPLE:
      tr = new Location.Location2D(je.maxLong, je.maxLat);
      bl = new Location.Location2D(je.minLong, je.minLat);
      mobility = new StreetMobilityRandom(je.segmentFile, je.streetFile,
          je.shapeFile, je.degree, je.probability, je.granularity, bl, tr, r);
      break;
    case Constants.MOBILITY_STRAW_OD:
      tr = new Location.Location2D(je.maxLong, je.maxLat);
      bl = new Location.Location2D(je.minLong, je.minLat);
      mobility = new StreetMobilityOD(je.segmentFile, je.streetFile,
          je.shapeFile, je.degree, bl, tr, r);
      break;
    default:
      throw new RuntimeException("unknown node mobility model " + je.mobility);
    }

    // initialize spatial binning
    Spatial spatial = null;

    // make all four points
    Location.Location2D corners[] = new Location.Location2D[4];
    if (je.mobility != Constants.MOBILITY_STRAW_SIMPLE
        && je.mobility != Constants.MOBILITY_STRAW_OD) {
      corners[0] = new Location.Location2D(0, 0);
      corners[1] = new Location.Location2D(je.field.getX(), 0);
      corners[2] = new Location.Location2D(0, je.field.getY());
      corners[3] = new Location.Location2D(je.field.getX(), je.field.getY());

    } else {
      je.sm = (StreetMobility) mobility;
      StreetMobility smr = (StreetMobility) mobility;
      Location.Location2D cornersTemp[] = new Location.Location2D[4];
      cornersTemp = (Location.Location2D[]) smr.getBounds();
      corners[0] = cornersTemp[2];
      corners[1] = cornersTemp[3];
      corners[2] = cornersTemp[0];
      corners[3] = cornersTemp[1];
    }

    VisualizerInterface v = null;

    if (je.useVisualizer) {
      v = new Visualizer();
    }

    je.visualizer = v;

    switch (je.spatial_mode) {
    case Constants.SPATIAL_LINEAR:
      spatial = new Spatial.LinearList(corners[0], corners[1], corners[2],
          corners[3]);
      break;
    case Constants.SPATIAL_GRID:
      spatial = new Spatial.Grid(corners[0], corners[1], corners[2],
          corners[3], je.spatial_div);
      break;
    case Constants.SPATIAL_HIER:
      spatial = new Spatial.HierGrid(corners[0], corners[1], corners[2],
          corners[3], je.spatial_div);
      break;
    default:
      throw new RuntimeException("unknown spatial binning model");
    }
    if (je.wrapField)
      spatial = new Spatial.TiledWraparound(spatial);

    PathLoss pl;
    // pathloss model
    switch (je.pathloss) {
    case Constants.PATHLOSS_FREE_SPACE:
      pl = new PathLoss.FreeSpace();
      break;
    // case Constants.PATHLOSS_SHADOWING:
    // pl = new PathLoss.Shadowing(je.exponent, je.stdDeviation);
    // break;
    case Constants.PATHLOSS_TWO_RAY:
      pl = new PathLoss.TwoRay();
      break;
    default:
      throw new RuntimeException("Unsupported pathloss model!");

    }

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

    // initialize radio information for non participating nodes
    Hashtable freqs = new Hashtable();
    freqs.put(new Short(Constants.MAC_802_11b), new Long(je.frequency));

    Hashtable channelSpacings = new Hashtable();
    channelSpacings.put(new Short(Constants.MAC_802_11b), new Double(Constants.CHANNEL_SPACING_80211a));

    Hashtable channelWidths = new Hashtable();
    channelWidths.put(new Short(Constants.MAC_802_11b), new Double(Constants.CHANNEL_WIDTH_80211a));

    Hashtable numberOfChannels = new Hashtable();
    numberOfChannels.put(new Short(Constants.MAC_802_11b), new Integer(Constants.ORTOGONONAL_CHANNEL_NUMBER_80211a));

    Hashtable bitrateThresholds = new Hashtable();
    bitrateThresholds.put(new Integer(je.bandwidth), new Double(Util.fromDB(10000)));

    RadioInfo noRadio = new RadioInfo("", freqs, channelSpacings, channelWidths,
        numberOfChannels, null, bitrateThresholds,
        Constants.BASIC_RATES_80211B,
        0 /*transmit*/, 0 /*gain*/, Util.fromDB(10000) /*sensitivity*/,
        je.temperature, je.ambient_noise,
        Constants.MAC_802_11b); // TODO: do this right.

    // initialize shared protocol mapper
    Mapper protMap = new Mapper(new int[] { Constants.NET_PROTOCOL_UDP,
        je.protocol, });
    // initialize packet loss models
    PacketLoss outLoss = new PacketLoss.Zero();
    PacketLoss inLoss = null;
    switch (je.loss) {
    case Constants.NET_LOSS_NONE:
      inLoss = new PacketLoss.Zero();
      break;
    case Constants.NET_LOSS_UNIFORM:
      inLoss = new PacketLoss.Uniform(Double.parseDouble(je.lossOpts));
      break;
    default:
      throw new RuntimeException("unknown packet loss model");
    }
    // initialize node placement model
    Placement place = null;
    switch (je.placement) {
    case Constants.PLACEMENT_RANDOM:
      place = new Placement.Random(je.field);
      break;
    case Constants.PLACEMENT_GRID:
      je.setPlacementOpts("");
      place = new Placement.Grid(je.field, je.placementOpts);
      break;
    case Constants.PLACEMENT_STREET_RANDOM:
      StreetMobility smr = (StreetMobility) mobility;
      Location.Location2D bounds[] = (Location.Location2D[]) smr.getBounds();
      place = new StreetPlacementRandom(bounds[0], bounds[3], smr,
          je.driverStdDev);
      break;
    default:
      throw new RuntimeException("unknown node placement model");
    }
    int i;
    // create each mobile node
    for (i = 1; i <= je.nodes; i++) {
      if (je.penetrationRatio < 1.0f
          && (je.penetrationRatio * i) - Math.floor(je.penetrationRatio * i) >= je.penetrationRatio) {
        // create nodes that won't transmit
        addNode(je, i, null, stats, field, place,
            noRadio /*use the same radio info object; doesn't harm here*/,
            protMap, inLoss,
            outLoss, mobility, zrpStats, v);
      } else // create nodes that will transmit
      {
        addNode(je, i, nodes, stats, field, place,
            RadioFactory.createRadioInfoDefault80211g(),
            protMap, inLoss, outLoss, mobility, zrpStats, v);
      }

    }

    // create each static node
    if (je.staticNodes >= 1) {
      Placement staticPlace = null;
      switch (je.staticPlacement) {
      case Constants.PLACEMENT_RANDOM:
        staticPlace = new Placement.Random(je.field);
        break;
      case Constants.PLACEMENT_GRID:

        staticPlace = new Placement.Grid(je.field, je.staticPlacementOpts);
        break;
      case Constants.PLACEMENT_STREET_RANDOM: // TODO update and add one for
                                              // intersections only
        StreetMobility smr = (StreetMobility) mobility;
        Location.Location2D bounds[] = (Location.Location2D[]) smr.getBounds();
        staticPlace = new StreetPlacementRandom(bounds[0], bounds[3], smr);
        break;
      default:
        throw new RuntimeException("unknown node placement model");
      }

      Field staticField = new Field(spatial, new Fading.None(),
          new PathLoss.FreeSpace(), new Mobility.Static(),
          Constants.PROPAGATION_LIMIT_DEFAULT /* check */);
      // initialize radio information
//      RadioInfo staticRadioInfo = RadioInfo.createShared(
//          je.frequency, je.bandwidth, je.staticTransmit, je.staticGain,
//          Util.fromDB(je.staticSensitivity), // TODO update these with decent
//                                              // values
//          Util.fromDB(je.threshold), je.temperature, je.temperature_factor,
//          je.ambient_noise);

      int max = je.staticNodes + i;
      for (int j = i; j <= max; j++) {
        addNode(je, j, nodes, stats, staticField, staticPlace,
            RadioFactory.createRadioInfoDefault80211g(),
            protMap, inLoss, outLoss, null, zrpStats, v);

      }
    } // end if static nodes

    // pick random sources
    Vector sources = new Vector();
    int num_sources = 0;
    final int MAX_SOURCES = je.transmitters;
    boolean nodelist[] = new boolean[nodes.size()];

    // random object for predicatability between runs
    Random myRandom = new Random(0);

    if (nodes.size() > 0) {
      int index = myRandom.nextInt(nodes.size());

      while (num_sources < MAX_SOURCES) {
        sources.add(nodes.elementAt(index));
        nodelist[index] = true;
        do {
          index = myRandom.nextInt(nodes.size());
        } while (nodelist[index] == true && num_sources < MAX_SOURCES - 1);

        num_sources++;
      }

      // set up message sending events
      JistAPI.sleep(je.startTime * Constants.SECOND);
      stats.clear();

      if (je.useCBR) {
        generateCBRTraffic(je, sources, nodes, stats, myRandom);
      } else {
        int numTotalMessages = (int) Math.floor(((double) je.sendRate / 60)
            * MAX_SOURCES * je.duration);
        long delayInterval = (long) Math.ceil((double) je.duration
            * (double) Constants.SECOND / (double) numTotalMessages);

        // send messages
        for (i = 0; i < numTotalMessages; i++) {
          // pick random send node
          int srcIdx = myRandom.nextInt(sources.size());
          int destIdx;
          do {
            // pick random dest node
            destIdx = myRandom.nextInt(nodes.size());
          } while (nodes.elementAt(destIdx) == sources.elementAt(srcIdx));

          // store time sent as payload
          int id = 0;
          byte data[] = new byte[je.packetSize];
          Message payload = new MessageBytes(data);
          NetMessage msg;
          TransUdp.UdpMessage udpMsg = new TransUdp.UdpMessage(je.port,
              je.port, payload);

          switch (je.protocol) {
          case Constants.NET_PROTOCOL_AODV:
            RouteAodv srcAodv = (RouteAodv) sources.elementAt(srcIdx);
            RouteAodv destAodv = (RouteAodv) nodes.elementAt(destIdx);

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

          case Constants.NET_PROTOCOL_DSR:
            RouteDsr srcDsr = (RouteDsr) sources.elementAt(srcIdx);
            RouteDsr destDsr = (RouteDsr) nodes.elementAt(destIdx);

            msg = new NetMessage.Ip(udpMsg, new NetAddress(nodes
                .indexOf(srcDsr)), new NetAddress(nodes.indexOf(destDsr)),
                Constants.NET_PROTOCOL_UDP, Constants.NET_PRIORITY_D_BESTEFFORT,
                Constants.TTL_DEFAULT);
            srcDsr.getProxy().send(msg, null);
            break;

          case Constants.NET_PROTOCOL_ZRP:
            RouteZrp srcZrp = (RouteZrp) sources.elementAt(srcIdx);
            RouteZrp destZrp = (RouteZrp) nodes.elementAt(destIdx);

            msg = new NetMessage.Ip(udpMsg, srcZrp.getLocalAddr(), destZrp
                .getLocalAddr(), Constants.NET_PROTOCOL_UDP,
                Constants.NET_PRIORITY_D_BESTEFFORT, Constants.TTL_DEFAULT);
            srcZrp.getProxy().send(msg, null);
            break;

          default:
            throw new RuntimeException("Unsupported routing protocol!");

          } // end switch

          JistAPI.sleep(delayInterval);
        }
      }
    } // end if nodes
    else // get memory usage every 5 %
    {
      int numTotalIters = 20;
      long delayInterval = (long) Math.ceil((double) je.duration
          * (double) Constants.SECOND / (double) numTotalIters);
      for (int j = 0; j < numTotalIters; j++) {
        memoryConsumption.add(new Long(jist.runtime.Util.getUsedMemory()));
        JistAPI.sleep(delayInterval);
      }
    }

  } // buildField

  /**
   * @param je
   * @param sources
   * @param nodes
   * @param stats
   * @param myRandom
   */
  private static void generateCBRTraffic(JistExperiment je, Vector sources,
      Vector nodes, AodvStats stats, Random myRandom) {

    int id = 0;
    long delayInterval = (long) (((double) je.cbrPacketSize / je.cbrRate) * 1 * Constants.SECOND);
    long iterations = (long) Math.ceil((double) je.duration
        * (double) Constants.SECOND / delayInterval);
    byte data[] = new byte[je.cbrPacketSize];
    Message payload = new MessageBytes(data);
    long currentTime = je.startTime * Constants.SECOND;

    int dests[] = new int[je.transmitters];

    // pick destinations for streams
    for (int i = 0; i < je.transmitters; i++) {
      // pick send node
      do {
        // pick random dest node
        dests[i] = myRandom.nextInt(nodes.size());
      } while (nodes.elementAt(dests[i]) == sources.elementAt(i));
    }

    // send messages
    for (int i = 0; i < iterations; i++) {

      for (int j = 0; j < je.transmitters; j++) {
        // store time sent as payload

        NetMessage msg;
        TransUdp.UdpMessage udpMsg = new TransUdp.UdpMessage(je.port, je.port,
            payload);

        switch (je.protocol) {
        case Constants.NET_PROTOCOL_AODV:
          RouteAodv srcAodv = (RouteAodv) sources.elementAt(j);
          RouteAodv destAodv = (RouteAodv) nodes.elementAt(dests[j]);

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

        case Constants.NET_PROTOCOL_DSR:
          RouteDsr srcDsr = (RouteDsr) sources.elementAt(j);
          RouteDsr destDsr = (RouteDsr) nodes.elementAt(dests[j]);

          msg = new NetMessage.Ip(udpMsg,
              new NetAddress(nodes.indexOf(srcDsr)), new NetAddress(nodes
                  .indexOf(destDsr)), Constants.NET_PROTOCOL_UDP,
              Constants.NET_PRIORITY_D_BESTEFFORT, Constants.TTL_DEFAULT);
          srcDsr.getProxy().send(msg, null);
          break;

        case Constants.NET_PROTOCOL_ZRP:
          RouteZrp srcZrp = (RouteZrp) sources.elementAt(j);
          RouteZrp destZrp = (RouteZrp) nodes.elementAt(dests[j]);

          msg = new NetMessage.Ip(udpMsg, srcZrp.getLocalAddr(), destZrp
              .getLocalAddr(), Constants.NET_PROTOCOL_UDP,
              Constants.NET_PRIORITY_D_BESTEFFORT, Constants.TTL_DEFAULT);
          srcZrp.getProxy().send(msg, null);
          break;

        default:
          throw new RuntimeException("Unsupported routing protocol!");

        } // end switch

      } // send message for each transmitter
      JistAPI.sleep(delayInterval);
      currentTime += delayInterval;
    }

  }

  public static String showAodvStats(RouteAodv.AodvStats stats) {
    String output = "";

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

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

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

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

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

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

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

    System.out.println("Route requests      = " + stats.rreqOrig);
    System.out.println("Route replies       = " + stats.rrepOrig);
    System.out.println("Routes added        = " + stats.rreqSucc);

    return output;
  }

  /**
   * Display statistics at end of simulation.
   *
   */
  public static void showStats(Vector nodes, RouteAodv.AodvStats stats,
      final JistExperiment je, Date startTime, final long freeMemory,
      final String name, RouteZrp.ZrpStats zrpStats) {

    String output = "";

    Date endTime = new Date();
    long elapsedTime = endTime.getTime() - startTime.getTime();

    output += (float) elapsedTime / 1000 + "\t";

    // print configuration values
    output += je.nodes + "\t";
    if (je.mobility == Constants.MOBILITY_STRAW_SIMPLE
        || je.mobility == Constants.MOBILITY_STRAW_OD)
      output += je.sm.getArea() + "\t";
    else {
      output += je.getFieldX() + "\t" + je.getFieldY() + "\t";
    }
    output += je.transmitters + "\t";
    output += je.exponent + "\t";
    output += (je.useCBR ? ((60.0 * je.cbrRate) / je.cbrPacketSize)
        : je.sendRate)
        + "\t";

    if (je.mobility == Constants.MOBILITY_STRAW_SIMPLE)
      output += je.probability + "\t";
    else
      output += je.pause_time + "\t";

    output += je.protocol + "\t";

    if (je.penetrationRatio > 0) {
      System.out.println("-------------");
      System.out.println("Packet stats:");
      System.out.println("-------------");

      switch (je.protocol) {
      case Constants.NET_PROTOCOL_AODV:
        output += showAodvStats(stats);
        output += je.aodvTimeout + "\t";
        break;

      // case Constants.NET_PROTOCOL_DSR:
      // output+=showDsrStats(dsrStats, macStats);
      // break;
      // case Constants.NET_PROTOCOL_ZRP:
      // output+=showMacStats(macStats);
      // zrpStats.print();
      // break;

      default:
        System.err.println("Undefined for this protocol!");
      }

    } else {
      output += printMemStats();
    }

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

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

    if (je.mobility == Constants.MOBILITY_STRAW_SIMPLE
        || je.mobility == Constants.MOBILITY_STRAW_OD) {
      output += je.sm.printAverageSpeed(je.duration + je.startTime
          + je.resolutionTime, VERBOSE)
          + "\t";
      output += je.penetrationRatio;
    } else {
      output += je.fieldX + "\t";
      output += je.fieldY + "\t";
    }

    String newname = name.split("\\.")[0] + ".txt";
    try {
      Util.writeResult(newname, output);
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
    // clear memory
    nodes = null;
    stats = null;

  }

  private static String printMemStats() {
    String output = "";
    double avg;
    long sum = 0;
    for (int i = 0; i < memoryConsumption.size(); i++) {
      sum += ((Long) memoryConsumption.get(i)).longValue();
    }
    avg = (double) sum / memoryConsumption.size();

    output += avg + "\t";

    double stdSum = 0.0;
    // std deviation
    for (int i = 0; i < memoryConsumption.size(); i++) {
      double diff = ((Long) memoryConsumption.get(i)).longValue() - avg;
      stdSum += diff * diff;
    }
    output += (Math.sqrt(stdSum / memoryConsumption.size())) + "\t";
    return output;
  }

  /**
   * Main entry point.
   *
   * @param args
   *          command-line arguments
   */
  public static void main(String[] args) {
    try {

      final JistExperiment je = (JistExperiment) (Util.readObject(args[0]));

      // constructs a new 2D field based on input
      je.setField();

      // store current free memory
      final long freeMemory = Runtime.getRuntime().freeMemory();

      long endTime = je.startTime + je.duration + je.resolutionTime;
      if (endTime > 0) {
        JistAPI.endAt(endTime * Constants.SECOND);
      }

      /** TODO change seed default value to named constant */
      // set random seed for simulator
      if (je.seed != 0) {
        Constants.random = new Random(je.seed);
      } else {
        Constants.random = new Random();
      }
      final Vector nodes = new Vector(je.nodes);
      final RouteAodv.AodvStats stats = new RouteAodv.AodvStats();

      final RouteZrp.ZrpStats zrpStats = new RouteZrp.ZrpStats();
      final Date startTime = new Date();

      buildField(je, nodes, stats, zrpStats);
      final String name = args[0];

      JistAPI.runAt(new Runnable() {
        public void run() {
          showStats(nodes, stats, je, startTime, freeMemory, name, zrpStats);
        }
      }, JistAPI.END);

    }

    catch (IOException e) {
      System.out.println(e.getMessage());
    }
  }

}
