package brn.analysis.dump;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

import brn.analysis.Statistics;
import brn.sim.data.AveragedTimeLine;
import brn.sim.data.TimeLine;
import brn.sim.data.XplotSerializer;
import brn.sim.data.dump.WiresharkDump;
import brn.sim.data.dump.WiresharkMessage;

import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.misc.MessageBytes;

public class PacketDelivProb {

  protected static final String[] rateColor = {
    "red", // 1
    "blue", // 2
    "yellow", // 5
    "green", // 11
    "white", // 6
    "magenta", // 9
    "orange", // 12
    "purple", // 18
    "red", // 24
    "blue", // 36
    "yellow", // 48
    "green", // 54
  };

  private static class Pair {

    private Object first;
    private Object second;

    public Pair(Object first, Object second) {
      this.first = first;
      this.second = second;
    }

    public Object getFirst() {
      return first;
    }

    public Object getSecond() {
      return second;
    }

    public boolean equals(Object obj) {
      if (!getClass().equals(obj.getClass()))
        return false;
      Pair other = (Pair) obj;
      if (first.equals(other.first)
          &&second.equals(other.second))
          return true;
      return false;
    }

    public int hashCode() {
      return first.hashCode() + second.hashCode();
    }

    public String toString() {
      return "Pair [" + first + "|" + second + "]";
    }


  }

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

  protected static final String[] rateString = {
    "1Mbps",
    "2Mbps",
    "5Mbps",
    "11Mbps",
    "6Mbps",
    "9Mbps",
    "12Mbps",
    "18Mbps",
    "24Mbps",
    "36Mbps",
    "48Mbps",
    "54Mbps"
  };

  protected static final String[] statusString = {
    "ok", "crc", "phy", "all" };

  protected static final Map nodeString;

  static
  {
    HashMap tempMap = new HashMap();

    tempMap.put(new MacAddress("00:0F:B5:3F:42:62"), "wgt23");
    tempMap.put(new MacAddress("00:0F:B5:3F:21:3C"), "wgt24");
    tempMap.put(new MacAddress("00:0F:B5:0B:95:29"), "wgt25");
    tempMap.put(new MacAddress("00:0F:B5:97:25:82"), "wgt28");
    tempMap.put(new MacAddress("00:15:6D:93:0A:8C"), "wgt28");
    tempMap.put(new MacAddress("00:0F:B5:3F:30:1F"), "wgt29");
    tempMap.put(new MacAddress("00:0F:B5:97:33:6C"), "wgt31");
    tempMap.put(new MacAddress("00:0F:B5:3F:22:E1"), "wgt32");
    tempMap.put(new MacAddress("00:0F:B5:3F:1E:C7"), "wgt33");
    tempMap.put(new MacAddress("00:0F:B5:3F:22:E9"), "wgt34");
    tempMap.put(new MacAddress("00:0F:B5:3F:21:60"), "wgt41");
    tempMap.put(new MacAddress("00:0F:B5:3F:20:D6"), "wgt42");
    tempMap.put(new MacAddress("00:15:6D:93:0A:75"), "wgt42");
    tempMap.put(new MacAddress("00:0F:B5:3F:21:81"), "wgt43");
    tempMap.put(new MacAddress("00:15:6D:93:0A:44"), "wgt43");
    tempMap.put(new MacAddress("00:0F:B5:3F:56:B1"), "wgt44");
    tempMap.put(new MacAddress("00:15:6D:93:0A:78"), "wgt44");
    tempMap.put(new MacAddress("00:0F:B5:3F:58:49"), "wgt45");
    tempMap.put(new MacAddress("00:15:6D:93:0A:84"), "wgt45");


    tempMap.put(new MacAddress("00:0F:B5:97:34:E9"), "wgt52");
    tempMap.put(new MacAddress("00:0F:B5:97:35:86"), "wgt54");



    tempMap.put(new MacAddress("00:0F:B5:97:33:D1"), "wgt60");
    tempMap.put(new MacAddress("00:0F:B5:0D:AE:34"), "wgt70");
    tempMap.put(new MacAddress("00:0F:B5:97:33:8B"), "wgt71");
    tempMap.put(new MacAddress("00:0F:B5:97:36:7D"), "wgt72");
    tempMap.put(new MacAddress("00:15:6D:93:0A:77"), "wgt70");
    tempMap.put(new MacAddress("00:15:6D:93:0A:43"), "wgt74");
    tempMap.put(new MacAddress("00:15:6D:93:0A:74"), "wgt78");
    tempMap.put(new MacAddress("00:15:6D:93:0A:8A"), "wgt79");

    tempMap.put(new MacAddress("00:15:6D:93:0A:8B"), "wgt46");
    tempMap.put(new MacAddress("00:15:6D:93:0A:3F"), "wgt47");
    tempMap.put(new MacAddress("00:15:6D:93:0A:71"), "wgt82");

    tempMap.put(new MacAddress("00:0F:B5:3F:57:43"), "wgt81");

    tempMap.put(new MacAddress("11:11:11:11:11:11"), "fake");

    nodeString = Collections.unmodifiableMap(tempMap);
  }

