package brn.sim;

import jargs.gnu.CmdLineParser;

import java.io.FileWriter;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import jist.runtime.Controller;
import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.field.Field;
import jist.swans.misc.Util;

import org.apache.commons.modeler.Registry;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.jmx.HierarchyDynamicMBean;
import org.apache.log4j.spi.LoggerRepository;

import brn.distsim.ormapper.util.DBSaver;
import brn.distsim.ormapper.util.DbBinaryLoader;
import brn.sim.DataManager.DataContribution;
import brn.sim.DataManager.DataContributor;
import brn.sim.builder.AppBuilder;
import brn.sim.builder.Builder;
import brn.sim.builder.BuilderException;
import brn.sim.builder.FadingBuilder;
import brn.sim.builder.FieldBuilder;
import brn.sim.builder.FlowBuilder;
import brn.sim.builder.MacBuilder;
import brn.sim.builder.MetricBuilder;
import brn.sim.builder.NetBuilder;
import brn.sim.builder.NodeBuilder;
import brn.sim.builder.PathLossBuilder;
import brn.sim.builder.Provider;
import brn.sim.builder.RadioBuilder;
import brn.sim.builder.RateBuilder;
import brn.sim.builder.RouteBuilder;
import brn.sim.builder.TrafficBuilder;
import brn.sim.builder.TransBuilder;
import brn.sim.data.dump.RadioDump;
import brn.sim.data.dump.WiresharkEventHandler;
import brn.sim.data.gantt.GanttTrace;
import brn.sim.data.javis.JavisTrace;
import brn.sim.handler.ForwardGraphHandlers;
import brn.sim.handler.JavisFieldEventHandler;
import brn.sim.handler.JavisMacEventHandler;
import brn.sim.handler.JavisNetEventHandler;
import brn.sim.handler.JavisRadioEventHandler;
import brn.sim.handler.LinkQualityHandler;
import brn.sim.handler.LinkTableHandlers;
import brn.sim.handler.StatsFieldHandler;
import brn.sim.handler.StatsFlowHandlers;
import brn.sim.handler.StatsMacHandler;
import brn.sim.handler.StatsNetHandler;
import brn.sim.handler.StatsRadioDivHandler;
import brn.sim.handler.StatsRadioHandler;
import brn.sim.handler.StatsRateHandler;
import brn.sim.handler.StatsRouteHandler;
import brn.sim.handler.StatsTransHandler;
import brn.sim.handler.TimeBarMacPacketHandler;
import brn.sim.handler.TimeBarMacStateHandler;
import brn.sim.handler.TimeBarNetQueueHandler;
import brn.sim.handler.TimeBarRadioPacketHandler;
import brn.sim.handler.TimeBarRadioStateHandler;
import brn.sim.handler.TimeBarTXOPStateHandler;
import brn.sim.handler.gantt.GanttMacEventHandler;
import brn.sim.handler.gantt.GanttRadioChannelEventHandler;
import brn.sim.handler.gantt.GanttRadioModeEventHandler;

public abstract class AbstractDriver implements SimulationSuite {

  /** logger for mac events. */
  public static final Logger log = Logger.getLogger(AbstractDriver.class.getName());

  public static class ConfigContributor extends DataContributor {
    private static final String ID = "ConfigContributor";
    private AbstractParams params;

