/*
* fractaliser.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 "fractaliser.h"
#include "vector2d.h"
#include "ui_list.h"
#include "console.h"
#include <vector>
#include <fstream>
using namespace std;


fractaliser::fractaliser () : seed ("seed.crv"), ed ("seed.ed"), lib ("seed.lib") {
  name = "Fractaliser";
  ed.add (&seed, &lis);
  ed.attach_library (&lib);
  threshold = 0;
  back_ed = 0;
  load_params ();
}

fractaliser::~fractaliser () {
  seed.save ("seed.crv");
  widget_save ("d_fractaliser", ctrls);
  save_params ();
}

void fractaliser::load_params () {
  ifstream f (make_fname().c_str(), ios::in);
  string ignore;
  f >> ignore >> threshold;
}

void fractaliser::save_params () {
  ofstream f (make_fname().c_str(), ios::out);
  string ignore;
  f << "threshold " << float(sp_threshold.f_value) << endl;
}

void fractaliser::setup () {

  plugin::setup ();

  widget* _ctrls [] = {&sp_threshold, &cd_seed, &ab_left, &ab_right, &l_seed, &b_edit, &b_mirror, &sp_scale};
  for (int i = 0; i < 8; ++i) ctrls.push_back (_ctrls[i]);
  num_ctrls = ctrls.size ();

	sp_threshold.set ("Threshold", 0.01f, 0, MILLION, 0, 0);
	sp_threshold.set_value (threshold);

	sp_scale.set ("Scale", 0.01f, -MILLION, MILLION, 0, 0);
	sp_scale.set_value (1.0f);

  l_seed.set_text ("Seed");

  b_edit.set_text ("Edit");
  b_mirror.set_text ("Flip");

  cd_seed.crv = &seed;
  cd_seed.unit_bbox ();

  ab_left.set_dir (arrow_button::left);
  ab_right.set_dir (arrow_button::right);
  ab_left.click_repeat = 1;
  ab_right.click_repeat = 1;

  widget_load ("d_fractaliser", ctrls);

  button* btn [] = {&ab_left, &ab_right, &b_edit, &b_mirror};
  for (int i = 0; i < 4; ++i) btn[i]->set_listener (this);

}

void fractaliser::clicked (button& b) {
  if (&b == &ab_left) {
    ed.win.calc ();
    ed.load_curve (-1);
    render ();
  } else if (&b == &ab_right) {
    ed.win.calc ();
    ed.load_curve (+1);
    render ();
  } else if (&b == &b_edit) {
    if (b_edit.text == "Edit") {
      b_edit.set_text ("Go back");
      back_ed = uis.crved;
      uis.set_current (&ed);
    } else {
      uis.set_current (back_ed);
      b_edit.set_text ("Edit");
    }
  } else if (&b == &b_mirror) {
    ed.get_picked_curve (); // pick curve
		int whole_curve = 1;
    ed.mirror (whole_curve);
  } else plugin::clicked (b);
}

void fractaliser::render () {
  // replace all segments of input curve with seed curve
  //

	curve_editor* ed = uis.crved;
  multi_curve& in = *ed->get_picked_curve (); // apply seed curve on this input curve

  vector<crvpt> in_pts; 
	in.get_profile_points (in_pts); // get curve profile

  int n = in_pts.size (); if (n < 2) return; 
  int m = n - 1;

  points.clear ();
  left_tangents.clear ();
  right_tangents.clear ();

  threshold = sp_threshold.f_value;

  vector<point<float> > sv = seed.vertices;
  vector<point<float> > slt = seed.left_tangents;
  vector<point<float> > srt = seed.right_tangents;
  int q = sv.size () - 1;
	scale_seed_curve (sv, slt, srt, q+1, float(sp_scale.f_value));

  point<float>& rt0 = srt[0];
  point<float>& ltq = slt[q];
  float dx=0, dy=0, pdx=0, pdy=0;
  crvpt vk;
  int subdivided=0;
  for (int i = 0; i < m; ++i) { // replace all segments of input curve with seed curve
		crvpt& vi = in_pts [i];  // ith vertex
		int j = i + 1;
		crvpt& vj = in_pts[j]; // jth vertex
		points.push_back (point<float>(vi.x, vi.y));
		if (i == 0) left_tangents.push_back (point<float>(vi.x, vi.y));
		else {
			if (subdivided) 
				left_tangents.push_back (point<float>(vk.x + ltq.x * dx + ltq.y * pdx, vk.y + ltq.x * dy + ltq.y * pdy));
			else
				left_tangents.push_back (point<float>(vi.x, vi.y));
		}
		dx = vj.x - vi.x; dy = vj.y - vi.y; // find vector joining i and j vertex
		pdx = -dy; pdy = dx; // find perpendiular vector (ie y-axis) to vector joining i and j vertices (ie x-axis)
		double length = magnitude (dx, dy); // find length of this vector
		subdivided = 0;
		if (length >= threshold) { // replace vector joining vertices with seed curve
			subdivided = 1;
			right_tangents.push_back (point<float>(vi.x + rt0.x * dx + rt0.y * pdx, vi.y + rt0.x * dy + rt0.y * pdy)); // = right tangent of seed curve's 1st vertex
			for (int p = 1; p < q; ++p) { // go thru the seed curve and replace
				// get a point of seed curve
				point<float>& pt = sv[p];
				point<float>& plt = slt[p];
				point<float>& prt = srt[p];
				// compute output by going along x-axis and y-axis vectors from input 
				// using x and y amounts from seed curve
				point<float> ov (vi.x + pt.x * dx + pt.y * pdx, vi.y + pt.x * dy + pt.y * pdy);
				point<float> olt (vi.x + plt.x * dx + plt.y * pdx, vi.y + plt.x * dy + plt.y * pdy);
				point<float> ort (vi.x + prt.x * dx + prt.y * pdx, vi.y + prt.x * dy + prt.y * pdy);
				// add vertex, left and right tangent to output curve
				points.push_back (ov); 
				left_tangents.push_back (olt);
				right_tangents.push_back (ort);
			}
		} else right_tangents.push_back (point<float> (vi.x, vi.y)); 		
		vk = vi;
  }

	crvpt& mv = in_pts[m];
  crvpt& mv1 = in_pts[m-1];
	points.push_back (point<float>(mv.x, mv.y));
  if (subdivided) 
    left_tangents.push_back (point<float>(mv1.x + ltq.x * dx + ltq.y * pdx, mv1.y + ltq.x * dy + ltq.y * pdy)); 
  else 
    left_tangents.push_back (point<float>(mv.x, mv.y));
  right_tangents.push_back (point<float>(mv.x, mv.y));

  ss.str("");
  ss << in.name << "_" << seed.name;

}

void fractaliser::draw (curve_editor* ed) {}

void fractaliser::scale_seed_curve (points_array& v, points_array& lt, points_array& rt, int n, float scale) {
	// we only scale in y, because x is normalised from 0 to 1 and cant be changed
	point<float> o; // origin at 0,0
	points_array* pa[3] = {&v, &lt, &rt};
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < 3; ++j) {
			points_array& p = *pa[j];
			point<float>& pi = p[i];
			point<float> dp; direction (dp, o, pi);
			dp.y *= scale;
			pi = o + dp;
		}
	}
}