  private long interval;

  private static class ReferenceCtx {
    public String sender = null;
    public int pktRateRef[] = new int[rateString.length];
  }

  private static class LinkCtx {
    public String sender = null;
    public String receiver = null;
    public AveragedTimeLine[][] tlPDR = new AveragedTimeLine[statusString.length][rateString.length];
    public TimeLine[][] tlRSSI = new TimeLine[statusString.length][rateString.length];
    public AveragedTimeLine[][] tlRSSIavg = new AveragedTimeLine[statusString.length][rateString.length];
    public int pktStatusRate[][] = new int[statusString.length][rateString.length];

    public LinkCtx(long interval) {
      for (int i = 0; i < statusString.length; i++)
        for (int j = 0; j < rateString.length; j++)
          tlPDR[i][j] = new AveragedTimeLine("PDR " + rateString[j] + " " + statusString[i],
              interval, AveragedTimeLine.MODE_C, 1.0);

      for (int i = 0; i < statusString.length; i++)
        for (int j = 0; j < rateString.length; j++) {
            tlRSSI[i][j] = new TimeLine("RSSI " + rateString[i] + " " + statusString[i]);
            tlRSSIavg[i][j] = new AveragedTimeLine("RSSI " + rateString[i] + " " + statusString[i],
                interval, AveragedTimeLine.MODE_A, 1.0);
        }
    }
  }

  private static class NodeCtx{
    public WiresharkDump dump = null;
    public String fileName = null;
    public String name;
  }

  private Map mapReference; // byte[6] -> ReferenceCtx

  private Map mapLinks; // Pair(byte[6],byte[6]) -> LinkCtx

  private NodeCtx node;

  private NodeCtx ref;


  /**
   * Constructor.
   *
   * @param fileName file name of the reference stream.
   * @param interval
   * @throws IOException
   */
  public PacketDelivProb(String fileName, long interval) throws IOException {
    this.interval = interval;
    mapReference = new HashMap();
    mapLinks = new HashMap();

    ref = null;
    node = null;

    analyzeRef(fileName);
  }

  /**
   * Analyze the reference file.
   *
   * @param fileName
   * @throws IOException
   */
  private void analyzeRef(String fileName) throws IOException {
    log.info("Analyzing " + fileName);

    ref = new NodeCtx();
    ref.dump = new WiresharkDump();
    ref.dump.open(fileName);

    // Iterate through the dump
    for (WiresharkMessage msg = ref.dump.read(); null != msg; msg = ref.dump.read()) {
      MessageBytes bytes = (MessageBytes) msg.getPayload();
      int idx = AthdescHeader.getExtTxRateIdx(bytes.getBytes(), 0);

      if (bytes.getBytes().length == 0)
        continue;
      MacAddress mac = new MacAddress(bytes.getBytes(), AthdescHeader.HEADER_SIZE
          + IEEE80211Header.OFFSET_ADDR2);
      String sender = (String)nodeString.get(mac);
      if (null == sender) {
        log.warn("Unknown packet sender address 1" + mac.toString());
        return;
      }

      ReferenceCtx refCtx = (ReferenceCtx)mapReference.get(sender);
      if (null == refCtx) {
        refCtx = new ReferenceCtx();
        refCtx.sender = sender;
        mapReference.put(sender, refCtx);
      }
      refCtx.pktRateRef[idx] += 1 + AthdescHeader.getExtTxLongRetry(bytes.getBytes(), 0);
    }

    ref = null;
  }

  /**
   * Main analyze loop.
   * @throws IOException
   *
   */
  public void analyze(String fileName) throws IOException {
    log.info("Analyzing " + fileName);

    node = new NodeCtx();
    node.fileName = fileName;
    node.dump = new WiresharkDump();
    node.dump.open(fileName);
    node.name = fileName.substring(fileName.indexOf("wgt")).substring(0, 5);

    // Iterate through the dump
    long start = -1;
    for (WiresharkMessage msg = node.dump.read(); null != msg; msg = node.dump.read()) {
      long time = msg.getTv_sec() * Constants.SECOND
        + msg.getTv_usec() * Constants.MICRO_SECOND;
      if (-1 == start)
        start = time;
      time -= start;

      analyzePacket(msg, time);
    }

    save();
    node = null;
  }

