package test.sim.scenario;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetIp;
import jist.swans.trans.TransUdp;
import brn.distsim.ormapper.util.DBSaver;
import brn.distsim.ormapper.util.DbBinaryLoader;
import brn.sim.AbstractDriver;
import brn.sim.AbstractParams;
import brn.sim.ScriptParams2;
import brn.sim.ScriptParams2.NodeConfig;
import brn.sim.builder.BuilderException;
import brn.sim.builder.MacBuilder;
import brn.sim.builder.MetricBuilder;
import brn.sim.builder.RateBuilder;
import brn.sim.builder.RouteBuilder;
import brn.sim.builder.RouteBuilder.BrnDsrParams;
import brn.sim.data.DiagramData;
import brn.sim.data.FlowStats;
import brn.sim.data.MacStats;
import brn.sim.data.PropertiesData;
import brn.swans.app.UdpApplication;


public class Mac802_11FreeTest4 extends AbstractDriver {
  public static class SimulationResult {
    public String driver;
    public String version;
    public int simulationId;

    public int seed;
    public int nodes;
    public int flows;
    public int ac;
    public int ap;
    public int txrate;
    public int txrate2;
    public int packetSize;
//    public double shadowStdDev;
    public int csSize;
//    public int globFwdTable;
    public int useRts;
//    public int dataTxDiv;
//    public int ignoreRtsCtsNav;
//    public int useAckCancelation;
//    public int metricDelta;
    public int csSelection;
    public int dataRate;
    public int controlRate;
    public String metric;
//    public int promiscStorePackets;
//    public int noCandSets;
//    public int scaleRemainingHops;

    public double avgThroughput;
    /** ratio relative to the corresponding traditional transmission run */
    public double avgThroughputRatio;
    /** ratio relative to the corresponding traditional transmission run w/o rts */
    public double avgNonRtsThroughputRatio;
    public double avgDelay;
    public double avgDelayRatio;
    public double avgNonRtsDelayRatio;
    public int sendPackets;
    public int recvPackets;
    public double flowEnd;
    public double avgHopCount;
    public double jitter;
    public double jittersqr;

    public double avgThroughput1;
    public double avgDelay1;
    public int sendPackets1;
    public int recvPackets1;
    public double flowEnd1;
    public double jitter1;
    public double jitter1sqr;

    public double avgThroughput2;
    public double avgDelay2;
    public int sendPackets2;
    public int recvPackets2;
    public double flowEnd2;
    public double jitter2;
    public double jitter2sqr;

    public double radAvgDelaySpread;
    public double radAvgPowerDelta;
    public int radTxDivCount;

    public int macDrops;
    public int macDuplicates;
    public double macBackoffCuml;
    public int macShortRetries;
    public int macLongRetries;
    public int macCanceledAcks;
    public int macSendImmediate;
    public MacStats macStats;

    public int rtgDuplicate;
    public int rtgDrops;
  }
  //static ScriptParams params;

  @Override
  public List getSimulationSuite(String version) {
    List r = new ArrayList();
    // seeds 4
    for (int seed = 1; seed < 5; ++seed) {
      // number of nodes 3
      for (int nodeNum = 2; nodeNum < 3; ++nodeNum) {
        // number of flows 1

            // packetsize 3
            for (int size : new int[]{50, 512, 1460}) {
              // throughput 4
              for (long througput : new long[]{3000000, 6000000, 9000000, 12000000}) {
                int txrate = (int) (througput / (long) (size * 8));

                for (long througput2 : new long[]{3000000, 6000000, 9000000, 12000000}) {
                  int txrate2 = (int) (througput2 / (long) (size * 8));
                ScriptParams2 sp = new ScriptParams2();
                sp.setSeed(seed);
                ArrayList nodes = new ArrayList();


                    NodeConfig nc = new NodeConfig();
                    NodeConfig nc2 = new NodeConfig();
                    nc.init(1, 2, 0, size, 25 * Constants.SECOND, 35 * Constants.SECOND, 9000 +1, 9000 + 1, txrate, txrate2);
                    nc2.init(1, 2, 1, size, 25 * Constants.SECOND, 35 * Constants.SECOND, 9000 +2, 9000 + 2, txrate2, txrate2);
                    nodes.add(nc);
                    nodes.add(nc2);


                if (nodes.size() > 0) {
                  NodeConfig[] noarr = new NodeConfig[nodes.size()];
                  for (int i = 0; i < nodes.size(); ++i) {
                    noarr[i] = (NodeConfig) nodes.get(i);
                  }
                  sp.setNodeConfigs(noarr);
                  r.add(sp);
                }
              }
            }
        }
      }
    }
    return r;
  }

