package brn.analysis.dump;

import jargs.gnu.CmdLineParser;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.net.NetAddress;
import jist.swans.phy.Phy802_11;
import brn.analysis.Statistics;
import brn.sim.DataManager;
import brn.sim.DataManager.DataContribution;
import brn.sim.DataManager.DataContributor;
import brn.sim.data.AbstractDiagramData;
import brn.sim.data.AveragedTimeLine;
import brn.sim.data.DiagramData;
import brn.sim.data.DiagramDataHist;
import brn.sim.data.Line;
import brn.sim.data.LinkTableData;
import brn.sim.data.TimeBar;
import brn.sim.data.TimeLine;
import brn.sim.data.XplotData;
import brn.sim.data.XplotSerializer;
import brn.sim.data.TimeBar.Entry;

public class RoofnetDumpSigcomm {

  int[] BITRATES = { Constants.BANDWIDTH_1Mbps, Constants.BANDWIDTH_2Mbps,
      Constants.BANDWIDTH_5_5Mbps, Constants.BANDWIDTH_11Mbps };

  public static int getIdx(int bitRate) {
    int idx = 0;
    switch (bitRate) {
    case Constants.BANDWIDTH_1Mbps:
      idx = 0;
      break;
    case Constants.BANDWIDTH_2Mbps:
      idx = 1;
      break;
    case Constants.BANDWIDTH_5_5Mbps:
      idx = 2;
      break;
    case Constants.BANDWIDTH_11Mbps:
      idx = 3;
      break;
    default:
      throw new RuntimeException();
    }
    return idx;
  }

  private String url = "jdbc:mysql://localhost/sigcomm";
  private String user = "distsim";
  private String passwd = "distsim";

  private String src = "23741";
  private String dst = "23633";
  private double sampleLen = .5;

  //private DbBinarySaver saver;
  private DataManager dataManager;

  private RoofnetContributor contributor;

  private void onStarted() throws Exception {
    dataManager = new DataManager.Database(url, url, user, passwd);
    dataManager.open();

    contributor = new RoofnetContributor();
    dataManager.add(contributor);

    contributor.setLevel(DataManager.LEVEL_ALL);
  }

  private void onFinished() throws Exception {
    contributor.onFinished();
    processPass2();
    dataManager.close();
  }

  private void onPacketSent(String src, double time, String seq, int bitRate) {

    // HUGO
    if (!src.equals(this.src))
      return;

    int nodeId = Integer.parseInt(src);
    long start = (long)(time * (double)Constants.SECOND);
    long end = start + Phy802_11.transmitTime(1500, bitRate, Constants.MAC_802_11b_LONG);

    getNodeData(src, src, bitRate).getMacSend().addNextPoint(time, 1.); // TODO

    if (!src.equals(this.src))
      return;

    contributor.linkTimeBar.add(nodeId, start, end, TimeBar.COLOR_GREEN, seq, String.valueOf(bitRate));
  }


  private void onPacketRecv(String src, String dst, double time, String seq,
      int bitRate, double signal, double noise) {

    // HUGO
    if (!src.equals(this.src) || !dst.equals(this.dst))
      return;

    int nodeId = Integer.parseInt(dst);
    long start = (long)(time * (double)Constants.SECOND);
    long end = start + Phy802_11.transmitTime(1500, bitRate, Constants.MAC_802_11b_LONG);
    long nSeq = Long.parseLong(seq);

    NodeData data = getNodeData(dst, src, bitRate);
    data.processMacLossLengthHist(bitRate, nSeq, time);
    data.getMacReceive().addNextPoint(time, 1.); // TODO
    data.getSignalPowerDB().addNextTimePoint(start, signal);
    data.getNoisePowerDB().addNextTimePoint(start, noise);
    data.getSnrDB().addNextTimePoint(start, signal - noise);
    data.getSignalPowerDBNoBias().addNextTimePoint(start, signal);
    data.getNoisePowerDBNoBias().addNextTimePoint(start, noise);
    data.getSnrDBNoBias().addNextTimePoint(start, signal - noise);

    if (!src.equals(this.src))
      return;

    long offset = getOffset(dst, seq, start);
    start += offset;
    end += offset;

    contributor.linkTimeBar.add(nodeId, start, end, TimeBar.COLOR_YELLOW, seq +"(" + src + ")",
        String.valueOf(bitRate) + ", SNR " + signal + "/" + noise);
  }

  private void processSend(String sendFile) throws Exception {
    BufferedReader reader = new BufferedReader(new FileReader(sendFile));

    for (String line = reader.readLine(); line != null; line = reader.readLine()) {
      StringTokenizer tok = new StringTokenizer(line, ",");
      String exp_id = tok.nextToken();
      tok.nextToken();
      tok.nextToken();
      String src = tok.nextToken();
      String seq = tok.nextToken();
      double time = Double.parseDouble(tok.nextToken());

      int bitRate = Constants.BANDWIDTH_11Mbps;

      if ("606062400".equals(exp_id))
        bitRate = Constants.BANDWIDTH_1Mbps;
      else if ("606062401".equals(exp_id))
        bitRate = Constants.BANDWIDTH_2Mbps;
      else if ("606062402".equals(exp_id))
        bitRate = Constants.BANDWIDTH_5_5Mbps;

      onPacketSent(src, time, seq, bitRate);
    }
  }