  /**
   * Analyze a single packet.
   *
   * @param msg
   * @param time
   */
  protected void analyzePacket(WiresharkMessage msg, long time) {
    MessageBytes bytes = (MessageBytes) msg.getPayload();

    if (bytes.getBytes().length < AthdescHeader.HEADER_SIZE
        + IEEE80211Header.OFFSET_ADDR3)
      return;

    MacAddress mac = new MacAddress(bytes.getBytes(), AthdescHeader.HEADER_SIZE
        + IEEE80211Header.OFFSET_ADDR2);
    String sender = (String)nodeString.get(mac);
    if (null == sender) {
      log.warn("Unknown packet sender address 2" + mac.toString());
      return;
    }

    LinkCtx linkCtx = (LinkCtx)mapLinks.get(new Pair(sender, node.name));
    if (null == linkCtx) {
      linkCtx = new LinkCtx(this.interval);
      linkCtx.sender = sender;
      linkCtx.receiver = node.name;
      mapLinks.put(new Pair(sender, node.name), linkCtx);
      log.info("created link " + sender + "-" + node.name);
    }

    int idx = AthdescHeader.getExtRxRateIdx(bytes.getBytes(), 0);
    byte status = AthdescHeader.getExtRxStatus(bytes.getBytes(), 0);
    int rssi = AthdescHeader.getExtRxRssi(bytes.getBytes(), 0);

    if (rssi != AthdescHeader.getRxRssi(bytes.getBytes(), 0))
      log.warn("RSSI values differ (" + rssi + "/" +
          AthdescHeader.getRxRssi(bytes.getBytes(), 0) + ")");

    if (12 > idx && 0 <= status) {
      linkCtx.tlPDR[status][idx].add(time, 1.0);
      linkCtx.tlPDR[3][idx].add(time, 1.0);
      linkCtx.tlRSSI[status][idx].add(time, rssi);
      linkCtx.tlRSSIavg[status][idx].add(time, rssi);
      linkCtx.tlRSSIavg[3][idx].add(time, rssi);
      linkCtx.pktStatusRate[status][idx]++;
    }
    else if (12 <= idx)
      log.warn("Invalid rate index " + idx);
    else if (0 > status)
      log.warn("Invalid status" + status);
  }

  /**
   * Save results to disk.
   *
   * @throws IOException
   */
  protected void save() throws IOException {
    node = null;
  }

