package brn.sim.scenario.rca;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import brn.distsim.ormapper.util.DBSaver;
import brn.distsim.ormapper.util.DbBinaryLoader;
import brn.sim.builder.FieldBuilder;
import brn.sim.builder.RadioBuilder;
import brn.sim.builder.MacBuilder.MDcfParams;
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.data.MacStats;
import brn.sim.data.PropertiesData;


public class Evaluator extends RcaSim {

  private static final long serialVersionUID = 1L;

  private static final String dbPre = "jdbc:mysql://";

  private static final String dbSuf = ":3306/simulation";

  private static final String dbSufRes = ":3306/result";

  /**
   * @param args
   */
  public static void main(String[] args) {
    System.out.println(Arrays.toString(args));
    if (args.length > 3 && args[3].equalsIgnoreCase("sample")) {
      SampleRateSim sim = new SampleRateSim();
      try {
        sim.evalSimulationResults(dbPre + args[0] + dbSuf, "distsim",
            "distsim", "brn.sim.scenario.rca." + args[1], args[2], "",
            "distsim", "distsim");
      } catch (Throwable e) {
        e.printStackTrace();
      }
      return;
    }

    Evaluator sim = new Evaluator();
    int versionFrom = 1;
    int versionTo;
    if (args.length > 2 && args[2] != null && args[2].length() > 0)
      versionFrom = Integer.parseInt(args[2]);
    if (args.length > 3 && args[3] != null && args[3].length() > 0)
      versionTo = Integer.parseInt(args[3]);
    else
      versionTo = versionFrom;
    for (int v = versionFrom; v <= versionTo; v++) {
      try {
        sim.evalSimulationResults(args[0], "distsim", "distsim",
            "brn.sim.scenario.rca." + args[1], String.valueOf(v), dbPre
                + args[0] + dbSufRes, "distsim", "distsim");
      } catch (Throwable e) {
        e.printStackTrace();
      }
    }
  }

