/**
 * 
 */
package com.allendowney.thinkdast;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;

import org.apache.commons.math3.stat.regression.SimpleRegression;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

/**
 * @author downey
 *
 */
public class Profiler extends ApplicationFrame {

   /**
    * Ten fragment kodu jest potrzeby, ponieważ rozszerzenie ApplicationFrame go wymaga.
    */
   private static final long serialVersionUID = 1L;

   /**
    * Interfejs Timeable definuje metody, które musi zapewnić obiekt, aby móc współpracować z klasą Profiler.
    *
    */
   public interface Timeable {
      /*
       * Metoda setup jest wywoływana przed uruchomieniem zegara.
       */
      public void setup(int n);

      /*
       * Metoda timeMe wykonuje operację, której czas działania mierzymy.
       */
      public void timeMe(int n);
   }

   private Timeable timeable;

   public Profiler(String title, Timeable timeable) {
      super(title);
      this.timeable = timeable;
   }

   /**
    * Wywołuje metodę timeIt w zakresie 'n' od 'startN' aż do chwili, gdy czas wykonania przekroczy 'endMillis'.
    *
    * @param data.timeable
    * @param n
    * @return
    */
   public XYSeries timingLoop(int startN, int endMillis) {
        final XYSeries series = new XYSeries("Czas (ms)");

      int n = startN;
      for (int i=0; i<20; i++) {
         // uruchom metodę raz na rozgrzewkę
         timeIt(n);

         // następnie zacznij mierzyć czas
         long total = 0;

         // uruchom 10 razy i zsumuj całowity czas wykonania
         for (int j=0; j<10; j++) {
            total += timeIt(n);
         }
         System.out.println(n + ", " + total);

         // nie zapisuj danych dopóki nie osiągniemy 4 ms
         if (total > 4) {
            series.add(n, total);
         }

         // zakończ, gdy czas wykonania przekroczy próg końca
         if (total > endMillis) {
            break;
         }
         // w przeciwnym razie dwukrotnie zwiększ rozmiar i kontynuuj
         n *= 2;
      }
      return series;
   }

   /**
    * Wywołuje metody setup i timeMe na rzecz osadzonego obiektu klasy Timeable.
    *
    * @param n
    * @return
    */
   public long timeIt(int n) {
      timeable.setup(n);
      final long startTime = System.currentTimeMillis();
      timeable.timeMe(n);
      final long endTime = System.currentTimeMillis();
      return endTime - startTime;
   }

   /**
    * Wykreśla wyniki.
    *
    * @param series
    */
   public void plotResults(XYSeries series) {
      double slope = estimateSlope(series);
      System.out.println("Szacowane nachylenie= " + slope);

      final XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series);

        final JFreeChart chart = ChartFactory.createXYLineChart(
            "",          // tytuł wykresu
            "",               // etykieta osi odciętych
            "",                  // etykieta osi rzędnych
            dataset,                  // dane
            PlotOrientation.VERTICAL,
            false,                     // uwzględnij legendę
            true,
            false
        );

        final XYPlot plot = chart.getXYPlot();
        final NumberAxis domainAxis = new LogarithmicAxis("Wielkość problemu (n)");
        final NumberAxis rangeAxis = new LogarithmicAxis("Czas wykonania (ms)");
        plot.setDomainAxis(domainAxis);
        plot.setRangeAxis(rangeAxis);
        chart.setBackgroundPaint(Color.white);
        chart.setBackgroundPaint(Color.white);
        plot.setOutlinePaint(Color.black);
        final ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(1000, 600));
        setContentPane(chartPanel);
        pack();
        RefineryUtilities.centerFrameOnScreen(this);
        
        // dostosuj wygląd wykresu
        plot.setBackgroundPaint(Color.white);
        int seriesCount = plot.getSeriesCount();

        for (int i = 0; i < seriesCount; i++) {
         plot.getRenderer().setSeriesStroke(i, new BasicStroke(3));
        }
        Font font24 = new Font("Dialog", Font.PLAIN, 24); 
        plot.getDomainAxis().setLabelFont(font24);
        plot.getRangeAxis().setLabelFont(font24);
        Font font20 = new Font("Dialog", Font.PLAIN, 20); 
        plot.getDomainAxis().setTickLabelFont(font20);
        plot.getRangeAxis().setTickLabelFont(font20);
        
        setVisible(true);
   }

   /**
    * Wykorzystuje prostą regresję w celu oszacowania nachylenia wykresu.
    *
    * @param series
    * @return
    */
   public double estimateSlope(XYSeries series) {
      SimpleRegression regression = new SimpleRegression();

      for (Object item: series.getItems()) {
         XYDataItem xy = (XYDataItem) item;
         regression.addData(Math.log(xy.getXValue()), Math.log(xy.getYValue()));
      }
      return regression.getSlope();
   }
}