import sk.uniba.fmph.pocprak.simplegraphics.*;
import sk.uniba.fmph.pocprak.ioutils.*;
import sk.uniba.fmph.pocprak.genutils.wait;

/**
 * Trieda postavena nad balikom simplegraphics ktora umoznuje zobrazovanie
 * kriviek zavislych na case pre casy dlhsie ako je rozsah vodorovnej osi.
 * V akomsi zmysle sa to podoba na osciloskop, ktory umoznuje kontinualne
 * sledovat casovy priebeh elektrickych signalov
 * Oscilatror je vnutorne vybaveny ring-bufferom ktoreho velkost pokryje 66 percen
 * caosvej osi.
 */
public class Osciloscope {
  GrGraphics gr;
  GrAxisY yaxis;
  double tmax;
  buffer bf;
  public boolean auto = false;
  private int everyn;
  private int counter=0;
  /**
   * Constructor osciloskkopu
   * @param ymin double dolna hranica y-ovej osi
   * @param ymax double horna hranica y-ovej osi
   * @param tmax double dlzka x-ovej osi v casovych jednodtkach
   * @param deltat double casovy krok s ktorym sa budu pridavat hodnoty na zobrazenie
   * do rin gbuffera osciloskopu
   */
  public Osciloscope(double ymin, double ymax, double tmax, double deltat) {
    wait.calibrate();	//calibrate the wait methods for the particular computer
    int N = (int) (0.66 * tmax / deltat);
    bf = new buffer(N);
    this.tmax = tmax;
    grenv(ymin,ymax,tmax);
    nakresliosi();
    gr.repaint();

  }

  void grenv(double ymin, double ymax, double tmax) {


    gr = SimpleGraphics.CreateGrEnvironment();
    gr.setBasePoint(gr.CENTER);
    gr.setUserFrameSize(tmax / 2, (ymax + ymin) / 2, tmax, ymax);
    yaxis = new GrAxisY(ymin, ymax, 0.);
    yaxis.setLTicks(ymin, (ymax - ymin) / 10.);
  }

  void nakresliosi(){
    yaxis.draw(gr);
    gr.drawLine2D(0.,0.,tmax,0.);
  }
  /**
   * Prida do ring-buffera jeden bod (t,y)
   * @param t double
   * @param y double
   */
  public void add(double t, double y){
    bf.add(t,y);
    if (!auto) return;
    counter++;
    if (counter<everyn) return;
    wait.milis(50);
    counter =0;
    gr.clearImage();
    nakresliosi();
    draw();

  }
  /**
   * Vykresli body podla aktualneho stavu ring buffera
   */
  public void draw(){
    bf.reset();
    if(!bf.hasNext()) return;
    double data [] = new double[2];
    double offset;
    data = bf.getNext();
    offset=data[0];
    gr.drawPoint(data[0]-offset,data[1]);
    while (bf.hasNext()){
      data = bf.getNext();
      gr.drawPoint(data[0]-offset,data[1]);
    }
    String time = SPrintF.Double("10.4g",data[0]);
    gr.drawString("t = "+time,gr.userXmax/2,gr.userYmax);
    gr.repaint();
  }
  /**
   * Inicializuje mod automatickeho volania funkcie draw po kazdych
   * everyn pridanych bodoch, teda kazdych everyn*delatat casovych jednotiek,
   * kde deltat je parameter constructora
   * @param everyn int
   */
  public void autodraw(int everyn){
    auto = true;
    this.everyn = everyn;
  }
  /**
   * Funkcia main nie je sucastou API, sluzi len na testovanie a ako priklad pouzitia
   * @param args String[]
   */
  public static void main(String[] args) {
    double deltat = 0.01;
    Killer.createKiller();
    Osciloscope osc = new Osciloscope(-2.,2.,10.,deltat);

    double t = 0;
    osc.autodraw(10);
    while(t<100){
        t=t+deltat;
        double y = Math.sin(t);
        osc.add(t,y);
    }

  }
  /**
   * Privatna trieda realizujuca ring-buffer
   */
  private class buffer{
    double valy[];
    double valx[];
    int N; //depth of the "ring" buffer
    int index;
    int ind0;
    int size;
    buffer(int N){
      this.N = N;
      size = 10*N;
      index = 0;
      ind0=0;
      valx = new double[size];
      valy = new double[size];
    }
    void add(double x,double y){
      if(index<size){
        valy[index]=y;
        valx[index]=x;
        index++;
      }
      else{
        reshuffle();
        add(x,y);//recursion
      }
    }
    void reshuffle(){
      double tmpx [] = valx;
      double tmpy [] = valy;
      valx = new double[size];
      valy = new double[size];
      for(int i = 0;i<N;i++){
        valx[i] = tmpx[index-N+i];
        valy[i] = tmpy[index-N+i];
      }
      index = N-1;
    }
    void reset(){
      ind0 = index-N+1;
      if (ind0<0) ind0=0;
    }
    boolean hasNext(){
      return (index>ind0);
    }
    double[] getNext(){
      double ret[] = new double[2];
      ret[0] = valx[ind0];
      ret[1] = valy[ind0];
      ind0++;
      return ret;
    }
  }
}