  private void processRecv(String recvFile) throws Exception {
    BufferedReader reader = new BufferedReader(new FileReader(recvFile));

    for (String line = reader.readLine(); line != null; line = reader.readLine()) {
      StringTokenizer tok = new StringTokenizer(line, ",");
      String exp_id = tok.nextToken();
      tok.nextToken();
      tok.nextToken();
      String src = tok.nextToken();
      String dst  = tok.nextToken();
      String seq = tok.nextToken();
      double time = Double.parseDouble(tok.nextToken());
      double signal = Double.parseDouble(tok.nextToken());
      double noise = Double.parseDouble(tok.nextToken());

      int bitRate = Constants.BANDWIDTH_11Mbps;

      if ("606062400".equals(exp_id))
        bitRate = Constants.BANDWIDTH_1Mbps;
      else if ("606062401".equals(exp_id))
        bitRate = Constants.BANDWIDTH_2Mbps;
      else if ("606062402".equals(exp_id))
        bitRate = Constants.BANDWIDTH_5_5Mbps;

      onPacketRecv(src, dst, time, seq, bitRate, signal, noise);
    }
  }

  private void processLoc(String locFile) throws Exception {
    BufferedReader reader = new BufferedReader(new FileReader(locFile));

    for (String line = reader.readLine(); line != null; line = reader.readLine()) {
      StringTokenizer tok = new StringTokenizer(line, ",");
      String src = tok.nextToken();
      String x = tok.nextToken();
      String y = tok.nextToken();

      contributor.mapX.put(src, Float.parseFloat(x));
      contributor.mapY.put(src, Float.parseFloat(y));
    }
  }

  private void processPass2() throws Exception {
    int[] lags = { 1, 2, 4, 8, 10, 100, 999 };

    CorrData[] snrLag = new CorrData[lags.length * BITRATES.length];
    for (int i = 0; i < lags.length; i++)
      for (int j = 0; j < BITRATES.length; j++)
        snrLag[i*BITRATES.length + j] = new CorrData(lags[i], BITRATES[j]);

    Iterator<NodeData> iter = contributor.nodeData.values().iterator();
    while (null != iter && iter.hasNext()) {
      NodeData data = iter.next();
      int rateIdx = getIdx(data.bitRate);

      for (int lagIdx = 0; lagIdx < lags.length; lagIdx++) {
        if (null != data.snrPowerAutocorr)
          snrLag[lagIdx*BITRATES.length + rateIdx].getSnrAutoCorrLag().
            addNextValue(data.snrPowerAutocorr.getY()[lags[lagIdx]]);
        if (null != data.snrPowerNoBiasAutocorr)
          snrLag[lagIdx*BITRATES.length + rateIdx].getSnrNoBiasAutoCorrLag().
            addNextValue(data.snrPowerNoBiasAutocorr.getY()[lags[lagIdx]]);

        if (null != data.signalPowerAutocorr)
          snrLag[lagIdx*BITRATES.length + rateIdx].getSignalAutoCorrLag().
            addNextValue(data.signalPowerAutocorr.getY()[lags[lagIdx]]);
        if (null != data.signalPowerNoBiasAutocorr)
          snrLag[lagIdx*BITRATES.length + rateIdx].getSignalNoBiasAutoCorrLag().
            addNextValue(data.signalPowerNoBiasAutocorr.getY()[lags[lagIdx]]);

        if (null != data.noisePowerAutocorr)
          snrLag[lagIdx*BITRATES.length + rateIdx].getNoiseAutoCorrLag().
            addNextValue(data.noisePowerAutocorr.getY()[lags[lagIdx]]);
        if (null != data.noisePowerNoBiasAutocorr)
          snrLag[lagIdx*BITRATES.length + rateIdx].getNoiseNoBiasAutoCorrLag().
            addNextValue(data.noisePowerNoBiasAutocorr.getY()[lags[lagIdx]]);

        if (null != data.receiveAutocorr)
          snrLag[lagIdx*BITRATES.length + rateIdx].getReceiveAutoCorrLag().
            addNextValue(data.receiveAutocorr.getY()[lags[lagIdx]]);
      }
    }

    for (int j = 0; j < BITRATES.length; j++) {
      XplotSerializer[] seri = new XplotSerializer[7];

      for (int i = 0; i < lags.length; i++) {
        CorrData corrData = snrLag[i*BITRATES.length + j];
        corrData.finish();
        String color = XplotSerializer.white;
        switch (i) {
        case 0: color = XplotSerializer.white; break; // 1
        case 1: color = XplotSerializer.green; break; // 2
        case 2: color = XplotSerializer.red; break; // 4
        case 3: color = XplotSerializer.blue; break; // 8
        case 4: color = XplotSerializer.yellow; break; // 10
        case 5: color = XplotSerializer.purple; break; // 1
        case 6: color = XplotSerializer.orange; break; // 1
        default:
          System.err.println("lag idx not found: " + i); break;
        }
        seri[0] = addToSeri(seri[0], corrData.getSnrAutoCorrCdf(),color);
        seri[1] = addToSeri(seri[1], corrData.getSnrNoBiasAutoCorrCdf(),color);
        seri[2] = addToSeri(seri[2], corrData.getNoiseAutoCorrCdf(),color);
        seri[3] = addToSeri(seri[3], corrData.getNoiseNoBiasAutoCorrCdf(),color);
        seri[4] = addToSeri(seri[4], corrData.getSignalAutoCorrCdf(),color);
        seri[5] = addToSeri(seri[5], corrData.getSignalNoBiasAutoCorrCdf(),color);
        seri[6] = addToSeri(seri[6], corrData.getReceiveAutoCorrCdf(),color);
      }

      if (null != seri[0])
        addData(new XplotData(new String[] {"Global", "SNR AutoCorr CDF Rate " +
            BITRATES[j]}, seri[0].getContent()), DataManager.LEVEL_BASIC);
      if (null != seri[1])
        addData(new XplotData(new String[] {"Global", "SNR NoBias AutoCorr CDF Rate " +
            BITRATES[j]}, seri[1].getContent()), DataManager.LEVEL_BASIC);
      if (null != seri[2])
        addData(new XplotData(new String[] {"Global", "Noise AutoCorr CDF Rate " +
            BITRATES[j]}, seri[2].getContent()), DataManager.LEVEL_BASIC);
      if (null != seri[3])
        addData(new XplotData(new String[] {"Global", "Noise NoBias AutoCorr CDF Rate " +
            BITRATES[j]}, seri[3].getContent()), DataManager.LEVEL_BASIC);
      if (null != seri[4])
        addData(new XplotData(new String[] {"Global", "Signal AutoCorr CDF Rate " +
            BITRATES[j]}, seri[4].getContent()), DataManager.LEVEL_BASIC);
      if (null != seri[5])
        addData(new XplotData(new String[] {"Global", "Signal NoBias AutoCorr CDF Rate " +
            BITRATES[j]}, seri[5].getContent()), DataManager.LEVEL_BASIC);
      if (null != seri[6])
        addData(new XplotData(new String[] {"Global", "Receive AutoCorr CDF Rate " +
            BITRATES[j]}, seri[6].getContent()), DataManager.LEVEL_BASIC);
    }

  }

