/*
* oscilloscope.cc
* DIN Is Noise is copyright (c) 2006-2024 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/

#include "dingl.h"
#include "oscilloscope.h"
#include "font.h"
#include "viewwin.h"
#include "input.h"
#include "log.h"
#include "console.h"
#include <string>
#include <algorithm>
using namespace std;

extern string user_data_dir;
extern int CURRENT_INSTRUMENT;
extern const int NUM_INSTRUMENTS;
extern int lmb;
extern int mousex, mouseyy;
extern viewport view;
extern int line_height;

oscilloscope::oscilloscope (const string& _settingsf) : win (0, 0, 2048, 250) {
  samples = 0;
  vertices = 0;
  colors = 0;
	settingsf = _settingsf;
  fold.set_listener (this);
  load ();
	px = py = -1;
	lmb_clicked = move = stop_move = 0;
	sample_t::lmin = sample_t::lmax = sample_t::rmin = sample_t::rmax = 0; // ok for oscilloscope.
  sprintf (lbuf1, "min %2.2f, max %2.2f", sample_t::lmin, sample_t::lmax);
  sprintf (rbuf1, "min %2.2f, max %2.2f", sample_t::rmin, sample_t::rmax);
	lr = 0; lg = lb = 1.0f;
	rr = rg = 1.0; rb = 0.0f;
  opacity = 1.0f;
}

oscilloscope::~oscilloscope () {
  save ();
  delete[] samples;
  delete[] vertices;
  delete[] colors;
}

void oscilloscope::alloc (int n) {
  max_samples = n;
  if (samples) delete[] samples;
  if (vertices) delete[] vertices;
  if (colors) delete[] colors;
  samples = new sample_t [max_samples];
  vertices = new int [8 * max_samples];
  colors = new float [16 * max_samples];
  set_num_samples (num_samples);
}

void oscilloscope::set_num_samples (int n) {
  num_samples = min (n, max_samples);
  startsamp =  max_samples - num_samples;
  calc_draw_params ();
}

void oscilloscope::calc_draw_params () {
  base = win.midy;
  leftx = win.left;
  rightx = leftx + num_samples;
  endx = rightx + num_samples - 1;
  ndraw = 4 * num_samples;
  win (leftx, base - height, endx, base + height);
  int dheight = 5;
  pick_win (win.left, base - dheight, win.right, base + dheight);
  lh = line_height;
  lly = base - lh;
  lry = lly;
  fold.set_pos (leftx - 1.5 * fold.size, base - fold.size);
}

int oscilloscope::load () {
  ifstream file ((user_data_dir + settingsf).c_str(), ios::in);
  if (!file) return 0;
  string ignore;
  for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
    file >> ignore >> left;
    file >> ignore >> base;
    file >> ignore >> height; 
    file >> ignore >> num_samples;
    file >> ignore >> opacity;
    file >> ignore >> visible;
    file >> ignore >> folded;
    params.push_back (oscilloscope_params_t (left, base, height, num_samples, opacity, visible, folded));
  }
  return 1;
}

int oscilloscope::save () {
  ofstream file ((user_data_dir + settingsf).c_str(), ios::out);
  if (!file) return false;
  for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
    oscilloscope_params_t& op = params[i];
    file << "left " << op.left << endl;
    file << "base " << op.base << endl;
    file << "height " << op.height << endl;
    file << "num_samples " << op.num_samples << endl;
    file << "opacity " << op.opacity << endl;
    file << "visible " << op.visible << endl;
    file << "folded " << op.folded << endl;
  }
  return 1;
}

void oscilloscope::load_current_instrument () {
  oscilloscope_params_t& op = params[CURRENT_INSTRUMENT];
  left = op.left;
  base = op.base;
  height = op.height;
  num_samples = op.num_samples;
  opacity = op.opacity;
  visible = op.visible;
  folded = op.folded;
  win (left, base - height, left + 2 * num_samples, base + height);
  set_folded (folded);
  set_num_samples (num_samples); // calls calc_draw_params
}

void oscilloscope::save_current_instrument () {
  oscilloscope_params_t& op = params[CURRENT_INSTRUMENT];
  op.left = win.left;
  op.base = base;
  op.height = height;
  op.num_samples = num_samples;
  op.opacity = opacity;
  op.visible = visible;
  op.folded = folded;
}

void oscilloscope::set_height (int h) {
  height = h;
  calc_draw_params ();
}

int oscilloscope::handle_input () {

  int result = fold.handle_input ();
  if (result) return result;

	int x = mousex;
	int y = mouseyy;

  if (is_lmb (this)) {
    if (lmb_clicked == 0) {
			lmb_clicked = 1;
			if (move) {
				move = 0;
				stop_move = 1;
			} else if (inbox (pick_win, x, y)) { // pick_win is small window along zero line of oscilloscope
        move = 1;
				is_lmb.tie = this;
      }
		} 
  } else {
		if (move) {
			int dx = x - px, dy = y - py;
			win.move (dx, dy);
			calc_draw_params ();
		} else if (stop_move) {
			stop_move = 0;
			is_lmb.clear (this);
		}
		lmb_clicked = 0;
  }

  px = x;
  py = y;
	result = move;
  return result;
}

void oscilloscope::add_samples (float* outl, float* outr, int n) {

  for (int i = 0; i < n; ++i) {
    sample_t& sa = samples [i];
    sa.left = outl[i]; sa.right = outr[i];
    sample_t::lmin = min (sa.left, sample_t::lmin);
    sample_t::lmax = max (sa.left, sample_t::lmax);
    sample_t::rmin = min (sa.right, sample_t::rmin);
    sample_t::rmax = max (sa.right, sample_t::rmax);
  }

  // used by mondrian
  float lm = sample_t::lmin * sample_t::rmin, rm = sample_t::lmax * sample_t::rmax;
  limit = (lm > 1 || rm > 1);

  {
    char lb = '.', rb = '.';
    float lsum = sample_t::lmin * sample_t::lmin + sample_t::lmax * sample_t::lmax;
    float rsum = sample_t::rmin * sample_t::rmin + sample_t::rmax * sample_t::rmax;
    if (lsum > rsum) lb = '@'; else if (rsum > lsum) rb = '@';
    sprintf (lbuf1, "min %2.2f, max %2.2f %c", sample_t::lmin, sample_t::lmax, lb);
    sprintf (rbuf1, "min %2.2f, max %2.2f %c", sample_t::rmin, sample_t::rmax, rb);
    sample_t::lmin = sample_t::lmax = sample_t::rmin = sample_t::rmax = 0;
  }

  if (folded == 0) {

    int x1 = leftx, x2 = rightx, y0 = base;
    for (int i = 0, j = startsamp, vi = 0, ci = 0; i < num_samples; ++i, ++j, vi += 8, ci += 16) {

      float l = samples[j].left;
      float r = samples[j].right;

      int ly = int (l * win.height);
      int ry = int (r * win.height);

      // distortion is red
      //
      float l2 = l * l;
      float lrr = lr, lgg = lg, lbb = lb; 
      if (l2 > 1.0f) {lrr = 1; lgg = 0; lbb = 0;}

      float rrr = rr, rgg = rg, rbb = rb; 
      float r2 = r * r;
      if (r2 > 1.0f) {rrr = 1; rgg = 0; rbb = 0;}

      vertices [vi] = x1;
      vertices [vi+1] = y0;
      vertices [vi+2] = x1;
      vertices [vi+3] = y0 + ly;
      vertices [vi+4] = x2;
      vertices [vi+5] = y0;
      vertices [vi+6] = x2;
      vertices [vi+7] = y0 + ry;

      colors [ci] = colors[ci+4] = lrr;
      colors [ci+1] = colors[ci+5] = lgg;
      colors [ci+2] = colors[ci+6] = lbb;
      colors [ci+3] = colors[ci+7] = opacity;
      colors [ci+8] = colors[ci+12] = rrr;
      colors [ci+9] = colors[ci+13] = rgg;
      colors [ci+10] = colors[ci+14] = rbb;
      colors [ci+11] = colors[ci+15] = opacity;

      ++x1; ++x2;

    }

  }

}

void oscilloscope::draw () {
  
  if (folded == 0) {
    // draw samples
    //
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glEnableClientState (GL_COLOR_ARRAY);
		glColorPointer (4, GL_FLOAT, 0, colors);
		glVertexPointer (2, GL_INT, 0, vertices);
    glDrawArrays (GL_LINES, 0, ndraw);
    glDisableClientState (GL_COLOR_ARRAY);
    glDisable (GL_BLEND);

  }

  // draw L and R labels 
  //

  glColor3f (lr , lg, lb);
  draw_string (lbuf1, leftx, lly, 0);
  glBegin (GL_LINES);
    glVertex2i (leftx, win.midy);
    glVertex2i (rightx, win.midy);
  glEnd ();

	glColor3f (rr, rg, rb);
	draw_string (rbuf1, rightx, lry, 0);
  glBegin (GL_LINES);
    glVertex2i (rightx, win.midy);
    glVertex2i (endx, win.midy);
  glEnd ();

  fold.draw ();

}

void oscilloscope::set_folded (int f) {
  folded = f;
  if (folded) {
    fold.set_dir (arrow_button::right); 
  } else {
    fold.set_dir (arrow_button::up); 
  }
}

void oscilloscope::clicked (button& b) {
  set_folded (!folded);
}

oscilloscope_params_t::oscilloscope_params_t (int l, int b, int h, int ns, float op, int v, int f) {
  left = l;
  base = b;
  height = h;
  num_samples = ns;
  opacity = op;
  visible = v;
  folded = f;
}
