package brn.sim.scenario.txdor;

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

import jist.swans.Constants;

import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;

import brn.distsim.ormapper.util.DBSaver;
import brn.distsim.ormapper.util.DbBinaryLoader;
import brn.sim.builder.MacBuilder;
import brn.sim.builder.MetricBuilder;
import brn.sim.builder.RateBuilder;
import brn.sim.builder.RouteBuilder;
import brn.sim.builder.FlowBuilder.CbrUdpParams;
import brn.sim.builder.FieldBuilder.FieldParams;
import brn.sim.builder.MacBuilder.M802_11Params;
import brn.sim.builder.PathLossBuilder.DistShadowingParams;
import brn.sim.builder.RadioBuilder.NoiseParams;
import brn.sim.builder.RouteBuilder.BrnDsrParams;
import brn.sim.builder.RouteBuilder.TxDORParams;
import brn.sim.builder.TrafficBuilder.RadialGatewayParams;
import brn.sim.builder.TrafficBuilder.RandomParams;
import brn.sim.data.DiagramData;
import brn.sim.data.DiagramDataHist;
import brn.sim.data.FlowStats;
import brn.sim.data.MacStats;
import brn.sim.data.PropertiesData;

/**
 * Simulations with multiple non-saturating TDiCOR or DSR flows, 
 * arranged randomly.
 * @deprecated
 * @author kurth
 */

public class MultiFlowCBR extends TxDOrSim {

  public static void main(String[] args) throws Throwable {
    MultiFlowCBR test = new MultiFlowCBR();
    test.run(args);
  }

  public static TxDOrParams getParams(int nodes, int seed, double stdDev,
      int csSize, int bitrate, int flows, int packetSize, int txRate, int rtsTh, 
      boolean gateway, boolean bidirectional) {
    TxDOrParams params = (csSize != 1 ?
        TxDOrParams.defaultParams(new TxDOrParams()) :
          TxDOrParams.unicastParams(new TxDOrParams()));
    params.seed = seed;
    params.nodes = nodes;

    FieldParams field = (FieldParams) params.field;
    field.fieldX = 2000;
    field.fieldY = 2000;

    DistShadowingParams pathLoss = (DistShadowingParams) field.pathloss;
    pathLoss.stdDeviation = stdDev;

    NoiseParams radio = (NoiseParams) params.node.radio;
    radio.fieldX = field.fieldX;
    radio.fieldY = field.fieldY;
    radio.placement = Constants.PLACEMENT_RANDOM;
    radio.threshold = .8;
    radio.min_connectivity_betweenNodes = 2;

    ((M802_11Params)params.node.mac).thresholdRts = rtsTh;
    ((M802_11Params)params.node.mac).messageQueueCapacity = 50;

    // 50000 for DSR/ETT, 500 for TDiCOR
    // TODO ETX bounds for DSR/ETT
    params.setMetricAndBitRate(false, (csSize != 1 ? 500 : 50000),
        bitrate, Constants.BANDWIDTH_6Mbps);

    if (csSize != 1) {
      RouteBuilder.TxDORParams route = (RouteBuilder.TxDORParams) params.node.route;
      route.candidateSetSize = csSize;
    }
    
    CbrUdpParams udpParams = new CbrUdpParams();
    udpParams.start = 80000;
    udpParams.end = 100000;
    udpParams.udpPayloadLen = packetSize;
    udpParams.udpTxRate = txRate;
    
    if (gateway) {
      RadialGatewayParams traffic = new RadialGatewayParams();
      traffic.flowParams = udpParams;
      traffic.flows = flows;
      traffic.useHalfCircle = false;
      traffic.startOffset = 10000 / flows;
      traffic.twoWay = bidirectional;
      params.trafficParams = traffic;
    } else {
      RandomParams traffic = new RandomParams();
      traffic.flowParams = udpParams;
      traffic.seed = seed;
      traffic.flows = flows;
      traffic.startOffset = 10000 / flows;
      traffic.twoWay = bidirectional;
      params.trafficParams = traffic;
    }
    return params;
  }