  private XplotSerializer addToSeri(XplotSerializer seri, AbstractDiagramData data, String color) {
    if (null == data)
      return seri;
    if (null == seri)
      seri = new XplotSerializer(data,color);
    else
      seri.addLine(data.getLine(),color);
    return seri;
  }

  protected void run(String dbUrl, String dbUser, String dbPasswd,
      String driver, String version,
      String dbResUrl, String dbResUser, String dbResPasswd,
      String sendFile, String recvFile, String locFile, String pass) throws Exception {
    this.url = dbResUrl;
    this.user = dbResUser;
    this.passwd = dbResPasswd;

    onStarted();
    if ("one".equals(pass)) {
      processLoc(locFile);
      buildLinkGraph();
      processSend(sendFile);
      processRecv(recvFile);
//    } else if ("two".equals(pass)){
//      processPass2(dbUrl, dbUser, dbPasswd, Integer.parseInt(version));
    } else
      throw new Error("unknown pass: " + pass);
    onFinished();
  }

  @SuppressWarnings("unchecked")
  private void buildLinkGraph() {
    boolean signalStrength = false;

    for (int bitRateIdx=0; bitRateIdx < 4; bitRateIdx++){
      List /*LinkTableData.Node*/ lstNodes = new ArrayList();
      List /*LinkTableData.Link*/ lstLinks = new ArrayList();

      Iterator iter = contributor.nodeData.values().iterator();
      while (null !=iter && iter.hasNext()) {
        NodeData data = (NodeData) iter.next();
        if (data.bitRate != BITRATES[bitRateIdx])
          continue;
        if (data.src.equals(data.dst)) {
          Float x = contributor.mapX.get(data.src);
          Float y = contributor.mapY.get(data.src);
          if (null == x || null == y) {
            System.err.println("Missing x or y for " + this.src);
            continue;
          }
          lstNodes.add(new LinkTableData.Node(Integer.parseInt(data.src),x,y));
        } else {
          Line line = null;
          if (signalStrength)
            line = data.getSnrDB().getLine();
          else
            line = data.getMacReceive().getLine();

          // at least 5 points !!
          if (5 >= line.getLengthInternal())
            continue;

          // TODO get real net addresses
          double metric = 0;
          for (int i = 0; i < line.getLengthInternal(); i++)
            metric += line.getYInternal()[i];
          metric = metric / line.getLengthInternal();
          if (!signalStrength)
            metric = 100. / metric;

          lstLinks.add(new LinkTableData.Link(new NetAddress(Integer.parseInt(data.src)),
              new NetAddress(Integer.parseInt(data.dst)), (int)metric, 0));
        }

      }

      LinkTableData ret = new LinkTableData(bitRateIdx, lstNodes, lstLinks);
      addData(ret, DataManager.LEVEL_BASIC);
    }
  }

  public static void main(String[] args) throws Exception {
    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 optEval = parser.addStringOption('e', "eval");
    CmdLineParser.Option optArgs = parser.addStringOption('a', "args");
    parser.parse(args);

    if(parser.getOptionValue(optEval)==null
        ||parser.getOptionValue(optArgs)==null)
      throw new Exception("missing parameters");

    StringTokenizer strtok = new StringTokenizer(
        (String)parser.getOptionValue(optEval), "#");
    StringTokenizer strtok2 = new StringTokenizer(
        (String)parser.getOptionValue(optArgs), "#");

    RoofnetDumpSigcomm roof = new RoofnetDumpSigcomm();
    roof.run(strtok.nextToken(), strtok.nextToken(),
        strtok.nextToken(), strtok.nextToken(), strtok.nextToken(),
        strtok.nextToken(), strtok.nextToken(), strtok.nextToken(),
        strtok2.nextToken(), strtok2.nextToken(), strtok2.nextToken(),
        strtok2.nextToken());
  }

