package brn.sim.data;

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

/**
 * Aggreagates a time line based on fixed aggregation intervals.
 *
 * @author kurth
 */
public class AveragedTimeLine extends TimeLine {
  private static final long serialVersionUID = 1L;

  /** weighted average per interval
   * weight values by their respective length and cumulate
   */
  public static final int MODE_WA = 0;

  /** cumulate all values in the interval and divide by the interval length */
  public static final int MODE_WA2 = 6; // MODE_WAVG_Y_ON_X

  /** average per interval
   * sum y values per interval and divide by the no. x values */
  public static final int MODE_A = 1; // MODE_AVG_Y_ON_X

  /** cumulates all values in an interval */
  public static final int MODE_C = 2;

  /** cumulates all values */
  public static final int MODE_CC = 3;

  /** counts all values and cumulates them*/
  public static final int MODE_CN = 4;

  /** rate per interval, count values and divide by interval length */
  public static final int MODE_R = 5; // MODE_RATE_X

  /** temp container for time values of an interval */
  private transient double[] intervalTime;
  private transient double[] intervalValue;

  /** next pointer into intervalTime and intervalValue */
  private transient int intervalNext;

  /** the length of the interval */
  private double interval;
  private double intervalStart;

  private int mode;

  private double scale;

  /** ctor for hibernate */
  protected AveragedTimeLine() {}

  /**
   * Constructor.
   *
   * @param title Name of the time line.
   * @param interval interval length.
   */
  public AveragedTimeLine(String title, double interval, int mode, double scale) {
    super(title);

    this.interval = interval;
    this.mode = mode;
    this.scale = scale;

    intervalTime = new double[64];
    intervalValue = new double[64];
  }

  public AveragedTimeLine(TimeLine base, double interval, int mode, double scale) {
    this(base.getTitle(), interval, mode, scale);

    double[] x = base.getX();
    double[] y = base.getY();

    for (int i = 0; i < x.length; i++)
      add((long)(x[i]*Constants.SECOND), y[i]);
  }

  public AveragedTimeLine(String title, double interval) {
    this(title, interval, MODE_WA, 1.);
  }

  public int getMode() {
    return mode;
  }

  public double getInterval() {
    return interval;
  }

  public void add(double time, double value) {
    // time must be monotonically increasing
    if (Main.ASSERT)
       Util.assertion(intervalNext == 0
           || time >= this.intervalTime[intervalNext-1]);

    while (intervalNext >= this.intervalTime.length) {
      double[] l = this.intervalTime;
      this.intervalTime = new double[Math.min(l.length*2,l.length + 1000)];
      System.arraycopy(l, 0, this.intervalTime, 0, l.length);

      double[] d = this.intervalValue;
      this.intervalValue = new double[this.intervalTime.length];
      System.arraycopy(d, 0, this.intervalValue, 0, d.length);
    }

    if (0 == intervalNext)
      intervalStart = time - time % interval;

    // do we have a filled interval to write through?
    if (intervalStart + interval  < time) {
      switch (mode) {
      case MODE_WA:
        calcWA(time, value);
        break;
      case MODE_A:
        calcA(time, value);
        break;
      case MODE_WA2:
        calcWA2(time, value);
        break;
      case MODE_C:
        calcC(time, value);
        break;
      case MODE_CC:
        calcCC(time, value);
        break;
      case MODE_CN:
        calcCN(time, value);
        break;
      case MODE_R:
        calcR(time, value);
        break;
      default:
        throw new Error("unknown mode: " + mode);
      }
    }
    else {
      this.intervalTime[intervalNext] = time;
      this.intervalValue[intervalNext] = value;
      intervalNext++;
    }
  }

  private void calcCC(double time, double value) {
    double temp = .0;

    for (int i = 0; i < intervalNext; i++) {
      temp += intervalValue[i];
    }

    super.add(intervalStart, temp * scale);

    // reset the recorded intermediates
    intervalStart = time - time % interval;
    intervalTime[0]   = time;
    intervalValue[0]  = value + temp;
    intervalNext =     1;
  }

  private void calcCN(double time, double value) {
    double tmp = intervalValue[0] + intervalNext;
    super.add(intervalStart, tmp * scale);

    // reset the recorded intermediates
    intervalStart = time - time % interval;
    intervalTime[0]   = time;
    intervalValue[0]  = tmp;
    intervalNext =     1;
  }

  private void calcR(double time, double value) {
    super.add(intervalStart, intervalNext / interval * scale);

    // reset the recorded intermediates
    intervalStart = time - time % interval;
    intervalTime[0]   = time;
    intervalValue[0]  = value;
    intervalNext =     1;
  }

  private void calcA(double time, double value) {
    double temp = .0;
    double count = .0;

    for (int i = 0; i < intervalNext; i++) {
      temp += intervalValue[i];
      count += 1;
    }

    super.add(intervalStart, temp / count * scale);

    // reset the recorded intermediates
    intervalStart = time - time % interval;
    intervalTime[0]   = time;
    intervalValue[0]  = value;
    intervalNext =     1;
  }

  private void calcWA2(double time, double value) {
    double temp = .0;

    for (int i = 0; i < intervalNext; i++) {
      temp += intervalValue[i];
    }

    super.add(intervalStart, temp / interval * scale);

    // reset the recorded intermediates
    intervalStart = time - time % interval;
    intervalTime[0]   = time;
    intervalValue[0]  = value;
    intervalNext =     1;
  }

  private void calcC(double time, double value) {
    double temp = .0;

    for (int i = 0; i < intervalNext; i++) {
      temp += intervalValue[i];
    }

    super.add(intervalStart, temp * scale);

    // reset the recorded intermediates
    intervalStart = time - time % interval;
    intervalTime[0]   = time;
    intervalValue[0]  = value;
    intervalNext =     1;
  }

  private void calcWA(double time, double value) {
    int last = intervalNext-1;

    double temp = (last < 0 ? .0 :
      intervalValue[last] * (intervalStart + interval - intervalTime[last]));

    for (int i = 1; i < last; i++) {
      temp += intervalValue[i-1] * (intervalTime[i] - intervalTime[i-1]);
    }

    super.add(intervalStart, temp / interval * scale);

    double newStart = time - time % interval;

    // is the new point in the next interval? or in the next next?
    if (intervalStart + 2 * interval < time)
      super.add(intervalStart + interval, intervalValue[last]);
    if (intervalStart + 3 * interval < time)
      super.add(newStart - interval, intervalValue[last]);

    // reset the recorded intermediates
    intervalStart = newStart;
    intervalTime[0]   = intervalStart;
    intervalValue[0]  = intervalValue[last];
    intervalTime[1]   = time;
    intervalValue[1]  = value;
    intervalNext =     2;
  }

  public static double aggregate(Line base, int mode, double scale) {
    double[] x = base.getX();
    double[] y = base.getY();
    double interval = x[x.length-1];

    AveragedTimeLine newLine = new AveragedTimeLine(base.getTitle(), interval, mode, scale);
    for (int i = 0; i < x.length; i++)
      newLine.add(x[i], y[i]);
    // trigger aggregation, value is bogus
    newLine.add(x[x.length-1]+1., 0);
    
    return newLine.getYInternal()[0];
  }
}
