package brn.sim.scenario.rca;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.misc.Util;

import org.apache.log4j.Logger;

import brn.distsim.ormapper.util.DbBinaryLoader;
import brn.sim.AbstractParams;
import brn.sim.builder.FieldBuilder;
import brn.sim.builder.PathLossBuilder;
import brn.sim.builder.MacBuilder.M802_11Params;
import brn.sim.builder.PathLossBuilder.DistShadowingParams;
import brn.sim.builder.PathLossBuilder.LogDistanceParams;
import brn.sim.builder.PathLossBuilder.ShadowingParams;
import brn.sim.builder.RateBuilder.ConstantParams;
import brn.sim.data.DiagramData;
import brn.sim.scenario.txdor.SingleFlow.SimulationResult;

public class SampleRateSim extends RcaSim {

  public static final Logger log = Logger.getLogger(SampleRateSim.class
      .getName());

  /**
   * @return Simulation with: - SampleRate - UDP app which fills the medium - 2
   *         stations moving away from each other - pathloss, no shadowing, no
   *         fading
   */
  public static List getSimulationSuite1() {
    List ret = new ArrayList();

    RcaParams params = new RcaParams.SampleParams();
    params.setAppUdp(3000); // really stuff the channel from the beg.
    params.setMovingAway();
    params.setProtocolsUdpAodv();
    params.endTime = 250 /* s */;
    params.nodes = 2;
    params.setParamsBatch();
    params.handlerNet = true;
    params.handlerForwardGraph = false;
    params.handlerRadio = true;

    LogDistanceParams lossExp = new LogDistanceParams();
    ShadowingParams pathLoss = new ShadowingParams();

    lossExp.exponent = 2.7;
    pathLoss.pathloss = lossExp;
    pathLoss.stdDeviation = 8;
    pathLoss.coherenceTime = Constants.SECOND;

    pathLoss.exponential = true;
    ((FieldBuilder.FieldParams) params.field).pathloss = pathLoss;
    ret.add(params);

    ret.add(params);
    return ret;
  }

  /**
   * @return Simulation with: - SampleRate - UDP app which fills the medium - 2
   *         stations moving away from each other - pathloss, shadowing and
   *         fading
   */
  public static List getSimulationSuite2() {
    List ret = new ArrayList();

    long cohTimes[] = new long[] { 0, 1 * Constants.SECOND,
        10 * Constants.SECOND, JistAPI.END };
    double exps[] = new double[] { 1.7, 2, 2.7, 3.5, 5 };

    for (int exp = 0; exp < exps.length; exp++) {
      for (double stdDev = 4; stdDev <= 12; stdDev += 4) {
        for (int coh = 0; coh < cohTimes.length; coh++) {
          for (int refDist = 1; refDist <= 100; refDist += 99) {
            for (boolean distShad = true, end = false; end != true; end = distShad = !distShad) {

              RcaParams params = new RcaParams.SampleParams();
              params.setAppUdp(3000); // really stuff the channel from the beg.
              params.setMovingAway();
              params.setProtocolsUdpAodv();
              params.endTime = 40 /* s */;
              params.nodes = 2;
              params.setParamsBatch();
              params.handlerNet = true;
              params.handlerForwardGraph = false;
              params.handlerRadio = true;

              LogDistanceParams lossExp = new LogDistanceParams();
              ShadowingParams pathLoss = new ShadowingParams();

              lossExp.exponent = exps[exp];
              lossExp.refDist = refDist;
              pathLoss.pathloss = lossExp;
              pathLoss.stdDeviation = stdDev;
              pathLoss.coherenceTime = cohTimes[coh];

              pathLoss.exponential = distShad;
              ((FieldBuilder.FieldParams) params.field).pathloss = pathLoss;
              ret.add(params);
            }

            RcaParams params = new RcaParams.SampleParams();
            params.setAppUdp(3000); // really stuff the channel from the beg.
            params.setMovingAway();
            params.setProtocolsUdpAodv();
            params.endTime = 40 /* s */;
            params.nodes = 2;
            params.setParamsBatch();
            params.handlerNet = true;
            params.handlerForwardGraph = false;
            params.handlerRadio = true;

            DistShadowingParams shadow = new PathLossBuilder.DistShadowingParams();
            shadow.exponent = exp;
            shadow.stdDeviation = stdDev;
            ((FieldBuilder.FieldParams) params.field).pathloss = shadow;
            ret.add(params);
          }
        }
      }
    }
    return ret;
  }