  private long getOffset(String dst, String seq, long start) {
    Long offset = contributor.mapNodeTimeOffset.get(dst);
    if (null == offset) {
      List entries = contributor.linkTimeBar.getEntries();
      for (int i = 0; i < entries.size(); i++) {
        TimeBar.Entry entry = (Entry) entries.get(i);
        if (entry.text.equals(seq)) {
          offset = entry.start - start;
          break;
        }
      }
      contributor.mapNodeTimeOffset.put(dst, offset);
    }
    return offset;
  }

  protected NodeData getNodeData(String dst, String src, int bitRate) {
    NodeData ret = contributor.nodeData.get(new NodeData(dst, src, bitRate));
    if (null == ret) {
      ret = new NodeData(dst, src, bitRate);
      contributor.nodeData.put(ret, ret);
    }
    return ret;
  }

  protected void addData(DataContribution data, int level) {
    contributor.addData(data, level);
  }

  private class RoofnetContributor extends DataContributor {


    private String category = "Global";

    public TimeBar linkTimeBar = new TimeBar(new String[] {category, "Timebar"});
    public Map<NodeData, NodeData> nodeData = new HashMap<NodeData, NodeData>();
    public Map<String, Long> mapNodeTimeOffset = new HashMap<String, Long>();
    public Map<String,Float> mapX = new HashMap<String, Float>();
    public Map<String,Float> mapY = new HashMap<String, Float>();



    @Override
    public String getId() {
      return RoofnetContributor.class.getSimpleName();
    }

    public void onFinished() {
      Iterator<NodeData> iter = nodeData.values().iterator();
      while (iter.hasNext()) {
        iter.next().finish();
      }
    }

    @Override
    public void setDataManager(DataManager manager) {
      super.setDataManager(manager);
      dataManager.addData(contributor, linkTimeBar);
    }

    @Override
    public int addData(DataContribution data, int level) {
      return super.addData(data, level);
    }
  }

  private class NodeData {
    private static final int AUTOCORRELLATION_SIZE = 1000;
    public long nSeq = -1;
    private DiagramData macSend;
    private DiagramData macReceive;
    private DiagramDataHist macLossLengthHist;
    private DiagramData signalPowerDB;
    private DiagramData noisePowerDB;
    private DiagramData snrDB;
    private DiagramData signalPowerDBNoBias;
    private DiagramData noisePowerDBNoBias;
    private DiagramData snrDBNoBias;

    private String category;
    private int bitRate;
    private String subCategory;
    private String dst;
    private String src;
    private DiagramData signalPowerAutocorr;
    private DiagramData noisePowerAutocorr;
    private DiagramData snrPowerAutocorr;
    private DiagramData signalPowerNoBiasAutocorr;
    private DiagramData noisePowerNoBiasAutocorr;
    private DiagramData snrPowerNoBiasAutocorr;
    private DiagramData receiveAutocorr;

