package brn.sim.data;

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

import org.apache.log4j.Logger;

public class DiagramDataHist extends AbstractDiagramData {

  private static final long serialVersionUID = 1L;

  /** logger. */
  public static final Logger log = Logger.getLogger(DiagramDataHist.class.getName());

  public static final int MODE_CUML = 1;

  public static final int MODE_MAX = 2;

  public static final int MODE_MIN = 3;

  /**
   * The underlying collector
   */
  private transient DiagramData data;

  /**
   * Whether to histograph x or y values of the underlying collector
   */
  private boolean useXValues;

  private int mode = MODE_CUML;

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

  /**
   * Create a histogram data collector.
   *
   * @param xLabel   Label of the x axis.
   * @param xUnits   Unit of the x axis.
   * @param yLabel   Label of the y axis.
   * @param yUnits   Unit of the y axis.
   */
  public DiagramDataHist(String[] path,
      String xLabel, String xUnits, String yLabel, String yUnits) {
    super(path, xLabel, xUnits, yLabel, yUnits,
        AbstractDiagramData.MARK);

    this.useXValues = false;
    this.data = null;
  }

  public DiagramDataHist(String[] path, String xLabel, String xUnits,
      String yLabel, String yUnits, int mode) {
    this(path, xLabel, xUnits, yLabel, yUnits);
    this.mode  = mode;
  }

  /**
   * Create a histogram data collector and connect it to a collector.
   *
   * @param xLabel     Label of the x axis.
   * @param xUnits     Unit of the x axis.
   * @param yLabel     Label of the y axis.
   * @param yUnits     Unit of the y axis.
   * @param data       The underlying collector
   * @param useXValues Whether to histograph x or y values of the underlying collector
   */
//  public DiagramDataHist(String[] path,
//      String xLabel, String xUnits, String yLabel, String yUnits,
//      DiagramData data, boolean useXValues) {
//    super(path, xLabel, xUnits, yLabel, yUnits, AbstractDiagramData.MARK);
//
//    this.useXValues = useXValues;
//    this.data = data;
//    this.data.addListener(this);
//  }

//  public DiagramDataHist(String[] path, String xLabel, String xUnits,
//      String yLabel, String yUnits, DiagramData data, boolean useXValues, boolean visible) {
//    this(path[path.length-1], getPath(path), path, xLabel, xUnits,
//        yLabel, yUnits, data, useXValues, visible);
//  }

  /**
   * Put a new data item into the histogram. It is assumed that only one point
   * is added to the base data. If one assert of the below ones fails, the
   * assumption did not hold.
   * <p/>
   * TODO In this case, the whole histogram has to be recalculated.
   *
   * @param source The event object.
   */
  protected void changed(AbstractDiagramData source) {
    int size = getLengthInternal();

    double[] xOther = data.getXInternal();
    double[] yOther = data.getYInternal();
    int sizeOther = data.getLengthInternal();

    // plausi test for the assumption
    assert (size + 1 == sizeOther);

    double xNext = xOther[sizeOther - 1];
    double yNext = yOther[sizeOther - 1];

    this.addNextValue(useXValues ? xNext : yNext);
  }

  // final to allow inlining
  public final void addNextValue(double category) {
    this.addNextValue(category, 1.);
  }
  /**
   * Add a value into the histogram.
   * final to allow inlining
   *
   * @param value The new value to add.
   */
  public final void addNextValue(double category, double value) {
    if (!active)
      return;

    double[] x = getXInternal();
    double[] y = getYInternal();
    int size = getLengthInternal();

    // 1000 categories are a bit much, and they are very expensive in terms of
    // runtime, so simply generate a message and return!
    if (size > 1000)
      return;
    if (size >= 1000)
      log.error("Category overflow! Is this a bug??");

    int idx = -1;
    for (int i = 0; i < size; i++)
      if (x[i] == category) {
        idx = i;
        break;
      }

    if (idx >= 0) {
      switch (mode) {
      case MODE_CUML:
        y[idx] += value;
        break;
      case MODE_MAX:
        y[idx] = (value > y[idx] ? value : y[idx]);
        break;
      case MODE_MIN:
        y[idx] += (value < y[idx] ? value : y[idx]);
        break;
      }
    } else {
      line.add(category, value);
    }

//    if (this.shouldFireChanged)
//      this.fireChanged();
  }

  public void savePlotFile(String fileName, String dataFile, String subTitle)
          throws IOException {
    FileWriter fwriter = new FileWriter(fileName);

    fwriter.write("set terminal png\n");
    fwriter.write("set title '[" + getPath() + "] " + line.getTitle() + "\\n" +
            subTitle + "'\n");
    fwriter.write("set xlabel '" + getXLabel() + " (" +
            getXUnits() + ")" + "'\n");
    fwriter.write("set ylabel '" + getYLabel() + " (" +
            getYUnits() + ")" + "'\n");
    fwriter.write("set boxwidth 0.5\n");
    fwriter.write("plot '" + dataFile + "' with boxes title '" +
            getYLabel() + "' \n");

    fwriter.close();
  }

  public Line generateCdf() {
    return this.generateCdf(this.getTitle() + " (cdf)");
  }

  public Line generateCdf(String title) {
    Line ret = new Line(title);

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

    double[] xnew = x.clone();
    double[] ynew = y.clone();
    Arrays.sort(xnew);
    for (int i = 0; i < y.length; i++) {
      int idx = Arrays.binarySearch(xnew, x[i]);
      ynew[idx] = y[i];
    }
    x = xnew;
    y = ynew;


    double samples = .0;
    for (int i = 0; i < y.length; i++) {
      samples += y[i];
    }

    double fraction = .0;
    for (int i = 0; i < x.length; i++) {
      fraction += y[i];
      ret.add(x[i], fraction/samples);
    }


    return ret;
  }

}
