/*
* sine_mixer.cc
* DIN Is Noise is copyright (c) 2006-2022 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 "input.h"
#include "viewwin.h"
#include "sine_mixer.h"
#include "ui_list.h"
#include "viewwin.h"
#include "vector2d.h"
#include "circler.h"
#include "log.h"
#include "console.h"
#include "container.h"
#include <math.h>
using namespace std;

extern const float PI;
extern const float TWO_PI;
extern curve_library sin_lib;
extern int line_height;
extern const char SPC;

sine_mixer::sine_mixer () : cp_sin ("sine_mixer_sin.crv"), sin_ed ("sine_mixer_sin.ed"), harms ("harmonics"), phase ("phases") {
  plugin::name = "Sine_Mixer";
  shapeform = 0;
  type = 0;
  sin_ed.add (&cp_sin.crv, this);
  sin_ed.attach_library (&sin_lib);
  harms.paintl = this;
  harms.chgl = this;
  harms.shftl = this;
  harms.szr.movlis = &szm;
  phase.paintl = this;
  phase.chgl = this;
  phase.shftl = this;
  phase.szr.movlis = &szm;
  szm.data = this;

  static const float fac = 0.6;
  phase.set_color (1.0f - fac * harms.clr.r, 1.0f - fac * harms.clr.g, 1.0f - fac * harms.clr.b);
  paintphase = 0;
}

void sine_mixer::load_params () {
  ifstream f (make_fname().c_str(), ios::in);
  string ignore;
  f >> ignore >> num_points >> ignore >> make_shapeform >> ignore >> type >> ignore >> auto_apply;
	NUM_SINE_SAMPLES = num_points + 1;
}

void sine_mixer::save_params () {
  ofstream f (make_fname().c_str(), ios::out);
	string ignore;
	f << "num_points " << int (sp_points.f_value) << " make_shapeform " << cb_make_shapeform.state << " type " << type \
  << " auto_apply " << cb_auto_apply.state << endl;
}

sine_mixer::~sine_mixer () {
	save_params ();
  widget_save ("d_sine_mixer", ctrls);
  dlog << "--- destroyed sine mixer ---" << endl;
}

void sine_mixer::num_harmonics (int h) {
  nharmonics = h;
  if (nharmonics < 1) nharmonics = 1;
  prep_harmonics ();
}

void sine_mixer::prep_harmonics () {
	harmonics.resize (nharmonics);
	float df = TWO_PI / (NUM_SINE_SAMPLES - 1);
  funktion& f_sin = *pf_sin;
  for (int i = 0, j = 1; i < nharmonics; ++i, ++j) {
    vector< point<float> >& harmonic = harmonics[i];
		harmonic.resize (NUM_SINE_SAMPLES);
    float dx = j * df; 
    float x = phase.values[i] * TWO_PI;
    for (int p = 0; p < NUM_SINE_SAMPLES; ++p) {
			point<float>& hp = harmonic[p];
			hp.x = cos(x);
			hp.y = f_sin (x, TWO_PI);
      x += dx;
      if (x > TWO_PI) x -= TWO_PI;
    }
  }
	norm.resize (NUM_SINE_SAMPLES);
  mix ();
}

void sine_mixer::mix () {
	for (int p = 0; p < NUM_SINE_SAMPLES; ++p) { point<float>& pi = norm[p]; pi.x = pi.y = 0;}
	ss.str(""); ss << plugin::name;
  for (int i = 0; i < nharmonics; ++i) {
    float lev = harms.values[i];
    if (lev > 0) {
      vector< point<float> >& harmonic = harmonics[i];
      for (int p = 0; p < NUM_SINE_SAMPLES; ++p) {
				point<float>& np = norm[p];
				point<float>& hp = harmonic[p];
				np.x += (lev * hp.x);
				np.y += (lev * hp.y);
    	}
			ss << '_' << (i+1);
  	}
	}
  normalise ();
}

void sine_mixer::normalise () {
	int n = NUM_SINE_SAMPLES;
  if (n) {
		point<float>& np0 = norm[0];
		float maxy = fabs (np0.y), maxx = fabs (np0.x);
    for (int i = 0; i < n; ++i) {
			point<float>& pi = norm[i];
			float vx = fabs (pi.x), vy = fabs (pi.y);
			if (vx > maxx) maxx = vx;
			if (vy > maxy) maxy = vy;
    }
    if (maxx != 0) for (int p = 0; p < n; ++p) norm[p].x /= maxx;
		if (maxy != 0) for (int p = 0; p < n; ++p) norm[p].y /= maxy;
  }
}

void sine_mixer::render () {

	undo = !cb_auto_apply.state;
	shapeform = cb_make_shapeform.state;

	int ns = norm.size ();
	points.resize (ns);
	if (shapeform) {
  	for (int i = 0; i < ns; ++i) {
    	point<float>& ni = norm[i];
			point<float>& pi = points[i];
			pi.x = ni.x;
			pi.y = ni.y;
  	}
	} else {
		float x = 0;
		float dx = 1.0f / (NUM_SINE_SAMPLES - 1);
  	for (int i = 0; i < ns; ++i) {
    	point<float>& ni = norm[i];
			point<float>& pi = points[i];
			pi.x = x;
			pi.y = ni.y;
			x += dx;
  	}
	}

	gen_pts ();

}

void sine_mixer::setup () {

  plugin::setup ();

  widget* _ctrls [] = {
    &sp_points, 
    &cb_make_shapeform, 
    &ol_sin, 
    &b_edit,
    &cbpaint,
    &olpaint,
  };

	num_ctrls = 8;

	ctrls.resize (num_ctrls);
  for (int i = 0, j = 2; j < num_ctrls; ++i, ++j) ctrls[j] = _ctrls[i];

  load_params ();

	sp_points.set ("Points", 1, 1, MILLION, this, 0);
	sp_points.set_value (num_points);

	cb_make_shapeform.set_text ("Make shapeform");
	cb_make_shapeform.set_state (make_shapeform);
	cb_make_shapeform.set_listener (this);
	cbpaint.set_text ("Paint");

  levs[0] = &harms;
  levs[1] = &phase;
  LISTEN (cbpaint, this)
  pail.data = this;
  LISTEN (olpaint, &pail)

	if (auto_apply) {
		cb_auto_apply.set_state (1, 0);
		plugin::mix = undo = 0;
	}

  ol_sin.set_listener (this);

  widget_load ("d_sine_mixer", ctrls);

  ctrls.push_back (&harms);
  olpaint.set_text (" Harmonics");

  num_ctrls = 9;

  //for (int i = 0; i < num_ctrls; ++i) ctrls[i]->set_moveable(1);

  b_edit.set_text ("Edit");

  set_type ();
  num_harmonics (harms.n);
  render ();

}

void sine_mixer::set_type () {
  if (type) {
    pf_sin = &cp_sin;
    ol_sin.option.set_text (" Custom sin");
    b_edit.set_color (clr.r, clr.g, clr.b);
    b_edit.set_listener (this);
  } else {
    pf_sin = &st_sin;
    ol_sin.option.set_text (" Standard sin");
    b_edit.set_color (0.25f, 0.25f, 0.25f);
    b_edit.set_listener (0);
  }
  int spacer = 10; b_edit.set_pos (ol_sin.option.extents.right + spacer, ol_sin.option.extents.bottom);
}

void sine_mixer::changed (field& f) { 
	if (&f == &sp_points.f_value) {
		set_samples (f);
		do_render ();
	}
}

void sine_mixer::changed (levels& l) {
  if (&l == &phase) prep_harmonics ();
	mix ();
	do_render ();
}

void sine_mixer::changed (checkbutton& cb) {
	if (&cb == &cb_make_shapeform) {
		render ();
		apply_not_auto_apply ();
	} else if (&cb == &cbpaint) {
    levs[paintphase]->paint = cbpaint.state;
	} else plugin::changed (cb);
}

void sine_mixer::set_samples (int s) {
  NUM_SINE_SAMPLES = s + 1;
  if (NUM_SINE_SAMPLES < MIN_SINE_SAMPLES) NUM_SINE_SAMPLES = MIN_SINE_SAMPLES;
  prep_harmonics ();
}

void sine_mixer::apply_not_auto_apply () {
	if (cb_auto_apply.state) {
		plugin::mix = undo = 1;
		uis.crved->apply_plugin (this);
		plugin::mix = undo = 0;
	} else uis.crved->apply_plugin (this);
}

void sine_mixer::picked (label& lbl, int dir) {
  type = !type;
  set_type ();
  prep_harmonics ();
  render ();
}

void sine_mixer::shift_apply () {
	mix ();
	render ();
	apply_not_auto_apply ();
}

void sine_mixer::clicked (button& b) {
	if (&b == &b_edit) uis.set_current (&sin_ed); 
  else plugin::clicked (b);
}

void sine_mixer::edited (curve_editor* e, int i) {
  cp_sin.sol.update ();
  curve_listener::edited (e, i);
  prep_harmonics ();
  render ();
}


void sine_mixer::moused (int dx, double scl) {
	if (dx > 0) harms.rshift (); else harms.lshift ();
	shift_apply ();
}

void sine_mixer::paint (levels& l) {
  cbpaint.set_state (levs[paintphase]->paint, 0);
}

void sine_mixer::shifted (levels& l) {
  if (&l == &phase) prep_harmonics ();
  shift_apply ();
}

void sine_mixer::toggpaint (label& l) {
  static const char* pp [] = {" Harmonics", " Phase"};
  paintphase = !paintphase;
  ctrls.pop_back ();
  if (paintphase) {
    uis.plugin__browser.l_title.swapchild (&harms, &phase);
    ctrls.push_back (&phase);
    phase.setpos2 (harms);
  }
  else {
    uis.plugin__browser.l_title.swapchild (&phase, &harms); 
    ctrls.push_back (&harms);
    harms.setpos2 (phase);
  }

  l.set_text (pp[paintphase]);

  levels* lp = levs[paintphase];
  olpaint.option.set_color (lp->clr.r, lp->clr.g, lp->clr.b);
  cbpaint.set_state (lp->paint,0);

}

int sine_mixer::handle_input () {
  int r = plugin::handle_input ();
  if (!_folded) {
    if (keypressed (SDLK_g)) {
      pressed.g = 1;
      toggpaint (olpaint.option);
      r |= 1;
    }
  }
  return r;
}

PICKED_OPTION (sine_mixer, paintlis) {
  sine_mixer& sm = (* (sine_mixer*) data);
  sm.toggpaint (l);
}

MOVED (sine_mixer, szrmov) {
  sine_mixer& sm = (* (sine_mixer*) data);
  levels* pla = sm.levs[sm.paintphase]; 
  pla->moved ();
  levels* plb = sm.levs[!sm.paintphase];
  plb->extents = pla->extents;
  plb->szr.setpos2 (pla->szr);
  plb->moved ();
}