    public NodeData(String dst, String src, int bitRate) {
      this.dst = dst;
      this.src = src;
      this.bitRate = bitRate;
      this.category = "Node " + this.dst;
      this.subCategory = "Sender " + this.src + " Rate " + this.bitRate;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + bitRate;
      result = prime * result + ((dst == null) ? 0 : dst.hashCode());
      result = prime * result + ((src == null) ? 0 : src.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final NodeData other = (NodeData) obj;
      if (bitRate != other.bitRate)
        return false;
      if (dst == null) {
        if (other.dst != null)
          return false;
      } else if (!dst.equals(other.dst))
        return false;
      if (src == null) {
        if (other.src != null)
          return false;
      } else if (!src.equals(other.src))
        return false;
      return true;
    }

    public void finish() {
      String[] path = new String[] {category, subCategory, "Signal Power autocorr"};
      Line line = Statistics.autoCorrelate(signalPowerDB,
          path[path.length-1], AUTOCORRELLATION_SIZE, .01);
      if (null != line) addData(signalPowerAutocorr = new DiagramData(line, path,
          "lag", "", "auto corralation", ""), DataManager.LEVEL_BASIC);

      path = new String[] {category, subCategory, "Noise Power autocorr"};
      line = Statistics.autoCorrelate(noisePowerDB,
          path[path.length-1], AUTOCORRELLATION_SIZE, .01);
      if (null != line) addData(noisePowerAutocorr = new DiagramData(line, path,
          "lag", "", "auto corralation", ""), DataManager.LEVEL_BASIC);

      path = new String[] {category, subCategory, "SNR autocorr"};
      line = Statistics.autoCorrelate(snrDB,
          path[path.length-1], AUTOCORRELLATION_SIZE, .01);
      if (null != line) addData(snrPowerAutocorr = new DiagramData(line, path,
          "lag", "", "auto corralation", ""), DataManager.LEVEL_BASIC);

      path = new String[] {category, subCategory, "Signal Power NoBias autocorr"};
      line = Statistics.autoCorrelate(signalPowerDBNoBias,
          path[path.length-1], AUTOCORRELLATION_SIZE, .01);
      if (null != line) addData(signalPowerNoBiasAutocorr = new DiagramData(line, path,
          "lag", "", "auto corralation", ""), DataManager.LEVEL_BASIC);

      path = new String[] {category, subCategory, "Noise Power NoBias autocorr"};
      line = Statistics.autoCorrelate(noisePowerDBNoBias,
          path[path.length-1], AUTOCORRELLATION_SIZE, .01);
      if (null != line) addData(noisePowerNoBiasAutocorr = new DiagramData(line, path,
          "lag", "", "auto corralation", ""), DataManager.LEVEL_BASIC);

      path = new String[] {category, subCategory, "SNR NoBias autocorr"};
      line = Statistics.autoCorrelate(snrDB,
          path[path.length-1], AUTOCORRELLATION_SIZE, .01);
      if (null != line) addData(snrPowerNoBiasAutocorr = new DiagramData(line, path,
          "lag", "", "auto corralation", ""), DataManager.LEVEL_BASIC);

      path = new String[] {category, subCategory, "receive autocorr"};
      line = Statistics.autoCorrelate(macReceive,
          path[path.length-1], AUTOCORRELLATION_SIZE, .01);
      if (null != line) addData(receiveAutocorr = new DiagramData(line, path,
          "lag", "", "auto corralation", ""), DataManager.LEVEL_BASIC);

      // Generate allan dev
      if (signalPowerDB != null) {
        path = new String[] {category, subCategory, "Signal Power AllanDev"};
        double[][] allan = Statistics.allanDeviation(signalPowerDB.getY(), 12800. / bitRate);
        addData(/*TODO signalPowerDBAllenDev =*/ new DiagramData(allan[0], allan[1], path,
            "time", "s", "allan dev", ""), DataManager.LEVEL_BASIC);

        path = new String[] {category, subCategory, "Signal Power AllanDev"};
        allan = Statistics.allanDeviation(signalPowerDB.getY(), 12800. / bitRate);
        addData(new DiagramData(allan[0], allan[1], path,
            "time", "s", "allan dev", ""), DataManager.LEVEL_BASIC);
      }
    }

    public DiagramData getSignalPowerDB() {
      if (null == signalPowerDB) {
        String[] path = new String[] {
            category, subCategory, "Signal Power dB "};
            //+ String.valueOf(rate/Constants.BANDWIDTH_1Mbps) + " Mbps",
        signalPowerDB = new DiagramData(new TimeLine(path[path.length-1]),
            path, "time", "sec", "Signal Power", "dBm", AbstractDiagramData.MARK);
        addData(signalPowerDB, DataManager.LEVEL_ALL);

        path = new String[] {
            category, subCategory, "Signal Power dB avg'd"};
        DiagramData signalPowerDBAvg = new DiagramData(
            new AveragedTimeLine(path[path.length-1], sampleLen, AveragedTimeLine.MODE_A, 1),
            path, "time", "sec", "Signal Power", "dBm");
        signalPowerDB.addChain(signalPowerDBAvg);
        addData(signalPowerDBAvg, DataManager.LEVEL_BASIC);
      }

      return signalPowerDB;
    }

    public DiagramData getNoisePowerDB() {
      if (null == noisePowerDB) {
        String[] path = new String[] {
            category, subCategory, "Noise Power dB "};
            //+ String.valueOf(rate/Constants.BANDWIDTH_1Mbps) + " Mbps",
        noisePowerDB = new DiagramData(new TimeLine(path[path.length-1]),
            path, "time", "sec", "Noise Power", "dBm", AbstractDiagramData.MARK);
        addData(noisePowerDB, DataManager.LEVEL_ALL);

        path = new String[] {
            category, subCategory, "Noise Power dB avg'd"};
        DiagramData noisePowerDBAvg = new DiagramData(
            new AveragedTimeLine(path[path.length-1], sampleLen, AveragedTimeLine.MODE_A, 1),
            path, "time", "sec", "Noise Power", "dBm");
        noisePowerDB.addChain(noisePowerDBAvg);
        addData(noisePowerDBAvg, DataManager.LEVEL_BASIC);
      }

      return noisePowerDB;
    }

    public DiagramData getSnrDB() {
      if (null == snrDB) {
        String[] path = new String[] {
            category, subCategory, "SNR dB "};
            //+ String.valueOf(rate/Constants.BANDWIDTH_1Mbps) + " Mbps",
        snrDB = new DiagramData(new TimeLine(path[path.length-1]),
            path, "time", "sec", "SNR", "dBm", AbstractDiagramData.MARK);
        addData(snrDB, DataManager.LEVEL_ALL);

        path = new String[] {
            category, subCategory, "SNR dB avg'd"};
        DiagramData snrDBAvg = new DiagramData(
            new AveragedTimeLine(path[path.length-1], sampleLen, AveragedTimeLine.MODE_A, 1),
            path, "time", "sec", "SNR", "dBm");
        snrDB.addChain(snrDBAvg);
        addData(snrDBAvg, DataManager.LEVEL_BASIC);
      }

      return snrDB;
    }

    public DiagramData getSignalPowerDBNoBias() {
      if (null == signalPowerDBNoBias) {
        String[] path = new String[] {
            category, subCategory, "Signal Power NoBias dB"};
            //+ String.valueOf(rate/Constants.BANDWIDTH_1Mbps) + " Mbps",
        signalPowerDBNoBias = new DiagramData(new TimeLine(path[path.length-1]),
            path, "time", "sec", "Signal Power", "dBm", AbstractDiagramData.MARK);
        addData(signalPowerDBNoBias, DataManager.LEVEL_ALL);

        path = new String[] {
            category, subCategory, "Signal Power NoBias dB avg'd"};
        DiagramData signalPowerDBAvgNoBias = new DiagramData(
            new AveragedTimeLine(path[path.length-1], sampleLen, AveragedTimeLine.MODE_A, 1),
            path, "time", "sec", "Signal Power", "dBm");
        signalPowerDBNoBias.addChain(signalPowerDBAvgNoBias);
        addData(signalPowerDBAvgNoBias, DataManager.LEVEL_BASIC);
      }

      return signalPowerDBNoBias;
    }

    public DiagramData getNoisePowerDBNoBias() {
      if (null == noisePowerDBNoBias) {
        String[] path = new String[] {
            category, subCategory, "Noise Power NoBias dB "};
            //+ String.valueOf(rate/Constants.BANDWIDTH_1Mbps) + " Mbps",
        noisePowerDBNoBias = new DiagramData(new TimeLine(path[path.length-1]),
            path, "time", "sec", "Noise Power", "dBm", AbstractDiagramData.MARK);
        addData(noisePowerDBNoBias, DataManager.LEVEL_ALL);

        path = new String[] {
            category, subCategory, "Noise Power NoBias dB avg'd"};
        DiagramData noisePowerDBAvgNoBias = new DiagramData(
            new AveragedTimeLine(path[path.length-1], sampleLen, AveragedTimeLine.MODE_A, 1),
            path, "time", "sec", "Noise Power", "dBm");
        noisePowerDBNoBias.addChain(noisePowerDBAvgNoBias);
        addData(noisePowerDBAvgNoBias, DataManager.LEVEL_BASIC);
      }

      return noisePowerDBNoBias;
    }

    public DiagramData getSnrDBNoBias() {
      if (null == snrDBNoBias) {
        String[] path = new String[] {
            category, subCategory, "SNR NoBias dB "};
            //+ String.valueOf(rate/Constants.BANDWIDTH_1Mbps) + " Mbps",
        snrDBNoBias = new DiagramData(new TimeLine(path[path.length-1]),
            path, "time", "sec", "SNR", "dBm", AbstractDiagramData.MARK);
        addData(snrDBNoBias, DataManager.LEVEL_ALL);

        path = new String[] {
            category, subCategory, "SNR NoBias dB avg'd"};
        DiagramData snrDBAvgNoBias = new DiagramData(
            new AveragedTimeLine(path[path.length-1], sampleLen, AveragedTimeLine.MODE_A, 1),
            path, "time", "sec", "SNR", "dBm");
        snrDBNoBias.addChain(snrDBAvgNoBias);
        addData(snrDBAvgNoBias, DataManager.LEVEL_BASIC);
      }

      return snrDBNoBias;
    }

    protected DiagramData getMacSend() {
      // MAC send counter
      if (null == macSend) {
        macSend = new DiagramData(
            new String[] {category, subCategory, "send vs time"}, "time", "s",
                "mac send packets", "n/a", AbstractDiagramData.MARK);
        addData(macSend, DataManager.LEVEL_ALL);

        DiagramData macSendCuml = new DiagramData(
            new AveragedTimeLine("send (cuml'd)", sampleLen, AveragedTimeLine.MODE_CN, 1.),
            new String[] {category, subCategory, "send (cuml'd)"}, "time", "sec",
            "sent packets", "no.");
        macSend.addChain(macSendCuml);
        addData(macSendCuml, DataManager.LEVEL_BASIC);

        DiagramData macSendRated = new DiagramData(
            new AveragedTimeLine("send (avg'd)", sampleLen, AveragedTimeLine.MODE_WA2, 1.),
              new String[] {category, subCategory, "send (avg)"}, "time", "s",
                "mac send packets", "n/a");
        macSendCuml.addChain(macSendRated);
        addData(macSendRated, DataManager.LEVEL_BASIC);
      }
      return macSend;
    }

    protected DiagramData getMacReceive() {
      // mac receive counter
      if (null == macReceive) {
        macReceive = new DiagramData(
            new String[] {category, subCategory, "receive vs time"}, "time", "s",
                "mac receive packets", "n/a", AbstractDiagramData.MARK);
        addData(macReceive, DataManager.LEVEL_ALL);

        DiagramData macRecvCuml = new DiagramData(
            new AveragedTimeLine("receive (cuml'd)", sampleLen, AveragedTimeLine.MODE_CN, 1.),
            new String[] {category, subCategory, "receive (cuml'd)"}, "time", "sec",
            "received packets", "no.");
        macReceive.addChain(macRecvCuml);
        addData(macRecvCuml, DataManager.LEVEL_BASIC);

        DiagramData macReceiveRated = new DiagramData(
            new AveragedTimeLine("receive (avg'd)", sampleLen, AveragedTimeLine.MODE_WA2, 1.),
            new String[] {category, subCategory, "receive (avg)"}, "time", "s",
            "mac receive packets", "n/a");
        macRecvCuml.addChain(macReceiveRated);
        addData(macReceiveRated, DataManager.LEVEL_BASIC);
      }
      return macReceive;
    }

    public DiagramDataHist getMacLossLengthHist() {
      if (null == macLossLengthHist) {
        macLossLengthHist = new DiagramDataHist(
            new String[] {category, subCategory, "loss length hist "},
            "loss length", "packets", "occurance", "n/a", DiagramDataHist.MODE_CUML);
        addData(macLossLengthHist, DataManager.LEVEL_ALL);
      }
      return macLossLengthHist;
    }

    public void processMacLossLengthHist(int bitRate, long nSeq, double time) {
      if (-1 == this.nSeq)
        this.nSeq = nSeq -1;
      Util.assertion(this.nSeq < nSeq);

      for (long i = this.nSeq+1; i < nSeq; i++) {
        this.getMacReceive().addNextPoint(time-1e-9, 0.);
        this.getSignalPowerDBNoBias().addNextPoint(time-1e-9, 0);
        this.getNoisePowerDBNoBias().addNextPoint(time-1e-9, 0);
        this.getSnrDBNoBias().addNextPoint(time-1e-9, 0);
      }

      long lossLength = nSeq - this.nSeq - 1;
      this.nSeq = nSeq;
      if (lossLength > 0)
        this.getMacLossLengthHist(). addNextValue(lossLength, 1.);
    }

  }