  @Override
  protected void runLocal() {
    ScriptParams2 p = (ScriptParams2) getSimulationSuite("").get(23);
    try {
      run(p);
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  protected void setupApplication(AbstractParams opts, int nodeId, Node node) throws BuilderException {
    super.setupApplication(opts, nodeId, node);

    ScriptParams2 params = (ScriptParams2) opts;

    int nodeNumber = 1;
    for (NodeConfig nc : params.getNodeConfigs()) {
      if (nc.getNodeNumber() > nodeNumber) {
        nodeNumber = nc.getNodeNumber();
      }
      if (nc.getDestNodeNumber() > nodeNumber) {
        nodeNumber = nc.getDestNodeNumber();
      }
    }
    params.setNodes(nodeNumber);
    TransUdp udp = new TransUdp();
    for (NodeConfig nc : params.getNodeConfigs()) {
      if (nodeId == nc.getNodeNumber() || nodeId == nc.getDestNodeNumber()) {
        final NetIp net = (NetIp)node.getNet();

        UdpApplication app = null;
        long startTime = nc.getStartTime() + params.getRnd().nextInt(500) * Constants.MILLI_SECOND;
        if (nc.getNodeNumber() == nodeId) {
          MessageAnno anno = new MessageAnno();

          anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(nc.getFlowNumber()));

          byte priority = Constants.NET_PRIORITY_D_BESTEFFORT;

          app = new UdpApplication(net.getAddress(), nc.getSourcePort(),
              new NetAddress(nc.getDestNodeNumber()), nc.getDestPort(), startTime, nc.getEndTime(), nc.getTxRate(), priority,
              new byte[nc.getPacketSize()], anno);
//          udp.setFlow(nc.getFlowNumber(), net.getAddress(), nc.getDestPort());
        }

        if (nc.getDestNodeNumber() == nodeId) {
          app = new UdpApplication(net.getAddress(), nc.getDestPort(), startTime, nc.getEndTime());
//          udp.setFlow(nc.getFlowNumber(), net.getAddress(), nc.getDestPort());
        }

        udp.setNetEntity(net.getProxy());
        net.setProtocolHandler(Constants.NET_PROTOCOL_UDP, udp.getProxy());
        app.setUdpEntity(udp.getProxy());

        node.addTransport(udp);
        node.addApplication(app);

        app.getProxy().run();
      }
    }
  }

  protected Object load(DbBinaryLoader loader, int simulationId, String configPath,
      boolean error) throws Exception  {
    Object ret = loader.load(simulationId, configPath);
    if (null == ret && error) {
      throw new Error("Object with path " + configPath + " not found for id " + simulationId);
    }
    return ret;
  }

  protected Object load(DbBinaryLoader loader, int simulationId, String configPath)
  throws Exception  {
    Object ret = loader.load(simulationId, configPath);
    if (null == ret) {
      throw new Error("Object with path " + configPath + " not found for id " + simulationId);
    }
    return ret;
  }

  /**
   * 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 {
    DbBinaryLoader loader = new DbBinaryLoader(dbUrl, dbUser, dbPasswd);
    List lstIds = loader.loadSimulationIds(driver, version);
    DBSaver saver = new DBSaver(dbResUrl, dbResUser, dbResPasswd,
        dbResUrl, dbResUser, dbResPasswd);

    List results = new LinkedList();
    if (null == lstIds || lstIds.size() <= 0)
      throw new Error("no simulations found for driver " + driver +
          " and version " + version);

    for(int i = 0; i < lstIds.size(); i++) {
      SimulationResult result = new SimulationResult();
      result.simulationId = ((Integer)lstIds.get(i)).intValue();
      result.driver = driver;
      result.version = version;

      try {
        { // read configuration
          ScriptParams2 params = (ScriptParams2)
            load(loader, result.simulationId, "Global, Config");
          result.seed = params.seed;
          result.nodes = params.nodes;
          result.txrate = params.getNodeConfigs()[0].getTxRate();
          result.txrate2 = params.getNodeConfigs()[0].getTxRate2();
          result.packetSize = params.getNodeConfigs()[0].getPacketSize();
          result.flows = params.getNodeConfigs().length;
//          result.shadowStdDev = ((DistShadowingParams)
//              ((FieldParams)params.field).pathloss).stdDeviation;
          result.csSize = 1;
//          result.globFwdTable = 0;
          result.csSelection = -1;
//          result.metricDelta = 0;
//          if (params.node.route instanceof RouteBuilder.TxDORParams) {
//            TxDORParams txDORParams = ((RouteBuilder.TxDORParams)params.node.route);
//            result.csSize = txDORParams.candidateSetSize;
//            result.globFwdTable = ((RouteBuilder.TxDORParams)params.node.route).globalForwarderTable ? 1 : 0;
//            result.metricDelta = ((RouteBuilder.TxDORParams)params.node.route).metricDelta;
//            result.csSelection = txDORParams.candidateSelection;
//            result.noCandSets = txDORParams.noCandSets;
//            result.scaleRemainingHops = txDORParams.scaleRemainingHops ? 1 : 0;
//          }
          MacBuilder.M802_11Params mac = (MacBuilder.M802_11Params)params.node.mac;
          result.useRts = mac.thresholdRts < 1000 ? 1 : 0;
          result.dataRate = ((RateBuilder.ConstantParams)mac.rateSelection).dataBitrate;
          result.controlRate = ((RateBuilder.ConstantParams)mac.rateSelection).controlBitrate;
//          result.dataTxDiv = false ? 1 : 0;
//          result.ignoreRtsCtsNav = false ? 1 : 0;
//          result.useAckCancelation = false ? 1 : 0;
//          result.promiscStorePackets = false ? 1 : 0;
          if (params.node.mac instanceof MacBuilder.M802_11TxDivParams) {
//            result.dataTxDiv = ((MacBuilder.M802_11TxDivParams)params.node.mac).useDataTxDiv ? 1 : 0;
//            result.ignoreRtsCtsNav = ((MacBuilder.M802_11TxDivParams)params.node.mac).ignoreRtsCtsNav ? 1 : 0;
//            result.useAckCancelation = ((MacBuilder.M802_11TxDivParams)params.node.mac).useAckCancelation ? 1 : 0;
//            result.promiscStorePackets = ((MacBuilder.M802_11TxDivParams)params.node.mac).promiscStorePackets ? 1 : 0;
          }
          RouteBuilder.BrnDsrParams route = (BrnDsrParams) params.node.route;
          if (route.metric.getClass().equals(MetricBuilder.EttParams.class))
            result.metric = "ETT";
          else if (route.metric.getClass().equals(MetricBuilder.EtxParams.class))
            result.metric = "ETX";
          else
            result.metric = route.metric.getClass().getSimpleName();
        }

        double[] rawdata;
        double sum = 0;

        { // flow parameters
          PropertiesData data = (PropertiesData)
            load(loader, result.simulationId, "All flows, Application, Flow stats");
          FlowStats stats = (FlowStats) data.getProperties();
          result.avgThroughput = stats.getAvgThroughput();
          result.avgDelay = stats.getAvgDelay();
          result.sendPackets = stats.getSendPackets();
          result.recvPackets = stats.getRecvPackets();
          result.flowEnd = stats.getEnd();
          result.avgHopCount = stats.getAvgHopCount();

          DiagramData dd = (DiagramData) load(loader, result.simulationId, "All Flows, Application, packet delay");
          rawdata = dd.getY();
          sum = 0;
          for (int j = 0; j < rawdata.length; ++j) {
            sum += Math.pow((rawdata[j] - stats.getAvgDelay() * 1000), 2);
          }
          result.jittersqr = (sum / rawdata.length) / 1000;
          result.jitter = Math.sqrt(sum / rawdata.length) / 1000;
        }

        switch (result.flows) {
        case 2:
          PropertiesData data2 = (PropertiesData)
                  load(loader, result.simulationId, "Flow 1, Application, Flow stats");
//          PropertiesData data2througput = (PropertiesData)
//                  load(loader, result.simulationId, "Flow 1, througput (avg\'d)");
//          DiagramData dd = (DiagramData) data2througput.getProperties();
//          dd.getY()
          FlowStats stats2 = (FlowStats) data2.getProperties();
          result.avgThroughput2 = stats2.getAvgThroughput();
          result.avgDelay2 = stats2.getAvgDelay();
          result.sendPackets2 = stats2.getSendPackets();
          result.recvPackets2 = stats2.getRecvPackets();
          result.flowEnd2 = stats2.getEnd();

          DiagramData dd2 = (DiagramData) load(loader, result.simulationId, "Flow 1, Application, packet delay");
          rawdata = dd2.getY();
          sum = 0;
          for (int j = 0; j < rawdata.length; ++j) {
            sum += Math.pow((rawdata[j] - stats2.getAvgDelay() * 1000), 2);
          }
          result.jitter2sqr = (sum / rawdata.length) / 1000;
          result.jitter2 = Math.sqrt(sum / rawdata.length) / 1000;
        case 1:
          PropertiesData data1 = (PropertiesData)
                  load(loader, result.simulationId, "Flow 0, Application, Flow stats");
          FlowStats stats1 = (FlowStats) data1.getProperties();
          result.avgThroughput1 = stats1.getAvgThroughput();
          result.avgDelay1 = stats1.getAvgDelay();
          result.sendPackets1 = stats1.getSendPackets();
          result.recvPackets1 = stats1.getRecvPackets();
          result.flowEnd1 = stats1.getEnd();

          DiagramData dd1 = (DiagramData) load(loader, result.simulationId, "Flow 0, Application, packet delay");
          rawdata = dd1.getY();
          sum = 0;
          for (int j = 0; j < rawdata.length; ++j) {
            sum += Math.pow((rawdata[j] - stats1.getAvgDelay() * 1000), 2);
          }
          result.jitter1sqr = (sum / rawdata.length) / 1000;
          result.jitter1 = Math.sqrt(sum / rawdata.length) / 1000;

          break;
        default:
          System.err.println("too many flows; collecting stats only for subset...");
        }

        { // mac stats
          PropertiesData data = (PropertiesData)
            load(loader, result.simulationId, "Global, Mac, Stats", true);
          result.macStats = (MacStats) data.getProperties();
        }

        { // mac drops
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Mac, discard vs time", false);
          result.macDrops = null == data ? 0 : data.getY().length;
        }

        { // mac dups
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Mac, duplicate vs time", false);
          result.macDuplicates = null == data ? 0 : data.getY().length;
        }

//        { // mac backoff
//          DiagramDataHist data = (DiagramDataHist)
//            load(loader, result.simulationId, "Global, Mac, backoff (cuml)");
//          double[] bos = data.getY();
//          double bo = .0;
//          for (int j = 0; j < bos.length; j++) bo += bos[j];
//          result.macBackoffCuml = bo;
//        }

        { // mac short retries
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Mac, short retries vs time", false);
          result.macShortRetries = data == null ? 0 : data.getY().length;
        }

        { // mac long retries
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Mac, long retries vs time", false);
          result.macLongRetries = data == null ? 0 : data.getY().length;
        }

        { // mac ack cancelation
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Mac, ack cancelation", false);
          result.macCanceledAcks = data == null ? 0 : data.getY().length;
        }

        { // mac send immediate
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Mac, send immediate", false);
          result.macSendImmediate = data == null ? 0 : data.getY().length;
        }

        { //
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Routing, duplicate vs time", false);
          result.rtgDuplicate = data == null ? 0 : data.getY().length;
        }

        { //
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Routing, ", false);
          result.rtgDrops = data == null ? 0 : data.getY().length;
        }

        { // radio txdiv delay spread
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Radio, txdiv delay spread", false);
          double[] spreads = (data == null ? null : data.getY());
          result.radAvgDelaySpread = 0;
          if (null != spreads && spreads.length > 0) {
            double spread = .0;
            for (int j = 0; j < spreads.length; j++) spread += spreads[j];
            result.radAvgDelaySpread = spread / spreads.length;
          }
        }

        { // radio txdiv delay spread
          DiagramData data = (DiagramData)
            load(loader, result.simulationId, "Global, Radio, txdiv power delta", false);
          double[] deltas = (data == null ? null : data.getY());
          result.radAvgPowerDelta = 0;
          result.radTxDivCount = 0;
          if (null != deltas && deltas.length > 0) {
            double delta = .0;
            for (int j = 0; j < deltas.length; j++) delta += deltas[j];
            result.radAvgPowerDelta = delta / deltas.length;
            result.radTxDivCount = deltas.length;
          }
        }

//        {
//          // TODO hugo
//          DescriptiveStatistics stats = DescriptiveStatistics.newInstance();
//          DiagramData data = (DiagramData)
//            load(loader, result.simulationId, "Flow 0, packet delay", true);
//          double[] y = data.getY();
//          for (int j = 1; j < y.length; j++)
//            stats.addValue(y[j] - y[j-1]);
//
//          DelayStats delayStats = new DelayStats(result.simulationId,
//              stats.getMin(),
//              stats.getPercentile(5.),
//              stats.getMean(),
//              stats.getPercentile(95.),
//              stats.getMax(),
//              stats.getStandardDeviation());
//          saver.save(delayStats, driver + "-" + version, false);
//        }

//        {
//          // TODO hugo
//          DescriptiveStatistics stats = DescriptiveStatistics.newInstance();
//          DiagramData data = (DiagramData)
//            load(loader, result.simulationId, "Flow 0, packet arrival vs time", true);
//          double[] y = data.getY();
//          for (int j = 0; j < y.length; j++)
//            stats.addValue(y[j]);
//          double[] sortedY = stats.getSortedValues();
//          stats.clear();
//          for (int j = 0; j < y.length; j++)
//            stats.addValue(y[j] - sortedY[j]);
//
////          ReorderDensityStats rdStats = new ReorderDensityStats(result.simulationId,
////              stats.getMin(),
////              stats.getPercentile(5.),
////              stats.getMean(),
////              stats.getPercentile(95.),
////              stats.getMax(),
////              stats.getStandardDeviation());
////          saver.save(rdStats, driver + "-" + version, false);
//        }

        /* TODO
         * radio utilization
         * routing number of hops, routing drops
         */

      }
      catch (Error e) {
        System.err.println(e.getMessage());
        continue;
      }
      catch (Throwable e) {
        e.printStackTrace();
        continue;
      }