  /**
   * Called when all input files have been analyzed
   * @throws IOException
   *
   */
  public void finished() throws IOException {
    log.info("Writing results");

    StringBuilder builder = new StringBuilder("status\trate\tsender\treceiver\tabs\trel\n");
    List listLinks = new ArrayList(mapLinks.values());
    List listReference = new ArrayList(mapReference.values());

    StringBuilder corrBuilder = new StringBuilder("node\trate\trate\tcorr_arrived\tcorr_correct\n");

    for (int rate = 0; rate < rateString.length; rate++) {
      for (int node = 0; node < listReference.size(); node++) {
        ReferenceCtx refCtx = (ReferenceCtx)listReference.get(node);

        builder.append("ok\t");
        builder.append(rateString[rate]);builder.append("\t");
        builder.append(refCtx.sender);builder.append("\t*\t");

        // build content for console output
        int packets = refCtx.pktRateRef[rate];
        builder.append(packets);builder.append("\t1.0\n");
      }

      for (int status = 0; status < statusString.length; status++) {
        for (int node = 0; node < listLinks.size(); node++) {
          LinkCtx linkCtx = (LinkCtx)listLinks.get(node);
          ReferenceCtx refCtx = (ReferenceCtx)mapReference.get(linkCtx.sender);

          if (refCtx == null)
                  continue;

          // prepare output files per rate
          for (int i = 0; i < 12; i++) {
            XplotSerializer seri = new XplotSerializer(rateString[i], "time (sec)", "PDR  / RSSI");
            seri.addLine(linkCtx.tlPDR[0][i], "green");
            seri.addLine(linkCtx.tlPDR[1][i], "yellow");
            seri.addLine(linkCtx.tlPDR[2][i], "red");
            seri.addLine(linkCtx.tlPDR[3][i], "blue");

//            seri.addPoints(linkCtx.tlRSSI[0][i], "green");
//            seri.addPoints(linkCtx.tlRSSI[1][i], "yellow");
//            seri.addPoints(linkCtx.tlRSSI[2][i], "red");

            String fileName = linkCtx.sender+"-"+linkCtx.receiver + "-" + rateString[i] + ".xpl";
            seri.saveToFile(fileName);

            seri = new XplotSerializer(rateString[i], "time (sec)", "RSSI");
            seri.addLine(linkCtx.tlRSSIavg[0][i], "green");
            seri.addLine(linkCtx.tlRSSIavg[1][i], "yellow");
            seri.addLine(linkCtx.tlRSSIavg[2][i], "red");
            seri.addLine(linkCtx.tlRSSIavg[3][i], "blue");
            seri.addPoints(linkCtx.tlRSSIavg[0][i], "green", "x");
            seri.addPoints(linkCtx.tlRSSIavg[1][i], "yellow", "x");
            seri.addPoints(linkCtx.tlRSSIavg[2][i], "red", "x");
            seri.addPoints(linkCtx.tlRSSIavg[3][i], "blue", "x");

            fileName = linkCtx.sender+"-"+linkCtx.receiver + "-" + rateString[i] + "-rssi.xpl";
            seri.saveToFile(fileName);
          }

          builder.append(statusString[status]);builder.append("\t");
          builder.append(rateString[rate]);builder.append("\t");
          builder.append(linkCtx.sender);builder.append("\t");
          builder.append(linkCtx.receiver);builder.append("\t");

          // build content for console output
          int packets = linkCtx.pktStatusRate[status][rate];
          double pdr = .0;
          if (0 != packets)
            pdr = packets / (double) refCtx.pktRateRef[rate];
          builder.append(packets);builder.append("\t");
          builder.append(pdr);builder.append("\n");
        }
      }
    }

    for (int node = 0; node < listLinks.size(); node++) {
      LinkCtx linkCtx = (LinkCtx)listLinks.get(node);

      // prepare output files per rate
      XplotSerializer seri = new XplotSerializer("avg rssi of arrived packets at " +
          linkCtx.receiver, "time (sec)", "RSSI");
      for (int i = 0; i < rateString.length; i++) {
        seri.addLine(linkCtx.tlRSSIavg[3][i], rateColor[i]);
        if (i < 4)
          seri.addPoints(linkCtx.tlRSSIavg[3][i], rateColor[i], "x");
      }

      String fileName = linkCtx.sender+"-"+linkCtx.receiver + "-rssi.xpl";
      seri.saveToFile(fileName);
    }

    // TODO calculate cross-rate correlation
    for (int node = 0; node < listLinks.size(); node++) {
      LinkCtx linkCtx = (LinkCtx)listLinks.get(node);
      for (int i = 0; i < 12; i++) {
        for (int j = i; j < rateString.length; j++) {
          double corr0 = Statistics.correlationCoefficient(linkCtx.tlPDR[0][i].getY(),
              linkCtx.tlPDR[0][j].getY());
          double corr1 = Statistics.correlationCoefficient(linkCtx.tlPDR[1][i].getY(),
              linkCtx.tlPDR[1][j].getY());
          double corr2 = Statistics.correlationCoefficient(linkCtx.tlPDR[2][i].getY(),
              linkCtx.tlPDR[2][j].getY());
          double corr3 = Statistics.correlationCoefficient(linkCtx.tlPDR[3][i].getY(),
              linkCtx.tlPDR[3][j].getY());

          corrBuilder.append(linkCtx.sender);
          corrBuilder.append("\t");
          corrBuilder.append(linkCtx.receiver);
          corrBuilder.append("\t");
          corrBuilder.append(rateString[i]);
          corrBuilder.append("\t");
          corrBuilder.append(rateString[j]);
          corrBuilder.append("\t");
          corrBuilder.append(corr0);
          corrBuilder.append("\t");
          corrBuilder.append(corr1);
          corrBuilder.append("\t");
          corrBuilder.append(corr2);
          corrBuilder.append("\t");
          corrBuilder.append(corr3);
          corrBuilder.append("\n");
        }
      }
    }
    // take care for german locale :-)
    System.out.println(builder.toString().replace('.', ','));

    System.out.println();
    System.out.println(corrBuilder.toString().replace('.', ','));


    this.mapLinks = null;
    this.mapReference = null;
  }

  /**
   *
   * @param args
   * @throws IOException
   */
  public static void main(String[] args) throws IOException {
    PatternLayout layout = new PatternLayout( "%d{ISO8601} %-5p [%t] %c: %m%n" );
    ConsoleAppender consoleAppender = new ConsoleAppender( layout );
    log.addAppender( consoleAppender );
    log.setLevel(Level.ERROR);

    long interval = 10 * Constants.SECOND;
    int idx = 0;

    if (-1 != "0123456789.".indexOf(args[idx].trim().charAt(0))) {
      interval = (long)(Double.parseDouble(args[idx]) * Constants.SECOND);
      idx++;
    }

    PacketDelivProb p = new PacketDelivProb(args[idx], interval);
    for (int i = idx+1; i < args.length; i++) {
      p.analyze(args[i]);
    }
    p.finished();
  }

}