  private class CorrData {
    private DiagramData snrAutoCorrCdf;
    private DiagramData snrNoBiasAutoCorrCdf;
    private DiagramData signalAutoCorrCdf;
    private DiagramData signalNoBiasAutoCorrCdf;
    private DiagramData noiseAutoCorrCdf;
    private DiagramData noiseNoBiasAutoCorrCdf;
    private DiagramData receiveAutoCorrCdf;

    private String category;
    private int lag;
    private String subCategory;

    private int bitrate;
    private DiagramDataHist receiveAutoCorrLag;
    private DiagramDataHist noiseAutoCorrLag;
    private DiagramDataHist noiseNoBiasAutoCorrLag;
    private DiagramDataHist signalAutoCorrLag;
    private DiagramDataHist signalNoBiasAutoCorrLag;
    private DiagramDataHist snrAutoCorrLag;
    private DiagramDataHist snrNoBiasAutoCorrLag;

    public CorrData(int lag, int bitrate) {
      this.lag = lag;
      this.bitrate = bitrate;
      this.category = "Global";
      this.subCategory = "Autocorrelation Rate " + this.bitrate;
    }

    public void finish() {
      Line cdf = null;
      String[] path = null;

      if (null != snrAutoCorrLag) {
        cdf = snrAutoCorrLag.generateCdf();
        path = snrAutoCorrLag.getPath().clone();
        path[path.length-1] = cdf.getTitle();
        addData(snrAutoCorrCdf = new DiagramData(cdf, path, "Autocorrelation", "n/a",
            "Fraction of Links", "n/a"), DataManager.LEVEL_ALL);
      }

      if (null != snrNoBiasAutoCorrLag) {
        cdf = snrNoBiasAutoCorrLag.generateCdf();
        path = snrNoBiasAutoCorrLag.getPath().clone();
        path[path.length-1] = cdf.getTitle();
        addData(snrNoBiasAutoCorrCdf = new DiagramData(cdf, path, "Autocorrelation", "n/a",
            "Fraction of Links", "n/a"), DataManager.LEVEL_ALL);
      }

      if (null != signalAutoCorrLag) {
        cdf = signalAutoCorrLag.generateCdf();
        path = signalAutoCorrLag.getPath().clone();
        path[path.length-1] = cdf.getTitle();
        addData(signalAutoCorrCdf = new DiagramData(cdf, path, "Autocorrelation", "n/a",
            "Fraction of Links", "n/a"), DataManager.LEVEL_ALL);
      }

      if (null != signalNoBiasAutoCorrLag) {
        cdf = signalNoBiasAutoCorrLag.generateCdf();
        path = signalNoBiasAutoCorrLag.getPath().clone();
        path[path.length-1] = cdf.getTitle();
        addData(signalNoBiasAutoCorrCdf = new DiagramData(cdf, path, "Autocorrelation", "n/a",
            "Fraction of Links", "n/a"), DataManager.LEVEL_ALL);
      }

      if (null != noiseAutoCorrLag) {
        cdf = noiseAutoCorrLag.generateCdf();
        path = noiseAutoCorrLag.getPath().clone();
        path[path.length-1] = cdf.getTitle();
        addData(noiseAutoCorrCdf = new DiagramData(cdf, path, "Autocorrelation", "n/a",
            "Fraction of Links", "n/a"), DataManager.LEVEL_ALL);
      }

      if (null != noiseNoBiasAutoCorrLag) {
        cdf = noiseNoBiasAutoCorrLag.generateCdf();
        path = noiseNoBiasAutoCorrLag.getPath().clone();
        path[path.length-1] = cdf.getTitle();
        addData(noiseNoBiasAutoCorrCdf = new DiagramData(cdf, path, "Autocorrelation", "n/a",
            "Fraction of Links", "n/a"), DataManager.LEVEL_ALL);
      }

      if (null != receiveAutoCorrLag) {
        cdf = receiveAutoCorrLag.generateCdf();
        path = receiveAutoCorrLag.getPath().clone();
        path[path.length-1] = cdf.getTitle();
        addData(receiveAutoCorrCdf = new DiagramData(cdf, path, "Autocorrelation", "n/a",
            "Fraction of Links", "n/a"), DataManager.LEVEL_ALL);
      }
    }