      results.add(result);
    }

    for (int i = 0; i < results.size(); i++) {
      SimulationResult res = (SimulationResult)results.get(i);
      if (res.csSize != 1 ) {
        res.avgThroughputRatio = -1.;
        res.avgNonRtsThroughputRatio = -1.;
        res.avgDelayRatio = -1.;
        res.avgNonRtsDelayRatio = -1.;

        for (int j = 0; j < results.size(); j++) {
          SimulationResult other = (SimulationResult)results.get(j);
          if (other.seed == res.seed
              &&other.nodes == res.nodes
//              &&other.shadowStdDev == res.shadowStdDev
              &&other.useRts == res.useRts
              &&other.csSize == 1
              &&other.dataRate == res.dataRate
              &&other.controlRate == res.controlRate) {
            res.avgThroughputRatio = res.avgThroughput / other.avgThroughput;
            if (Double.isNaN(res.avgThroughputRatio))
              res.avgThroughputRatio = .0;
            other.avgThroughputRatio = 1.;
            res.avgDelayRatio = res.avgDelay / other.avgDelay;
            if (Double.isNaN(res.avgDelayRatio))
              res.avgDelayRatio = .0;
            other.avgDelayRatio = 1.;
            break;
          }
        }

        for (int j = 0; j < results.size(); j++) {
          SimulationResult other = (SimulationResult)results.get(j);
          if (other.seed == res.seed
              &&other.nodes == res.nodes
//              &&other.shadowStdDev == res.shadowStdDev
              &&other.useRts == 0
              &&other.csSize == 1
              &&other.dataRate == res.dataRate
              &&other.controlRate == res.controlRate) {
            res.avgNonRtsThroughputRatio = res.avgThroughput / other.avgThroughput;
            if (Double.isNaN(res.avgNonRtsThroughputRatio))
              res.avgNonRtsThroughputRatio = .0;
            other.avgNonRtsThroughputRatio = 1.;
            res.avgNonRtsDelayRatio = res.avgDelay / other.avgDelay;
            if (Double.isNaN(res.avgNonRtsDelayRatio))
              res.avgNonRtsDelayRatio = .0;
            other.avgNonRtsDelayRatio = 1.;
            break;
          }
        }
      }
    }

//    System.out.println(SimulationResult.getHeader());
//    for (int i = 0; i < results.size(); i++) {
//      System.out.println(((SimulationResult)results.get(i)).toString());
//    }

    for (int i = 0; i < results.size(); i++)
      saver.save(results.get(i), driver + "-" + version, false);
    saver.finalize();
  }

  public static void main(String[] args) {
    Mac802_11FreeTest4 test = new Mac802_11FreeTest4();

    if (args.length < 1) {
      test.showUsage();
      return;
    }

    try {
      test.run(args);
//    params = new ScriptParams2();
//
//    test.run(params);
    } catch (Exception e) {
      throw new RuntimeException(e);
    } catch (Throwable e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}