  /**
   * @return Simulation with: - SampleRate - UDP app which fills the medium - 2
   *         stations moving away from each other - pathloss, shadowing and
   *         fading
   */
  public static List getSimulationSuiteOutdoor() {
    List ret = new ArrayList();

    long cohTimes[] = new long[] { 0, 1 * Constants.SECOND,
        10 * Constants.SECOND, JistAPI.END };
    double exps[] = new double[] { 2, 2.7, 3.5, 5 };

    for (int exp = 0; exp < exps.length; exp++) {
      for (double stdDev = 4; stdDev <= 12; stdDev += 4) {
        for (int coh = 0; coh < cohTimes.length; coh++) {
          // for (int refDist = 1; refDist <= 100; refDist += 99) {
          // for (boolean distShad = true, end = false; end != true; end =
          // distShad = !distShad) {

          RcaParams params = new RcaParams.SampleParams();
          params.setAppUdp(3000); // really stuff the channel from the beg.
          params.setMovingAway();
          params.setProtocolsUdpAodv();
          params.endTime = 250 /* s */;
          params.nodes = 2;
          params.setParamsBatch();
          params.handlerNet = true;
          params.handlerForwardGraph = false;
          params.handlerRadio = true;

          LogDistanceParams lossExp = new LogDistanceParams();
          ShadowingParams pathLoss = new ShadowingParams();

          lossExp.exponent = exps[exp];
          // lossExp.refDist = refDist;
          pathLoss.pathloss = lossExp;
          pathLoss.stdDeviation = stdDev;
          pathLoss.coherenceTime = cohTimes[coh];

          pathLoss.exponential = true; // distShad;
          ((FieldBuilder.FieldParams) params.field).pathloss = pathLoss;
          ret.add(params);
          // }
          // }
        }
      }
    }
    return ret;
  }

  public static List getSimulationSuiteOutdoorRcas() {
    List ret = new ArrayList();

    long cohTimes[] = new long[] { /* 0, */1 * Constants.SECOND /*
                                                                 * , 10 *
                                                                 * Constants.SECOND,
                                                                 * JistAPI.END
                                                                 */};
    double exps[] = new double[] { 2 /* , /*2.7, 3.5, 4 */};

    for (int exp = 0; exp < exps.length; exp++) {
      for (double stdDev = 8; stdDev <= 8; stdDev += 4) {
        for (int coh = 0; coh < cohTimes.length; coh++) {
          // for (int refDist = 1; refDist <= 100; refDist += 99) {
          // for (boolean distShad = true, end = false; end != true; end =
          // distShad = !distShad) {

          RcaParams params = new RcaParams.AarfParams();
          params.setAppUdp(3000); // really stuff the channel from the beg.
          params.setMovingAway();
          params.setProtocolsUdpAodv();
          params.endTime = 200 /* s */;
          params.nodes = 2;
          params.setParamsBatch();
          params.handlerNet = true;
          params.handlerForwardGraph = false;
          params.handlerRadio = true;

          LogDistanceParams lossExp = new LogDistanceParams();
          ShadowingParams pathLoss = new ShadowingParams();

          lossExp.exponent = exps[exp];
          // lossExp.refDist = refDist;
          pathLoss.pathloss = lossExp;
          pathLoss.stdDeviation = stdDev;
          pathLoss.coherenceTime = cohTimes[coh];

          pathLoss.exponential = true; // distShad;
          ((FieldBuilder.FieldParams) params.field).pathloss = pathLoss;
          ret.add(params);
          // }
          // }
        }
      }
    }
    return ret;
  }

  public static List getSimulationSuiteConstant() {
    List ret = new ArrayList();

    long cohTimes[] = new long[] { /* 0, */1 * Constants.SECOND /*
                                                                 * , 10 *
                                                                 * Constants.SECOND,
                                                                 * JistAPI.END
                                                                 */};
    double exps[] = new double[] { 2.7 /* ,2.7, 3.5, 4 */};

    for (int exp = 0; exp < exps.length; exp++) {
      for (double stdDev = 8; stdDev <= 8; stdDev += 4) {
        for (int coh = 0; coh < cohTimes.length; coh++) {
          // for (int refDist = 1; refDist <= 100; refDist += 99) {
          // for (boolean distShad = true, end = false; end != true; end =
          // distShad = !distShad) {
          for (int rix = 0; rix < Constants.BITRATES_ALL.length; rix++) {
            {
              RcaParams params = new RcaParams.ConstParams(
                  Constants.BITRATES_ALL[rix], Constants.BITRATES_ALL[rix]);

              params.setAppUdp(3000); // really stuff the channel from the beg.
              params.setMovingAway();
              params.setProtocolsUdpAodv();
              params.endTime = 250 /* s */;
              params.nodes = 2;
              params.setParamsBatch();
              params.handlerNet = true;
              params.handlerForwardGraph = false;
              params.handlerRadio = true;

              LogDistanceParams lossExp = new LogDistanceParams();
              ShadowingParams pathLoss = new ShadowingParams();

              lossExp.exponent = exps[exp];
              // lossExp.refDist = refDist;
              pathLoss.pathloss = lossExp;
              pathLoss.stdDeviation = stdDev;
              pathLoss.coherenceTime = cohTimes[coh];

              pathLoss.exponential = true; // distShad;
              ((FieldBuilder.FieldParams) params.field).pathloss = pathLoss;
              ret.add(params);
            }
          }
          // }
          // }
        }
      }
    }
    return ret;
  }