    public DiagramDataHist getReceiveAutoCorrLag() {
      if (null == receiveAutoCorrLag) {
        String[] path = new String[] {
            category, subCategory, "Receive Autocorrelation at lag " + this.lag};
        receiveAutoCorrLag = new DiagramDataHist(
            path, "link", "n/a", "Autocorrelation", "n/a", DiagramDataHist.MODE_CUML);
        addData(receiveAutoCorrLag, DataManager.LEVEL_ALL);
      }

      return receiveAutoCorrLag;
    }

    public DiagramDataHist getNoiseNoBiasAutoCorrLag() {
      if (null == noiseNoBiasAutoCorrLag) {
        String[] path = new String[] {
            category, subCategory, "Noise NoBias Autocorrelation at lag " + this.lag};
        noiseNoBiasAutoCorrLag = new DiagramDataHist(
            path, "link", "n/a", "Autocorrelation", "n/a", DiagramDataHist.MODE_CUML);
        addData(noiseNoBiasAutoCorrLag, DataManager.LEVEL_ALL);
      }

      return noiseNoBiasAutoCorrLag;
    }

    public DiagramDataHist getNoiseAutoCorrLag() {
      if (null == noiseAutoCorrLag) {
        String[] path = new String[] {
            category, subCategory, "Noise Autocorrelation at lag " + this.lag};
        noiseAutoCorrLag = new DiagramDataHist(
            path, "link", "n/a", "Autocorrelation", "n/a", DiagramDataHist.MODE_CUML);
        addData(noiseAutoCorrLag, DataManager.LEVEL_ALL);
      }

      return noiseAutoCorrLag;
    }