  protected void evalSimulationResults(String dbUrl, String dbUser,
      String dbPasswd, String driver, String version, String dbResUrl,
      String dbResUser, String dbResPasswd) throws Throwable {
  	
  	System.out.println("#################################################\r\ndbUrl:" 
  			+ dbUrl + "\r\ndbUser:" + dbUser + "\r\ndbDriver:" + driver + "\r\ndbVersion:"
  			+  version + "\r\ndbResUrl:" +  dbResUrl + "\r\ndbUser:" +  dbResUser);
  	
  	//if(true) return;
  	
    String longDbUrl = dbUrl;
    if (dbUrl.indexOf(dbPre) < 0)
      longDbUrl = dbPre + dbUrl + dbSuf;
    DbBinaryLoader loader = new DbBinaryLoader(longDbUrl, dbUser, dbPasswd);
    System.out.println("### DB Loader instantiated ###");
    List lstIds = loader.loadSimulationIds(driver, version);
    
    DBSaver saver = new DBSaver(dbResUrl, dbResUser, dbResPasswd, Integer.parseInt(version));
    System.out.println("### DB Saver instantiated ###");    
    
    if (null == lstIds || lstIds.size() <= 0) {
      System.out.println("no simulations found for driver " + driver
          + " and version " + version);
      return;
    }

    boolean aggregatePerDistance = true;
    
    System.out.println("Found " + lstIds.size() + " simulations.");
    ArrayList results = new ArrayList(lstIds.size());
    for (int iid = 0; iid < lstIds.size(); iid++) {
      SimResult res = new SimResult();
      int simId = ((Integer) lstIds.get(iid)).intValue();
      res.host = dbUrl;
      res.simId = simId;
      res.driver = driver;
      res.version = version;

      try {
        // read configuration
        RcaParams params = (RcaParams) super.load(loader, simId,
            "Global, Config");
        res.startTime = 0;
        res.endTime = params.endTime;
        res.timeStep = .5;
        String po = ((RadioBuilder.NoiseParams) params.node.radio).placementOpts;
        String mo = ((RadioBuilder.NoiseParams) params.node.radio).startMobility;
        res.initDistance = Integer.parseInt(po.substring(po.indexOf(':') + 1,
            po.lastIndexOf('x')));
        // dirty hack
        if (!aggregatePerDistance)
        	res.initDistance -= Integer.parseInt(mo.substring(mo.indexOf(':') + 1));
        try {
          FieldBuilder.FieldParams fp = (FieldBuilder.FieldParams) params.field;
          if (fp.pathloss instanceof ShadowingParams) {
            ShadowingParams pl = (ShadowingParams) fp.pathloss;
            res.cohTime = pl.coherenceTime;
            res.exp = ((LogDistanceParams) pl.pathloss).exponent;
            res.exponential = pl.exponential;
            res.stdDev = pl.stdDeviation;
          }
        } catch (Error e) {
          System.err.println(e.getMessage());
        }
        if (params instanceof RcaParams.ConstParams) {
          res.controlBitrate = ((ConstantParams) ((MDcfParams) params.node
              .getMac()).rateSelection).controlBitrate;
          res.dataBitrate = ((ConstantParams) ((MDcfParams) params.node
              .getMac()).rateSelection).dataBitrate;
        }
        try {
          PropertiesData data = (PropertiesData) load(loader, simId,
              "Global, Mac, Stats", true);
          MacStats ms = (MacStats) data.getProperties();
          res.ackRcvd = ms.getAckRecv();
          res.ackSent = ms.getAckSend();
          res.broadcastRcvd = ms.getBroadcastRecv();
          res.broadcastSent = ms.getBroadcastSend();
          res.unicastRcvd = ms.getUnicastRecv();
          res.unicastSent = ms.getUnicastSend();
        } catch (Error e) {
          System.err.println(e.getMessage());
          System.out.println("No MacStats for sim " + simId
              + ". Skip this result.");
          continue;
        }
        try { // net throughput
          double[][] throughputData = new double[(int) (params.endTime / params.sampleLen)][2];
          DiagramData dataThru = (DiagramData) load(loader, simId,
              "Node 2, Net, throughput (avg)", false);
          DiagramData dataDist = (DiagramData) load(loader, simId,
              "Node 2, Field, distance to 1", false);
          if (dataThru != null) {
            double[] dataThruX = dataThru.getX();
            double[] dataThruY = dataThru.getY();
            double[] dataDistX;
            double[] dataDistY;
            if (dataDist != null) {
            	dataDistX = dataDist.getX();
            	dataDistY = dataDist.getY();
            } else {
            	dataDistX = new double[throughputData.length];
            	dataDistY = new double[throughputData.length];
            	Arrays.fill(dataDistY, res.initDistance);
            }
            int dataThruIdx = 0;
            int dataDistIdx = 0;
            double time = 0.;
            // fill the array with time/throughput pairs, filling in 0 for
            // missing values
            res.throughput = new double[throughputData.length];
            res.distance = new double[res.throughput.length];
            for (int idx = 0; idx < throughputData.length; idx++) {
              throughputData[idx][0] = time;
              if (dataThruIdx < dataThruX.length
                  && dataThruX[dataThruIdx] == time) {
                throughputData[idx][1] = dataThruY[dataThruIdx];
                res.throughput[idx] = dataThruY[dataThruIdx];
                dataThruIdx++;
              }
              // else {
              // throughputData[idx][1] = 0;
              // }
              if (dataDistIdx < dataDistX.length
                  && dataDistX[dataDistIdx] == time) {
                throughputData[idx][1] = dataDistY[dataDistIdx];
                res.distance[idx] = dataDistY[dataDistIdx];
                dataDistIdx++;
              } else {
                res.distance[idx] = res.distance[idx - 1];
              }
              time += params.sampleLen;
            }
          } else {
            System.out.println("No throughput data for sim " + simId
                + ". Skip this result.");
            continue;
          }
        } catch (Error e) {
          System.err.println(e.getMessage());
        }
        // System.out.println(res.toString());
        results.add(res);

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

    Hashtable /* dataRate --> SimResult */finals = new Hashtable();
    System.out.println("Simulations: " + results.size());
    for (int i = 0; i < results.size(); i++) {
      SimResult sr = (SimResult) results.get(i);
      SimResult total;
      if (aggregatePerDistance)
      	total = (SimResult) finals.get(Integer.valueOf(sr.initDistance));
      else total = (SimResult) finals.get(Integer.valueOf(sr.dataBitrate));
      	
      if (total == null) { // || i == 0) {
        total = (SimResult) sr.clone();
        total.throughput = new double[sr.throughput.length];
        total.simId = -1;
        total.simIds = "";
        if (aggregatePerDistance)
        	finals.put(Integer.valueOf(total.initDistance), total);
        else finals.put(Integer.valueOf(total.dataBitrate), total);
      } else {
        total.ackRcvd += sr.ackRcvd;
        total.ackSent += sr.ackSent;
        total.broadcastRcvd += sr.broadcastRcvd;
        total.broadcastSent += sr.broadcastSent;
        total.unicastRcvd += sr.unicastRcvd;
        total.unicastSent += sr.unicastSent;
      }
      System.out.println(sr.simId + ": sent:" + sr.unicastSent + " rcvd:" + sr.unicastRcvd);
      System.out.println("total: sent:" + total.unicastSent + " rcvd:" + total.unicastRcvd);
      System.out.println();
      
      total.resCount++;
      total.simIds += sr.simId + ", ";
      for (int d = 0; d < total.throughput.length; d++) {
        total.throughput[d] += sr.throughput[d];
      }
    }

    Enumeration e = finals.keys();
    while (e.hasMoreElements()) {
      SimResult total = (SimResult) finals.get(e.nextElement());
      // divide by number of stats
      for (int d = 0; d < total.throughput.length; d++) {
        total.throughput[d] /= total.resCount;
      }
      
      System.out.println("total(" + total.resCount + "): sent:" + total.unicastSent + " rcvd:" + total.unicastRcvd);
      
      // TODO correct rounding
      total.ackRcvd /= total.resCount;
      total.ackSent /= total.resCount;
      total.broadcastRcvd /= total.resCount;
      total.broadcastSent /= total.resCount;
      total.unicastRcvd /= total.resCount;
      total.unicastSent /= total.resCount;
      
      System.out.println("total(" + total.resCount + "): sent:" + total.unicastSent + " rcvd:" + total.unicastRcvd);
      
//      try {
//        total.toFile();
//      } catch (Throwable t) {
//        t.printStackTrace();
//      }
      try {
        saver.save(total, driver + "-" + version, false);
      } catch (Throwable t) {
        t.printStackTrace();
      }
    }
  }

  private static class SimResult implements Cloneable {
    int resCount;

    String host;

    int simId;

    String simIds;

    String driver;

    String version;

    double exp;

    double stdDev;

    long cohTime;

    boolean exponential;

    int dataBitrate;

    int controlBitrate;

    long startTime;

    long endTime;

    double timeStep;

    int initDistance;

    double[] throughput;

    double[] distance;

    int unicastSent;

    int unicastRcvd;

    int broadcastSent;

    int broadcastRcvd;

    int ackSent;

    int ackRcvd;

    public Object clone() throws CloneNotSupportedException {
      return super.clone();
    }

    public void overview() throws IOException {
      // Host; SimulationId; Pathloss exp.; Std. deviation; exp. distr.;
      // coherence time (ns); unicast sent; unicast recv; broadcast sent;
      // broadcast recv; rca;
      StringBuffer head = new StringBuffer(
          "Host;SimulationId;Pathloss exp.;Std. deviation;exp. distr.;"
              + "coherence time (ns);unicast sent;unicast recv;broadcast sent;"
              + "broadcast recv;rca;driver;version");
      File f = new File("d:\\Eclipse\\SimulationResults\\overview_" + host
          + "_" + driver.substring(driver.lastIndexOf('.') + 1) + "_" + version
          + ".csv");
      if (!f.exists())
        f.createNewFile();
      FileWriter fw = new FileWriter(f);
      fw.write(head.append("\r\n").toString());
      StringBuffer sb = new StringBuffer();
      sb.append(host).append(';').append(simId).append(';').append(exp).append(
          ';').append(stdDev).append(';').append(exponential).append(';')
          .append(cohTime).append(';');

      sb.append(unicastSent).append(';').append(unicastRcvd).append(';')
          .append(broadcastSent).append(';').append(broadcastRcvd).append(';')
          .append(ackSent).append(';').append(ackRcvd).append(';');
      fw.write(sb.append("\r\n").toString());
      fw.flush();
      fw.close();
      System.out.println("Wrote and closed file " + f.getAbsolutePath());
    }

    public void toFile() throws IOException {
      File f = new File("d:\\Eclipse\\SimulationResults\\"
          + host.substring(host.lastIndexOf('.') + 1, host.length()) + "_"
          + driver.substring(driver.lastIndexOf('.') + 1) + "_" + version + "_"
          + (dataBitrate / 1000000) + "_" + (initDistance) + "m.csv");
      if (!f.exists())
        f.createNewFile();
      FileWriter fw = new FileWriter(f);
      fw.append(this.toString());
      fw.flush();
      fw.close();
      f.setReadOnly();
      System.out.println("Wrote and closed file " + f.getAbsolutePath());
    }

    public String toString() {
      StringBuffer sb = new StringBuffer();
      sb.append("Number of sims").append(';').append(resCount).append("\r\n")
          .append("Host").append(';').append(host).append("\r\n").append(
              "Driver").append(';').append(driver).append("\r\n").append(
              "Version").append(';').append(version).append("\r\n").append(
              "SimIds").append(';')
          .append(simIds == null ? simId + "" : simIds).append("\r\n").append(
              "dataRate").append(';').append(dataBitrate).append("\r\n")
          .append("controlRate").append(';').append(controlBitrate).append(
              "\r\n").append("initial distance").append(';').append(
              initDistance).append("\r\n").append("unicast sent").append(';')
          .append(unicastSent).append("\r\n").append("unicast rcvd")
          .append(';').append(unicastRcvd).append("\r\n").append("ack sent")
          .append(';').append(ackSent).append("\r\n").append("ack rcvd")
          .append(';').append(ackRcvd).append("\r\n").append(
              "broadcast sent").append(';').append(broadcastSent).append("\r\n")
          .append("broadcast rcvd").append(';').append(broadcastRcvd).append(
              "\r\n").append("time (s)").append(';').append("distance (m)")
          .append(';').append("throughput (Mbit/s)").append("\r\n");
      for (int i = 0; i < throughput.length; i++) {
        sb.append(startTime + i * timeStep) // time
            .append(';').append(distance[i]) // distance
            .append(';').append(throughput[i]).append("\r\n"); // throughput
      }
      return sb.toString();
    }
  }
}