  public static List getSimulationSuitePathlossFading() {
    List ret = new ArrayList();

    for (int seed = 1; seed <= 11; seed++) {
      RcaParams params = new RcaParams.SampleParams();
      params.setPathlossFadingSim1(seed);

      ret.add(params);
    }

    return ret;
  }

  public static List getSimulationSuiteShadow1() {
    List ret = new ArrayList();

    long cohTimes[] = new long[] { 1 * Constants.SECOND };
    double exps[] = new double[] { 2.7 /* ,2.7, 3.5, 4 */};

    for (int exp = 0; exp < exps.length; exp++) {
      for (double stdDev = 8; stdDev <= 8; stdDev += 4) {
        for (int coh = 0; coh < cohTimes.length; coh++) {
          for (int seed = 7; seed <= 11; seed++) {
            RcaParams.SampleParams params = new RcaParams.SampleParams();
            params.setShadowSim2(exps[exp], stdDev, cohTimes[coh], seed);
            ret.add(params);
          }
        }
      }
    }
    return ret;
  }

  public static List getSimulationSuitePathloss() {
    List ret = new ArrayList();
    for (int seed = 1; seed <= 7; seed++) {
      RcaParams params = new RcaParams.SampleParams();
      // params.setPathlossSim(2.7, seed);
      params.setPathlossSim2(2.7, seed);
      ret.add(params);
    }
    return ret;
  }

  public static List getSimulationSuitePathlossShadowing() {
    List ret = new ArrayList();
    for (int seed = 1; seed <= 11; seed++) {
      for (int dist = 80; dist <= 180; dist += 50) {
        RcaParams params = new RcaParams.SampleParams();
        params.setPathlossShadowingSim(seed, dist);
        ret.add(params);
      }
    }
    return ret;
  }

  public static List getSimulationSuitePathlossShadowingMoving() {
    List ret = new ArrayList();
    for (int seed = 1; seed <= 5; seed++) {
      for (int dist = 80; dist <= 80; dist += 50) {
        // TODO: Check this???
        RcaParams params = new RcaParams.AmrrParams();
        params.setPathlossShadowingMovingSim(seed, dist);
        ret.add(params);
      }
    }
    return ret;
  }

  public static List getSimulationSuiteCustomRadio() {
    List ret = new ArrayList();
    RcaParams params = new RcaParams.SampleParams();
    params.setProtocolsUdpAodv();
    params.setCustomRadioParams();
    params.endTime = 12;
    ret.add(params);
    return ret;
  }

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