    public DiagramDataHist getSignalNoBiasAutoCorrLag() {
      if (null == signalNoBiasAutoCorrLag) {
        String[] path = new String[] {
            category, subCategory, "Signal NoBias Autocorrelation at lag " + this.lag};
        signalNoBiasAutoCorrLag = new DiagramDataHist(
            path, "link", "n/a", "Autocorrelation", "n/a", DiagramDataHist.MODE_CUML);
        addData(signalNoBiasAutoCorrLag, DataManager.LEVEL_ALL);
      }

      return signalNoBiasAutoCorrLag;
    }

    public DiagramDataHist getSignalAutoCorrLag() {
      if (null == signalAutoCorrLag) {
        String[] path = new String[] {
            category, subCategory, "Signal Autocorrelation at lag " + this.lag};
        signalAutoCorrLag = new DiagramDataHist(
            path, "link", "n/a", "Autocorrelation", "n/a", DiagramDataHist.MODE_CUML);
        addData(signalAutoCorrLag, DataManager.LEVEL_ALL);
      }

      return signalAutoCorrLag;
    }

    public DiagramDataHist getSnrNoBiasAutoCorrLag() {
      if (null == snrNoBiasAutoCorrLag) {
        String[] path = new String[] {
            category, subCategory, "SNR NoBias Autocorrelation at lag " + this.lag};
        snrNoBiasAutoCorrLag = new DiagramDataHist(
            path, "link", "n/a", "Autocorrelation", "n/a", DiagramDataHist.MODE_CUML);
        addData(snrNoBiasAutoCorrLag, DataManager.LEVEL_ALL);
      }

      return snrNoBiasAutoCorrLag;
    }

    public DiagramDataHist getSnrAutoCorrLag() {
      if (null == snrAutoCorrLag) {
        String[] path = new String[] {
            category, subCategory, "SNR Autocorrelation at lag " + this.lag};
        snrAutoCorrLag = new DiagramDataHist(//new TimeLine(path[path.length-1]),
            path, "link", "n/a", "Autocorrelation", "n/a", DiagramDataHist.MODE_CUML);
        addData(snrAutoCorrLag, DataManager.LEVEL_ALL);
      }

      return snrAutoCorrLag;
    }

    public DiagramData getSnrAutoCorrCdf() {
      return snrAutoCorrCdf;
    }

    public DiagramData getSnrNoBiasAutoCorrCdf() {
      return snrNoBiasAutoCorrCdf;
    }

    public DiagramData getSignalAutoCorrCdf() {
      return signalAutoCorrCdf;
    }

    public DiagramData getSignalNoBiasAutoCorrCdf() {
      return signalNoBiasAutoCorrCdf;
    }

    public DiagramData getNoiseAutoCorrCdf() {
      return noiseAutoCorrCdf;
    }

    public DiagramData getNoiseNoBiasAutoCorrCdf() {
      return noiseNoBiasAutoCorrCdf;
    }

    public DiagramData getReceiveAutoCorrCdf() {
      return receiveAutoCorrCdf;
    }

    public String getCategory() {
      return category;
    }

    public int getLag() {
      return lag;
    }

    public String getSubCategory() {
      return subCategory;
    }

    public int getBitrate() {
      return bitrate;
    }
  }
}
