package brn.analysis.dump;

import java.io.IOException;
import java.util.Arrays;

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

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.TimeLine;
import brn.sim.data.XplotSerializer;
import brn.sim.data.dump.WiresharkDump;
import brn.sim.data.dump.WiresharkMessage;

/**
 * Calculate a time series of ack response times, calc mean and variance
 *
 * Calculate number of correct/false - positives/negatives
 *
 * @author kurth
 */
public class AckTiming {

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

  protected static final MacAddress ADDR_DEST = new MacAddress("00:CA:FF:EE:BA:BE");

  protected TimeLine ackByFrame = new TimeLine("Ack delay by frame time");
  protected TimeLine ackByHost = new TimeLine("Ack delay by host time");
  protected TimeLine ackByMac = new TimeLine("Ack delay by mac time");

  protected static class Frame {
    public long frameTime;
    public long hostTime;
    public long macTime;
    public byte[] addr2;
  }

  protected Frame frame;

  private void analyzePacket(WiresharkMessage msg, long time) {
    byte[] packet = new byte[msg.getPayload().getSize()];
    msg.getPayload().getBytes(packet, 0);

    byte[] addr1 = IEEE80211Header.getAddr1(packet, Prism2Header.HEADER_SIZE);
    byte[] addr2 = IEEE80211Header.getAddr2(packet, Prism2Header.HEADER_SIZE);

    // if data frame, collect timing
    if (Arrays.equals(ADDR_DEST.getAddress(),addr1)
        &&null != addr2) {
      this.frame = new Frame();
      this.frame.frameTime = time;
      this.frame.hostTime = Prism2Header.getRxHostTime(packet, 0);
      this.frame.macTime = Prism2Header.getRxMacTime(packet, 0);
      this.frame.addr2 = addr2;
      return;
    }

    // if ack, match againt data frame and store timing
    if (this.frame != null
        &&null == addr2
        &&Arrays.equals(this.frame.addr2, addr1)) {
      int frameDiff = (int)(time - this.frame.frameTime);
      int hostDiff = (int)(Prism2Header.getRxHostTime(packet, 0) - this.frame.hostTime);
      int macDiff = (int)(Prism2Header.getRxMacTime(packet, 0) - this.frame.macTime);

      this.ackByFrame.add(time, frameDiff);
      this.ackByHost.add(time, hostDiff);
      this.ackByMac.add(time, macDiff);
    }

    // otherwise clear state
    this.frame = null;
  }

  private void analyze(String fileName) throws IOException {
    log.info("Analyzing " + fileName);

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

    // Iterate through the dump
    long start = -1;
    for (WiresharkMessage msg = dump.read(); null != msg; msg = 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(fileName);
  }

  private void save(String fileName) throws IOException {
    XplotSerializer seri = new XplotSerializer("ack timing by frames", "ack time", "ack delay");
    seri.addLine(ackByFrame, "red");
    seri.saveToFile(fileName + "-frame.xpl");

    seri = new XplotSerializer("ack timing by host", "ack time", "ack delay");
    seri.addLine(ackByHost, "red");
    seri.saveToFile(fileName + "-host.xpl");

    seri = new XplotSerializer("ack timing by mac", "ack time", "ack delay");
    seri.addLine(ackByMac, "red");
    seri.saveToFile(fileName + "-mac.xpl");

    double[] avg = new double[3];
    double[] std = new double[3];

    avg[0] = Statistics.mean(ackByFrame.getY());
    avg[1] = Statistics.mean(ackByHost.getY());
    avg[2] = Statistics.mean(ackByMac.getY());

    std[0] = Statistics.stddev(ackByFrame.getY());
    std[1] = Statistics.stddev(ackByHost.getY());
    std[2] = Statistics.stddev(ackByMac.getY());

    System.out.println("By frame timing: mean "+avg[0]+" stddev "+std[0]);
    System.out.println("By host timing: mean "+avg[1]+" stddev "+std[1]);
    System.out.println("By mac timing: mean "+avg[2]+" stddev "+std[2]);
  }

  private void finished() throws IOException {
  }

  protected static void usage() {
    System.out.println("AckTiming capture.pcap [must be with prism2 header]");
  }

  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.ALL);

    if (1 != args.length) {
      usage();
      return;
    }

    AckTiming p = new AckTiming();
    for (int i = 0; i < args.length; i++) {
      p.analyze(args[i]);
    }
    p.finished();
  }

}