  /*
   * (non-Javadoc)
   * 
   * @see brn.sim.AbstractDriver#runDistSim()
   */
  protected void runDistSim() throws Exception {
    // load properties from stdin
    Properties configuration = new Properties();
    configuration.load(System.in);

    String configFile = configuration.getProperty("driver.config");
    AbstractParams options = (AbstractParams) Util.readObject(configFile);
    if (log.isInfoEnabled())
      log.info(brn.swans.Strings.toString(options, "  "));
    // RcaParams options = RcaParams.defaultParams().setUdpAodvParams();

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

  /**
   * Runs a local test defined in derived classes.
   */
  protected void runLocal() {
    try {
      List ps = getSimulationSuiteCustomRadio();
      if (ps.size() <= 0) {
        System.out.println("No sim defined");
        return;
      }
      AbstractParams p = (AbstractParams) ps.get(0);
      System.out.println("Sim params: " + p);
      run(p);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * @param args
   * @throws Throwable
   */
  public static void main(String[] args) throws Throwable {
    SampleRateSim sim = new SampleRateSim();
    sim.run(args);
  }

  protected void evalSimulationResults(String dbUrl, String dbUser,
      String dbPasswd, String driver, String version, String dbResUrl,
      String dbResUser, String dbResPasswd) throws Throwable {

    super.evalSimulationResults(dbUrl, dbUser, dbPasswd, driver, version,
        dbResUrl, dbResUser, dbResPasswd);
    if (true)
      return;
    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);

    // Hashtable /* sim id --> throughput */h = new Hashtable();
    ArrayList al = new ArrayList();
    for (int id = 0; id < lstIds.size(); id++) {
      SimulationResult result = new SimulationResult();
      result.simulationId = ((Integer) lstIds.get(id)).intValue();
      result.driver = driver;
      result.version = version;

      try {
        // read configuration
        RcaParams params = (RcaParams) load(loader, result.simulationId,
            "Global, Config");
        double[][] throughputData = new double[3 + (int) (params.endTime / params.sampleLen)][2];
        throughputData[0][1] = result.simulationId;
        // ConstantRate simulations
        if (params instanceof RcaParams.ConstParams) {
          int dataRate = ((ConstantParams) ((M802_11Params) params.node.mac).rateSelection).dataBitrate;
          int ctrlRate = ((ConstantParams) ((M802_11Params) params.node.mac).rateSelection).controlBitrate;
          throughputData[1][1] = dataRate;
          throughputData[2][1] = ctrlRate;
        }
        { // net throughput
          DiagramData data = (DiagramData) load(loader, result.simulationId,
              "Node 2, Net, throughput (avg)", false);
          if (data != null) {
            double[] dataX = data.getX();
            double[] dataY = data.getY();
            int dataIdx = 0;
            double time = 0.;
            // fill the array with time/throuput pairs, filling in 0 for
            // missing values
            for (int idx = 3; idx < throughputData.length; idx++) {
              throughputData[idx][0] = time;
              if (dataIdx < dataX.length && dataX[dataIdx] == time) {
                throughputData[idx][1] = dataY[dataIdx];
                dataIdx++;
              } else {
                throughputData[idx][1] = 0;
              }
              time += params.sampleLen;
            }
            // h.put(lstIds.get(id), throughputData);
            al.add(throughputData);
          } else {
            System.out.println("No throughput data for sim "
                + result.simulationId);
          }
        }
      } catch (Error e) {
        System.err.println(e.getMessage());
        continue;
      } catch (Throwable e) {
        e.printStackTrace();
        continue;
      }
    }
    File f = new File("d:\\Eclipse\\SimulationResults\\" + driver + "_"
        + version + ".csv");
    if (!f.exists())
      f.createNewFile();
    FileWriter fw = new FileWriter(f);
    System.out.println(al.size() + " elements in result list");
    Collections.sort(al, new C());
    if (al.size() > 0) {
      int cols = ((double[][]) al.get(0)).length;
      StringBuffer line1 = new StringBuffer("SimulationId;");
      StringBuffer line2 = new StringBuffer("Data rate (Mbit/s);");
      StringBuffer line3 = new StringBuffer("Control rate (Mbit/s);");
      for (int a = 0; a < al.size(); a++) {
        double[][] d = (double[][]) al.get(a);
        line1.append((int) d[0][1]).append(';'); // sim id
        line2.append(d[1][1] / 1000000).append(';'); // data rate
        line3.append(d[2][1] / 1000000).append(';'); // ctrl rate
      }
      fw.write(line1.append("\r\n").toString());
      fw.write(line2.append("\r\n").toString());
      fw.write(line3.append("\r\n").toString());
      fw.write("distance;throughput (Mbit/s)\r\n");
      for (int i = 3; i < cols; i++) {
        StringBuffer sl = new StringBuffer();
        double[][] d = (double[][]) al.get(0);
        // movement starts at
        sl.append(d[i][0]) // time (= distance)
            .append(';').append(d[i][1]); // throughput
        for (int a = 1; a < al.size(); a++) {
          sl.append(';').append(((double[][]) al.get(a))[i][1]); // throughput
        }
        fw.write(sl.append("\r\n").toString());
        fw.flush();
      }
    }
    fw.close();
    System.out.println("Wrote and closed file " + f.getAbsolutePath());
  }

  public static class C implements Comparator {

    public int compare(Object arg0, Object arg1) {
      double[][] a1 = (double[][]) arg0;
      double[][] a2 = (double[][]) arg1;
      return (int) (a2[1][1] - a1[1][1]);
    }

  }
}