  /**
   * Benchmark TDiCOR vs. DSR at 6/12 Mbps, multi-flow
   */
  public static List getSimulationSuite201() {
    List ret = new ArrayList();
  
    for (int nodes = 200; nodes <= 200; nodes *= 2) {
      for (int seed = 1; seed <= 20; seed++) {
        for (int stdDev = 4; stdDev <= 12; stdDev += 4) {
          for (int csSize = 1; csSize <= 4; csSize++) {
            for (int bitrate = Constants.BANDWIDTH_6Mbps; bitrate <= Constants.BANDWIDTH_6Mbps; bitrate *= 2) {
              for (int gateway = 0; gateway <= 1; gateway ++) {
                for (int flows = 2; flows <= 20; flows += 3) {
                  // Parameters like G.711 with 20ms packetization and 160 byte
                  TxDOrParams params = getParams(nodes, seed, stdDev, csSize,
                      bitrate, flows, 160, 50, 100, gateway == 1, false);
  
                  ret.add(params.setParamsBatch());
                }
              }
            }
          }
        }
      }
    }
  
    return ret;
  }

  /**
   * Benchmark TDiCOR vs. DSR at 6/12 Mbps, multi-flow
   * 
   * 202, 203, 205
   */
  public static List getSimulationSuite202() {
    List ret = new ArrayList();
  
    for (int nodes = 100; nodes <= 100; nodes *= 2) {
      for (int seed = 1; seed <= 10; seed++) {
        for (int stdDev = 4; stdDev <= 12; stdDev += 4) {
          for (int csSize = 1; csSize <= 4; csSize++) {
            for (int bitrate = Constants.BANDWIDTH_6Mbps; bitrate <= Constants.BANDWIDTH_6Mbps; bitrate *= 2) {
              for (int flows = 2; flows <= 10; flows += 2) {
                for (int gateway = 0; gateway <= 1; gateway ++) {
                  for (int bidirectional = 0; bidirectional <= 1; bidirectional++) {

                    // Parameters like G.711 with 20ms packetization and 160 byte
                    int effectiveFlows = (bidirectional == 0 ? flows : flows/2 + 1);
                    TxDOrParams params = getParams(nodes, seed, stdDev, csSize,
                        bitrate, effectiveFlows, 160, 50, (1 < csSize ? 100 : 3000), 
                        (gateway == 1), (bidirectional == 1));
                    
                    ret.add(params.setParamsBatch());
                  }
                }
              }
            }
          }
        }
      }
    }
  
    return ret;
  }

  /**
   * Benchmark TDiCOR vs. DSR at 6/12 Mbps, multi-flow, 
   * traffic model RadialGateway, 2 packet sizes, higher data rate
   * 
   * 208
   * 
   * TODO correlated shadowing
   */
  public static List getSimulationSuite207() {
    List ret = new ArrayList();
  
    // TODO random Gateway traffic
    for (int nodes = 100; nodes <= 100; nodes *= 2) {
      for (int seed = 1; seed <= 10; seed++) {
        for (int stdDev = 4; stdDev <= 12; stdDev += 4) {
          for (int csSize = 1; csSize <= 4; csSize++) {
            for (int bitrate = Constants.BANDWIDTH_6Mbps; bitrate <= Constants.BANDWIDTH_24Mbps; bitrate *= 2) {
              for (int flows = 2; flows <= 6; flows += 1) {
                for (int size = 0; size <= 2; size++) {
                  boolean gateway = true;
                  boolean bidirectional = true;
                  int rtsThreshold = (1 < csSize ? 100 : 3000);

                  // Parameters like G.711 with 20ms packetization and 160 byte
                  int packetSize = 160;
                  switch (size) {
                  case 0: packetSize = 160; break;
                  case 1: packetSize = 536; break;
                  case 2: packetSize = 1480; break;
                  default: throw new Error("ERROR"); 
                  }
                  int txRate = 50 * 160 / packetSize;
                  
                  TxDOrParams params = getParams(nodes, seed, stdDev, csSize,
                      bitrate, flows, packetSize, txRate, rtsThreshold, 
                      gateway, bidirectional);
                  
                  ret.add(params.setParamsBatch());
                }
              }
            }
          }
        }
      }
    }
  
    return ret;
  }

  /* (non-Javadoc)
   * @see brn.sim.AbstractDriver#getSimulationSuite()
   */
  public List getSimulationSuite(String version) {
    return getSimulationSuite207();
  }

  public static class SimulationResult {
    public String driver;
    public String version;
    public int simulationId;