    public ConfigContributor(AbstractParams params) {
      this.params = params;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DataManager.DataContributor#getContribution(int)
     */
    public DataContribution getContribution(int id, String[] path) {
      return params;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DataManager.DataContributor#setDataManager(brn.sim.DataManager)
     */
    public void setDataManager(DataManager manager) {
      super.setDataManager(manager);
      addData(params, DataManager.LEVEL_IMPORTANT);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DataManager.DataContributor#getId()
     */
    public String getId() {
      return ID;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DataManager.DataContributor#getContent(int, brn.sim.DataManager.DataContribution)
     */
    public String getContent(int id, DataContribution contribution) throws Exception {
      return ((AbstractParams)contribution).getContent();
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DataManager.DataContributor#getFileName(int, brn.sim.DataManager.DataContribution)
     */
    public String getFileName(int id, String[] path, DataContribution contribution) {
      return contribution.getClass().getSimpleName() + ".config";
    }
  }

  //////////////////////////////////////////////////
  // locals
  //

  /** dump handler on network layer, option .dumpNet */
  protected WiresharkEventHandler wiresharkNetHandler;

  /** dump handler on mac layer, option .dumpMac */
  protected WiresharkEventHandler wiresharkMacHandler;


  /** collects all options */
  protected AbstractParams options;

  /** collects all nodes */
  protected List nodes;

  /** builder provider */
  protected Provider builderProvider;

  /** connection to the brn.gui */
  protected UiConnector uiConnector;

  /** manager for all data, stores them in db or files */
  protected DataManager dataManager;

  /** whether to randomize the simulation suite for distsim */
  protected boolean randomizeSimulationSuite = true;


  //////////////////////////////////////////////////
  // initialization
  //

  protected AbstractDriver() {
    nodes = new ArrayList();
  }

  /**
   * TODO update
   */
  protected void showUsage() {
    System.out.println("Usage: " + getClass() + " -f <config.xml>");
  }

  public List getNodes() {
    return nodes;
  }

  /**
   * @return the builderProvider
   */
  protected Provider getBuilderProvider() {
    return builderProvider;
  }

  protected DataManager getDataManager() {
    return dataManager;
  }

  //////////////////////////////////////////////////
  // simulation creation
  //

  /**
   * Main entry point for starting simulations.
   *
   * @param options the options to use.
   * @throws Exception in the case of errors.
   */
  public final void run(AbstractParams options) throws Exception {
    this.options = options;

    installHandlers(options);

    installBuilders(options);

    buildField(options);

    if (Main.ASSERT)
      Util.assertion(JistAPI.getTime() <= 100);

    URL url= AbstractDriver.class.getResource("/mbeans-descriptors.xml");
    Registry registry = Registry.getRegistry();
    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
    registry.setMBeanServer(server);
    registry.loadMetadata(url);

    List contributions = this.dataManager.getContributors();
    for (int i= 0; i < contributions.size(); i++) {
      DataContributor handler = (DataContributor) contributions.get(i);
      try {
        registry.registerComponent(handler, "brn.sim:type=DataContributor,name="
            + handler.getClass().getSimpleName(), "DataContributor" );
      } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }

    registry.registerComponent(Controller.getActiveController(),
        "brn.sim:type=Controller,name=ActiveController", "Controller");

    // Create and Register the top level Log4J MBean
    HierarchyDynamicMBean hdm = new HierarchyDynamicMBean();
    ObjectName mbo = new ObjectName("log4j:hiearchy=default");
    server.registerMBean(hdm, mbo);

    // Add the root logger to the Hierarchy MBean
    Logger rootLogger = Logger.getRootLogger();
    hdm.addLoggerMBean(rootLogger.getName());

    // Get each logger from the Log4J Repository and add it to
    // the Hierarchy MBean created above.
    LoggerRepository r = LogManager.getLoggerRepository();
    Enumeration currentLoggers = r.getCurrentLoggers();
    while (currentLoggers.hasMoreElements()){
      Logger logger = (Logger) currentLoggers.nextElement();
      hdm.addLoggerMBean(logger.getName());
    }
  }

  /**
   * @return the randomizeSimulationSuite
   */
  public boolean isRandomizeSimulationSuite() {
    return randomizeSimulationSuite;
  }

  /**
   * @param randomizeSimulationSuite the randomizeSimulationSuite to set
   */
  protected void setRandomizeSimulationSuite(boolean randomizeSimulationSuite) {
    this.randomizeSimulationSuite = randomizeSimulationSuite;
  }


  /*
   * (non-Javadoc)
   * @see brn.sim.SimulationSuite#getSimulationSuite(java.lang.String)
   */
  public List getSimulationSuite(String version) {
    return null;
  }

  /**
   * Call this method from your main method.
   *
   * @param args command line arguments
   * @throws Exception
   */
  protected void run(String[] args) throws Throwable {
    String cmd = "";
    for (int i=0; i < args.length; i++)
      cmd += args[i] + " ";
    StringTokenizer tok = new StringTokenizer(cmd);
    args = new String[tok.countTokens()];
    for (int i = 0; i < args.length; i++)
      args[i] = tok.nextToken();

    CmdLineParser parser = new CmdLineParser();
    CmdLineParser.Option optDistsim = parser.addBooleanOption('d', "distsim");
    CmdLineParser.Option optFile = parser.addStringOption('f', "file");
    CmdLineParser.Option optTest = parser.addStringOption('t', "test");
    CmdLineParser.Option optEval = parser.addStringOption('e', "eval");
    CmdLineParser.Option optLocal = parser.addBooleanOption('l', "local");
    CmdLineParser.Option optConvert = parser.addStringOption('c', "convert");
    parser.parse(args);

    // TODO
//    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
//    server.createMBean("org.hibernate.jmx.HibernateService",
//        new ObjectName("jboss.jca:service=HibernateFactory,name=HibernateFactory"));

    if(parser.getOptionValue(optDistsim)!=null) {
      runDistSim();
    }
    else if(parser.getOptionValue(optTest)!=null) {
      runTest((String)parser.getOptionValue(optTest));
    }
    else if(parser.getOptionValue(optEval)!=null) {
      StringTokenizer strtok = new StringTokenizer(
          (String)parser.getOptionValue(optEval), "#");
      evalSimulationResults(strtok.nextToken(), strtok.nextToken(),
          strtok.nextToken(), strtok.nextToken(), strtok.nextToken(),
          strtok.nextToken(), strtok.nextToken(), strtok.nextToken());
    }
    else if(parser.getOptionValue(optConvert)!=null) {
      StringTokenizer strtok = new StringTokenizer(
          (String)parser.getOptionValue(optConvert), "#");
      convertResult(strtok.nextToken(), strtok.nextToken(), strtok.nextToken(),
          strtok.nextToken(), strtok.nextToken(),
          Integer.valueOf(strtok.nextToken()).intValue(),
          strtok.nextToken(), strtok.nextToken(), strtok.nextToken(),
          strtok.hasMoreTokens() ? strtok.nextToken() : null);
    }
    else if(parser.getOptionValue(optFile)!=null) {
      String fileName = (String)parser.getOptionValue(optFile);
      runFile(fileName);
    } else if (parser.getOptionValue(optLocal) != null) {
      runLocal();
    }
    else
      throw new RuntimeException("unknown arguments " + cmd);
  }

  protected void runTest(String version) {
    log.error(this + " does not define any tests, exiting...");
  }

  /**
   * Executes the given config file.
   *
   * @param fileName
   */
  protected void runFile(String fileName) {
      try {
      AbstractParams params = (AbstractParams) Util.readObject(fileName);
        run(params);
      } catch (Throwable e) {
        e.printStackTrace();
        System.exit(1);
      }
  }

  /**
   * Automatically executes a simulation within the distsim framework.
   * @throws Exception
   */
  protected void runDistSim() throws Exception {
    // load properties from stdin
    Properties configuration = new Properties();
    configuration.load(System.in);

    // Available properties:
    //
    // driver.config, simulationId, host.name, host.id, host.architecture, host.description
    // definitions.host, definitions.database, definitions.username, definitions.password,
    // results.host, results.database, results.username, results.password

    String configFile = configuration.getProperty("driver.config");
    AbstractParams options = (AbstractParams) Util.readObject(configFile);

    options.db = true;
    options.dbJobId = Integer.valueOf(configuration.getProperty("simulationId")).intValue();
    options.dbDef = "jdbc:mysql://" + configuration.getProperty("definitions.host")
        + "/" + configuration.getProperty("definitions.database");
    options.dbRes = "jdbc:mysql://" + configuration.getProperty("results.host")
        + "/" + configuration.getProperty("results.database");
    options.dbUser = configuration.getProperty("results.username");
    options.dbPassword = configuration.getProperty("results.password");

    try {
      run(options);
    } catch (Throwable e) {
      e.printStackTrace();
      System.exit(1);
    }
  }

  /**
   * Runs a local test defined in derived classes.
   */
  protected void runLocal() {
    throw new RuntimeException("no local tests defined, please overwrite this method");
  }

  /**
   * Evaluates a simulation study.
   *
   * @param dbUrl db url
   * @param dbUser db user
   * @param dbPasswd db password
   * @param driver name of the study driver for evaluation
   * @param version version of the study for evaluation
   * @throws Exception
   */
  protected void evalSimulationResults(String dbUrl, String dbUser, String dbPasswd,
      String driver, String version,
      String dbResUrl, String dbResUser, String dbResPasswd) throws Throwable {
    throw new RuntimeException("no evaluation defined, please overwrite this method");
  }

  protected void convertResult(String dbUrl, String dbUser, String dbPasswd,
      String driver, String version, int simulationId,
      String dbResUrl, String dbResUser, String dbResPasswd,
      String filter) throws Throwable {
    filter = filter.replace('_', ' ');
    DbBinaryLoader loader = new DbBinaryLoader(dbUrl, dbUser, dbPasswd);
//    List lstIds = loader.loadSimulationIds(driver, version);
//    if (!lstIds.contains(new Integer(simulationId)))
//      throw new Error("no simulation with id " + simulationId + " for driver " +
//          driver + "-" + version);

    StringTokenizer tok = new StringTokenizer(filter, "*");
    String[] filters = new String[tok.countTokens()];
    for (int i = 0; i < filters.length; i++)
      filters[i] = tok.nextToken();

    System.out.println("" + simulationId + ", " + filter);
    for (int i = 0; i < filters.length; i++)
      System.out.println(filters[i]);

    List dbEntries = loader.loadEntries(simulationId);
    if (null == dbEntries || dbEntries.size() <= 0)
      throw new Error("no simulation with id " + simulationId);

    DBSaver saver = new DBSaver(dbResUrl, dbResUser, dbResPasswd,
        dbResUrl, dbResUser, dbResPasswd);

    Iterator iter = dbEntries.iterator();
    b : while (null != iter && iter.hasNext()) {
      DbBinaryLoader.DbEntry entry = (DbBinaryLoader.DbEntry) iter.next();

      for (int i = 0; i < filters.length; i++)
        if (null != filter && !entry.path.contains(filters[i]))
          continue b;

      DataContribution contrib =
        (DataContribution) loader.load(simulationId, entry.id);
      System.out.println("Processing " + DataManager.getPath(contrib.getPath()));
//      saver.addToMapping(contrib);
      saver.save(contrib, DataManager.getPath(contrib.getPath()), false);
    }
    saver.finalize();
  }

  /**
   * Installs all necessary builders. Overwrite this method if you want to
   * install your custom builders.

   * @param options options for this run.
   */
  protected void installBuilders(AbstractParams options) {
    builderProvider = new Provider(this.dataManager);

    builderProvider.addBuilder(new NodeBuilder.Base());
    builderProvider.addBuilder(new NodeBuilder.Traffic());
    builderProvider.addBuilder(new NodeBuilder.Flow());
    builderProvider.addBuilder(new NodeBuilder.App());

    builderProvider.addBuilder(new FieldBuilder());
    builderProvider.addBuilder(new PathLossBuilder.FreeSpaceBuilder());
    builderProvider.addBuilder(new PathLossBuilder.TwoRayBuilder());
    builderProvider.addBuilder(new PathLossBuilder.LogDistanceBuilder());
    builderProvider.addBuilder(new PathLossBuilder.DistShadowingBuilder());
    builderProvider.addBuilder(new PathLossBuilder.ShadowingBuilder());

    builderProvider.addBuilder(new FadingBuilder.None());
    builderProvider.addBuilder(new FadingBuilder.Rayleigh());
    builderProvider.addBuilder(new FadingBuilder.Rician());
    builderProvider.addBuilder(new FadingBuilder.PunnooseRician());
    builderProvider.addBuilder(new FadingBuilder.Jakes());

    builderProvider.addBuilder(new RadioBuilder.DiversityReceiver());
    builderProvider.addBuilder(new RadioBuilder.NoiseIndep());
    builderProvider.addBuilder(new RadioBuilder.NoiseAdditive());
    builderProvider.addBuilder(new RadioBuilder.NoiseAdditiveTxDiv());
    builderProvider.addBuilder(new RadioBuilder.NoiseAdditiveBer());

    builderProvider.addBuilder(new MacBuilder.Multiple());
    builderProvider.addBuilder(new MacBuilder.Dumb());
    builderProvider.addBuilder(new MacBuilder.M802_11());
    builderProvider.addBuilder(new MacBuilder.M802_11e());
    builderProvider.addBuilder(new MacBuilder.M802_11TxDiv());
    builderProvider.addBuilder(new MacBuilder.MCExOR());

    builderProvider.addBuilder(new RateBuilder.Constant());
    builderProvider.addBuilder(new RateBuilder.Anno());
    builderProvider.addBuilder(new RateBuilder.Arf());
    builderProvider.addBuilder(new RateBuilder.Aarf());
    builderProvider.addBuilder(new RateBuilder.AmrrBuilder());
    builderProvider.addBuilder(new RateBuilder.Sample());

    builderProvider.addBuilder(new NetBuilder.Ip());
    builderProvider.addBuilder(new NetBuilder.QoS());
    builderProvider.addBuilder(new NetBuilder.IpNotify());
    builderProvider.addBuilder(new NetBuilder.TxDOR());

    builderProvider.addBuilder(new RouteBuilder.Table());
    builderProvider.addBuilder(new RouteBuilder.Dsr());
    builderProvider.addBuilder(new RouteBuilder.Aodv());
    builderProvider.addBuilder(new RouteBuilder.Zrp());
    builderProvider.addBuilder(new RouteBuilder.BrnDsr());
    builderProvider.addBuilder(new RouteBuilder.MCExOR());
    builderProvider.addBuilder(new RouteBuilder.TxDOR());

    builderProvider.addBuilder(new MetricBuilder.Etx());
    builderProvider.addBuilder(new MetricBuilder.Ett());
    builderProvider.addBuilder(new MetricBuilder.HopCount());

    builderProvider.addBuilder(new TransBuilder.Udp());
    builderProvider.addBuilder(new TransBuilder.Tcp());

    builderProvider.addBuilder(new AppBuilder.UdpNotify());
    builderProvider.addBuilder(new AppBuilder.Udp());

    builderProvider.addBuilder(new FlowBuilder.CbrUdp());
    builderProvider.addBuilder(new FlowBuilder.SaturatingUdp());
    builderProvider.addBuilder(new FlowBuilder.CaUdp());
    builderProvider.addBuilder(new FlowBuilder.Tcp());

    builderProvider.addBuilder(new TrafficBuilder.RandomBuilder());
    builderProvider.addBuilder(new TrafficBuilder.RandomSingleHop());
    builderProvider.addBuilder(new TrafficBuilder.RandomGatewayBuilder());
    builderProvider.addBuilder(new TrafficBuilder.HorizontalBuilder());
    builderProvider.addBuilder(new TrafficBuilder.RadialBuilder());
    builderProvider.addBuilder(new TrafficBuilder.RadialGatewayBuilder());
  }

  /**
   * @deprecated
   */
  private void addHandler(DataContributor handler, boolean boolOpt, int intOpt) {
    if (boolOpt) intOpt = DataManager.LEVEL_ALL;
    if (intOpt != DataManager.LEVEL_OFF) {
      handler.setLevel(intOpt);
      handler.registerHandlers();
      dataManager.add(handler);
    }
  }

  protected void addHandler(DataContributor handler, int intOpt) {
    if (intOpt != DataManager.LEVEL_OFF) {
      handler.setLevel(intOpt);
      handler.registerHandlers();
      dataManager.add(handler);
    }
  }

  /**
   * Installs event handlers for data and result acquisition. Overwrite this
   * method if you want to install additional handlers.
   *
   * @param options options for this run.
   * @throws Exception thrown if somethings wents wrong.
   */
  protected void installHandlers(AbstractParams options) throws Exception {
    // First, create the data manager. If started via distsim wrapper, the
    // job id is set externally and therefore not null.
    if (options.db && options.dbJobId != 0)
      dataManager = new DataManager.Database(options.dbRes, options.dbUser,
          options.dbPassword, options.dbJobId);
    else if (options.db && options.dbJobId == 0)
      dataManager = new DataManager.Database(options.dbDef, options.dbRes,
          options.dbUser, options.dbPassword);
    else if (options.file)
      dataManager = new DataManager.File(options.directory);
    else if (options.fileBinary)
      dataManager = new DataManager.FileBinary(options.directory);
    else
      dataManager = new DataManager.Discard();
    dataManager.open();

    JistAPI.runAt(new Runnable() {
      public void run() {
        try {
          dataManager.close();
        } catch (Exception e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    }, JistAPI.END);

    // Create the ui connector, if necessary.
    if (options.gui) {
      uiConnector = new UiConnector(dataManager);
      uiConnector.connect(options.uiHost, options.uiPort);
    }

    // Create data contributions, first the config item
    addHandler(new ConfigContributor(options), false,
        DataManager.LEVEL_ALL);

    // forward graph handler, option .handlerForwardGraph
    addHandler(new ForwardGraphHandlers(), options.handlerForwardGraph,
        options.handlerForwardGraphLevel);

    // link table information handler, option .handlerLinkTable
    addHandler(new LinkTableHandlers(), options.handlerLinkTable,
        options.handlerLinkTableLevel);

    // link quality information handler, option .handlerLinkQuality
    addHandler(new LinkQualityHandler(), options.handlerLinkQuality,
        options.handlerLinkQualityLevel);

    // collect information on field layer
    addHandler(new StatsFieldHandler(options.nodes), options.handlerField,
        options.handlerFieldLevel);

    // collect information on radio layer
    addHandler(new StatsRadioHandler(options.sampleLen), options.handlerRadio,
        options.handlerRadioLevel);

    // collect information on radio layer
    addHandler(new StatsRadioDivHandler(options.sampleLen), options.handlerRadioEx,
        options.handlerRadioDivLevel);

    // collect information on mac layer
    addHandler(new StatsMacHandler(options.sampleLen), options.handlerMac,
        options.handlerMacLevel);

    // collect information on mac layer in rate module
    addHandler(new StatsRateHandler(options.sampleLen), options.handlerRate,
        options.handlerRateLevel);

    // collect information on net layer
    addHandler(new StatsNetHandler(options.sampleLen), options.handlerNet,
        options.handlerNetLevel);

    // collect information on route layer
    addHandler(new StatsRouteHandler(options.sampleLen), options.handlerRoute,
        options.handlerRouteLevel);

    // collect information on route layer
    addHandler(new StatsTransHandler(options.sampleLen), options.handlerTrans,
        options.handlerTransLevel);

    // flow information handler, option .handlerFlow
    addHandler(new StatsFlowHandlers(options.sampleLen), options.handlerFlow,
        options.handlerFlowLevel);

    // nam on field layer
    if (options.dumpFieldNam != null && options.dumpFieldNam.length() > 0) {
      JavisFieldEventHandler jeh = new JavisFieldEventHandler(
          JavisTrace.createTrace(options.dumpFieldNam));
      jeh.registerHandlers();
    }

    // nam on NET layer
    if (options.dumpNetNam != null && options.dumpNetNam.length() > 0) {
      JavisNetEventHandler jeh = new JavisNetEventHandler(
          JavisTrace.createTrace(options.dumpNetNam));
      jeh.registerHandlers();
    }

    // nam on MAC layer
    if (options.dumpMacNam != null && options.dumpMacNam.length() > 0) {
      JavisMacEventHandler jeh = new JavisMacEventHandler(
          JavisTrace.createTrace(options.dumpMacNam));
      jeh.registerHandlers();
    }

    // nam on Radio layer
    if (options.dumpRadioNam != null && options.dumpRadioNam.length() > 0) {
      JavisRadioEventHandler jeh = new JavisRadioEventHandler(
          JavisTrace.createTrace(options.dumpRadioNam));
      jeh.registerHandlers();
    }

    // GANTT diagram
    // mac layer
    if (options.dumpMacGantt != null && options.dumpMacGantt.length() > 0) {
      GanttMacEventHandler jeh = new GanttMacEventHandler(
          GanttTrace.createTrace(options.dumpMacGantt));
      jeh.registerHandlers();
    }

    // GANTT diagram
    // radio layer (used channel)
    if (options.dumpRadioChannelGantt != null && options.dumpRadioChannelGantt.length() > 0) {
      GanttRadioChannelEventHandler jeh = new GanttRadioChannelEventHandler(
          GanttTrace.createTrace(options.dumpRadioChannelGantt));
      jeh.registerHandlers();
    }
    // radio layer (radioo mode)
    if (options.dumpRadioModeGantt != null && options.dumpRadioModeGantt.length() > 0) {
      GanttRadioModeEventHandler jeh = new GanttRadioModeEventHandler(
          GanttTrace.createTrace(options.dumpRadioModeGantt));
      jeh.registerHandlers();
    }

    // Radio Timebar
    addHandler(new TimeBarRadioPacketHandler(), options.handlerRadioTimeBar,
        options.handlerRadioTimeBarLevel);
    addHandler(new TimeBarRadioStateHandler(), options.handlerRadioTimeBar,
        options.handlerRadioTimeBarLevel);

    // Mac Timebar
    addHandler(new TimeBarMacStateHandler(), options.handlerMacTimeBar,
        options.handlerMacTimeBarLevel);
    addHandler(new TimeBarMacPacketHandler(), options.handlerMacTimeBar,
        options.handlerMacTimeBarLevel);

    //  collect information on mac layer
    addHandler(new TimeBarTXOPStateHandler(), options.handlerTXOP,
        options.handlerTXOPLevel);

    // Net Timebar
    addHandler(new TimeBarNetQueueHandler(), options.handlerNetTimeBar,
        options.handlerNetTimeBarLevel);
  }

  /**
   * Constructs field and nodes with given command-line options, establishes
   * client/server pairs and starts them.
   *
   * @param opts
   *          command-line parameters
   * @throws Exception
   */
  private final void buildField(AbstractParams opts) throws Exception {
    // Do not use 0 as seed, since 0 is ignored and the system entropy is used instead
    //Util.assertion(0 != opts.seed);
    if (0 == opts.seed) {
      System.err.println("You are using 0 as random seed. " +
          "Random numbers will be different for each simulation");
    }

    // set the random seed, if necessary
    Constants.random = SecureRandom.getInstance("SHA1PRNG");
    Constants.random.setSeed(opts.seed);

    Constants.placementRandom = SecureRandom.getInstance("SHA1PRNG");
    Constants.placementRandom.setSeed(opts.seed);

    if (opts.endTime > 0) {
      JistAPI.endAt(opts.endTime * Constants.SECOND);
    }

    // Init the radio tracer
    // TODO move somewhere else
    if (opts.dumpRadio != null && 0 < opts.dumpRadio.length()) {
      final RadioDump radioDump = new RadioDump(new FileWriter(opts.dumpRadio),
          RadioDump.METHOD_PLAIN, RadioDump.LEVEL_BASE);
      JistAPI.runAt(new Runnable() {
        public void run() {
          try {
            radioDump.exit();
          } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }
      }, JistAPI.END);
    }

    // Check assertion flag, ...
    Util.assertion(Main.ASSERT == opts.assertion);

    // build field
    FieldBuilder fieldBuilder = (FieldBuilder) builderProvider.getBuilder(opts.field);
    Field field = (Field) fieldBuilder.build(opts.field, null);
    builderProvider.addHookUp(fieldBuilder, opts.field, null, field);

    // old interface
    {
      // start building nodes here...
      // create each node
      for (int i = opts.nodesStart; i < opts.nodes + opts.nodesStart; i++) {
        Node node = buildNode(opts.node, i, field);
        nodes.add(node);

        // TODO move to installHandlers
        if (opts.dumpMac)
          wiresharkMacHandler = WiresharkEventHandler.enableDumping(wiresharkMacHandler,
              node.getMac(0));
        // TODO move to installHandlers
        if (opts.dumpNet)
          wiresharkNetHandler = WiresharkEventHandler.enableDumping(wiresharkNetHandler,
              node.getNet());

        // trans + app
        setupApplication(opts, i, node);
      }

      buildAdditionNodes(opts, opts.nodes+opts.nodesStart, field);
    }

    // new parameter interface
    int nextNodeId = opts.nodes + opts.nodesStart;
    for (int j = 0; j < opts.nodeNumber.length; j++) {
      if (0 >= opts.nodeNumber[j])
        continue;

      for (int i = 0; i < opts.nodeNumber[j]; i++) {
        int nodeId = nextNodeId++;
        Node node = buildNode(opts.nodeParams[j], nodeId, field);
        nodes.add(node);

        // TODO move to installHandlers
        if (opts.dumpMac)
          wiresharkMacHandler = WiresharkEventHandler.enableDumping(wiresharkMacHandler,
              node.getMac(0));
        // TODO move to installHandlers
        if (opts.dumpNet)
          wiresharkNetHandler = WiresharkEventHandler.enableDumping(wiresharkNetHandler,
              node.getNet());

        // trans + app
        setupApplication(opts, nodeId, node);
      }
    }


    // hookup all builders
    builderProvider.hookUp();
    postHookUp(opts);
  } // buildField

  /**
   * Build a node.
   *
   */
  protected Node buildNode(NodeBuilder.NodeParams params, int nodeId, Field field)
      throws BuilderException {

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

    Builder builder = builderProvider.getBuilder(params);
    builder.build(params, node);
    builderProvider.addHookUp(builder, params, node, node);

    return node;
  }

  /**
   * Called after all lower layers have been initialized. Could be used to create
   * transport and application objects.
   *
   * @param opts the current opts.
   * @param nodeId
   * @param node
   */
  protected void setupApplication(AbstractParams opts, int nodeId, Node node)
      throws BuilderException {
    // nothing
  }

  /**
   * Overwrite if you want to build additional nodes
   *
   * @param opts the current opts.
   * @param nextNodeId the next node id for use.
   * @param field the field object
   * @throws BuilderException in the case of errors
   *
   * @deprecated use {@link AbstractParams#nodeParams}
   */
  protected void buildAdditionNodes(AbstractParams opts, int nextNodeId,
      Field field) throws BuilderException {
    // nothing
  }

  /**
   * Called after successful hookup. Overwrite if you want to do some
   * custom initialization.
   *
   * @param params
   * @throws BuilderException
   */
  protected void postHookUp(AbstractParams params) throws BuilderException {
    // nothing
  }

}