    public int seed;
    public int nodes;
    public double shadowStdDev;
    public int csSize;
    public int useRts;
    public int csSelection;
    public int dataRate;
    public int controlRate;
    public String metric;
    public int flows;
    public int gateway;
    public int twoWay;
    public int packetSize;

    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 List /*FlowResult*/ flowStats;

    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 netDrops;

    public int rtgDuplicate;
    public int rtgDrops;
  }

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

    String entity = driver + "-" + 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
          TxDOrParams params = (TxDOrParams)
            load(loader, result.simulationId, "Global, Config");
          result.seed = params.seed;
          result.nodes = params.nodes;
          result.shadowStdDev = ((DistShadowingParams)
              ((FieldParams)params.field).pathloss).stdDeviation;
          result.csSize = 1;
          result.csSelection = -1;
          if (params.node.route instanceof RouteBuilder.TxDORParams) {
            TxDORParams txDORParams = ((RouteBuilder.TxDORParams)params.node.route);
            result.csSize = txDORParams.candidateSetSize;
            result.csSelection = txDORParams.candidateSelection;
          }
          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;
          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();
          result.flows = params.trafficParams.flows;
          result.packetSize = ((CbrUdpParams)params.trafficParams.flowParams).udpPayloadLen;
          if (params.trafficParams instanceof RadialGatewayParams) {
            result.gateway = 1;
            result.twoWay = ((RadialGatewayParams)params.trafficParams).twoWay ? 1 : 0;
          } else {
            result.gateway = 0;
            result.twoWay = ((RandomParams)params.trafficParams).twoWay ? 1 : 0;
          }
        }

        { // 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;
          }
        }
        
        { // Network
          { 
            DiagramData data = (DiagramData)
              load(loader, result.simulationId, "Global, Net, drop vs time", false);
            result.netDrops = data == null ? 0 : data.getY().length;
          }
        }
        
        { // Routing
          { 
            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, discard vs time", false);
            result.rtgDrops = data == null ? 0 : data.getY().length;
          }
        }
        
        { // Radio
          { // 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;
            }
          }
        }
        
        // flow parameters
        result.flowStats = new ArrayList();
        for (int flow = -1; flow < result.flows; flow++) 
        { 
          String flowName = (-1 == flow ? "All Flows, " : "Flow " + flow + ", ");
          flowName += "Application, ";
          FlowResult flowResult = new FlowResult();
          flowResult.flowId = flow;
          result.flowStats.add(flowResult);

          { 
            PropertiesData data = (PropertiesData)
              load(loader, result.simulationId, flowName + "Flow stats");
            FlowStats stats = (FlowStats) data.getProperties();
            flowResult.flowStats = stats;
            
            if (0 > flow) {
              result.avgThroughput = stats.getAvgThroughput();
              result.avgDelay = stats.getAvgDelay();
              result.sendPackets = stats.getSendPackets();
              result.recvPackets = stats.getRecvPackets();
              result.avgHopCount = stats.getAvgHopCount();
            }
          }
  
          {
            DescriptiveStatistics stats = DescriptiveStatistics.newInstance();
            DiagramData data = (DiagramData)
              load(loader, result.simulationId, flowName + "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, entity, false);
            flowResult.delayStats = delayStats;
          }
  
          {
            DescriptiveStatistics stats = DescriptiveStatistics.newInstance();
            DiagramData data = (DiagramData)
              load(loader, result.simulationId, flowName + "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, entity, false);
            flowResult.rdStats = rdStats;
          }
        }
        
        /* TODO
         * radio utilization
         */

      }
      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;
//            other.avgThroughputRatio = 1.;
//            res.avgDelayRatio = res.avgDelay / other.avgDelay;
//            other.avgDelayRatio = 1.;
//            break;
//          }
//        }

        for (int j = 0; j < results.size(); j++) {
          SimulationResult other = (SimulationResult)results.get(j);
          if (other.nodes == res.nodes
              &&other.seed == res.seed
              &&other.shadowStdDev == res.shadowStdDev
              &&other.useRts == 0
              &&other.csSize == 1
              &&other.dataRate == res.dataRate
              &&other.controlRate == res.controlRate
              &&other.gateway == res.gateway
              &&other.flows == res.flows) {
            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), entity, false);
//      saver.addToMapping(results.get(i));
//      saver.quickSave(results.get(i), entity, false);
    }
    saver.finalize();
  }

}
