/*
* multi_curve.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 "multi_curve.h"
#include "vector2d.h"
#include "container.h"
#include "random.h"
#include "log.h"
#include <iostream>

extern string user_data_dir;
static const char eol = '\n';

multi_curve::multi_curve (const string& filename) {
  load (filename);
}

multi_curve::multi_curve () {
  clear ();
}

void multi_curve::clear (int all) {

  if (all) {
    name = "nameless";
    set_color (1, 1, 1);
  }

  vertices.clear ();
	num_vertices = 0;
	last_vertex = -1;

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

  curv.clear ();

  limit = 0.001f;

  shapeform = 0;

	centroid_style = CALC;
	scx = 0.5f;
	scy = 0.0f;
	cx = scx;
	cy = scy;

}

//
// must call following 3 together with add_vertex called first
// to correctly add curve
//
void multi_curve::add_vertex (float x, float y) {
	last_vertex = num_vertices++;
  vertices.push_back (point<float> (x, y));
  if (num_vertices > 1) curv.push_back (curve());
}

void multi_curve::add_left_tangent (float x, float y) {
  left_tangents.push_back (point<float> (x, y));
}

void multi_curve::add_right_tangent (float x, float y) {
  right_tangents.push_back (point<float> (x, y));
}

void multi_curve::get_vertex (int i, float& x, float& y) {
  point<float>& v = vertices[i];
  x = v.x;
  y = v.y;
}

int multi_curve::set_vertex (int i, float x, float y, int carry_tangents) {
  point<float>& v = vertices[i];
  if ((v.x != x) || (v.y != y)) {
    float dlx, dly, drx, dry;
    if (carry_tangents) {
      point<float>& lt = left_tangents[i];
      point<float>& rt = right_tangents[i];
      dlx = lt.x - v.x; dly = lt.y - v.y;
      drx = rt.x - v.x; dry = rt.y - v.y;
      lt.x = x + dlx; lt.y = y + dly;
      rt.x = x + drx; rt.y = y + dry;
    }
    v.x = x;
    v.y = y;
		if ((unsigned int) i < curv.size ()) curv[i].eval_state = curve::EVAL_REQUIRED;
		int i_1 = i - 1; if (i_1 > -1) curv[i_1].eval_state = curve::EVAL_REQUIRED;
    return 1;
  } else return 0;
}

int multi_curve::set_left_tangent (int i, float x, float y) {
  point<float>& lt = left_tangents [i];
  if ((lt.x != x) || (lt.y != y)) {
    lt.x = x;
    lt.y = y;
    int j = i - 1;
		if (j > -1) curv[j].eval_state = curve::EVAL_REQUIRED;
    return 1;
  } 
	return 0;
}

int multi_curve::set_right_tangent (int i, float x, float y) {
  point<float>& rt = right_tangents[i];
  if ((rt.x != x) || (rt.y != y)) {
    rt.x = x;
    rt.y = y;
		if (i < (int) curv.size()) curv[i].eval_state = curve::EVAL_REQUIRED;
    return 1;
  } 
	return 0;
}

void multi_curve::get_left_tangent (int i, float& x, float& y) {
  point<float>& lt = left_tangents [i];
  x = lt.x;
  y = lt.y;
}

void multi_curve::get_right_tangent (int i, float& x, float& y) {
  point<float>& rt = right_tangents[i];
  x = rt.x;
  y = rt.y;
}

int multi_curve::insert (float x, float y, float tx, float ty) {
  points_array::iterator vter = ++vertices.begin (), lter = ++left_tangents.begin (), rter = ++right_tangents.begin ();
  vector<curve>::iterator cter = curv.begin ();
  for (int i = 0, j = last_vertex; i < j; ++i, ++vter, ++lter, ++rter, ++cter) {
    point<float>& vi = vertices[i];
    point<float>& vi1 = vertices[i + 1];
    if ((x >= vi.x) && (x <= vi1.x)) {
      /*float ltx, lty; unit_vector (ltx, lty, x, y, vi.x, vi.y);
      float rtx, rty; unit_vector (rtx, rty, x, y, vi1.x, vi1.y);
			point<float> v (x, y), lt (x + tx * ltx, y + ty * lty), rt (x + tx * rtx, y + ty * rty);*/
      point<float> v (x, y);
      vertices.insert (vter, v);
			last_vertex = num_vertices++;
      left_tangents.insert (lter, v); 
      right_tangents.insert (rter, v);
      /*left_tangents.insert (lter, lt); 
      right_tangents.insert (rter, rt);*/
			(*cter).eval_state = curve::EVAL_REQUIRED;
      curv.insert (cter, curve());
      return 1;
    }
  }
  return 0;
}

int multi_curve::remove (int i) {
  // remove ith vertex and its tangents
  if (num_vertices < 3) return 0;
  erase_id (vertices, i);
  erase_id (left_tangents, i);
  erase_id (right_tangents, i);
  if (i == (int) curv.size ()) {
    int j = i - 1;
    erase_id (curv, j);
  } else {
    erase_id (curv, i);
		if (--i > -1) curv[i].eval_state = curve::EVAL_REQUIRED; else curv[0].eval_state = curve::EVAL_REQUIRED;
  }
	--num_vertices;
	last_vertex = num_vertices - 1;
  return 1;
}

void multi_curve::set_limit (float d) {
  limit = d;
  require_eval ();
}

void multi_curve::require_eval () {
  for (int i = 0, j = curv.size(); i < j; ++i) curv[i].eval_state = curve::EVAL_REQUIRED;
}

void multi_curve::set_color () {
  static const float base = 0.1f;
  static rnd<float> rd (base, 1.0f - base);
  r = rd (); g = rd (); b = rd ();
  calc_tangent_color ();
}

void multi_curve::set_color (float rr, float gg, float bb) {
  r = rr;
  g = gg;
  b = bb;
  calc_tangent_color ();
}

void multi_curve::calc_tangent_color () {
  static const float factor = 0.5;
  rt = factor * r; 
  gt = factor * g;
  bt = factor * b;
}

void multi_curve::evaluate () {
  int nevals = 0;
	int ncrvs = curv.size ();
  for (int i = 0; i < ncrvs; ++i) {
    curve& crv = curv[i];
    if (crv.eval_required ()) {
      int j = i + 1;
      point<float>& v0 = vertices[i];
      point<float>& v1 = vertices[j];
      point<float>& rt0 = right_tangents[i];
      point<float>& lt1 = left_tangents[j];
      crv.vertex (0, v0.x, v0.y);
      crv.vertex (1, v1.x, v1.y);
      crv.tangent (0, rt0.x, rt0.y);
      crv.tangent (1, lt1.x, lt1.y);
      crv.set_limit (limit);
      crv.eval (shapeform);
      ++nevals;
    }
  }

  if (nevals) {
		if (shapeform) {
    	float total_length = 0.0f;
			float now_length = 0.0f; 
    	for (int i = 0; i < ncrvs; ++i) total_length += curv[i].length;
			for (int i = 0; i < ncrvs; ++i) curv[i].normalise_length (now_length, total_length);
			check_shapeform ();
		}
  }
}

void multi_curve::check_shapeform () {
	for (int i = 0, j = 1, k = curv.size () - 1; i < k; ++i, ++j) {
		curve& crvi = curv[i];
		curve& crvj = curv[j];
		vector<crvpt>& dptsi = crvi.dpts;
		vector<crvpt>& dptsj = crvj.dpts;
		int last = dptsi.size () - 1;
		crvpt& dl = dptsi[last];
		crvpt& d0 = dptsj[0];
		d0.x = dl.x;
		d0.y = dl.y;
	}
}

void multi_curve::calc_centroid () {
	if (centroid_style == SET) {
		cx = scx;
		cy = scy;
	} else {
		box<float> b; calc_bbox (b);
		cx = (b.left + b.right) / 2.; 
		cy = (b.bottom + b.top) / 2.;
	}
}

void multi_curve::calc_bbox (box<float>& b) {
  int num_curves = curv.size ();
  if (num_curves) {
    int num_points = curv[0].vpts.size ();
    if (num_points) {
      const vector<crvpt>& vpts = curv[0].vpts;
      b.left = b.right = vpts[0].x;
      b.bottom = b.top = vpts[0].y;
      for (int i = 0; i < num_curves; ++i) {
        const vector <crvpt>& vpts = curv[i].vpts;
        num_points = vpts.size ();
        for (int j = 0; j < num_points; ++j) {
          const crvpt& p = vpts[j];
          if (p.x < b.left) b.left = p.x; else if (p.x > b.right) b.right = p.x;
          if (p.y < b.bottom) b.bottom = p.y; else if (p.y > b.top) b.top = p.y;
        }
      }
    }
  }
	b.calc ();
}


void multi_curve::load (const string& filename) {
  ifstream file ((user_data_dir + filename).c_str (), ios::in);
  if (!file) {
    dlog << "!!! failed to load curve from: " << filename << " !!!" << endl;
    return;
  }
  load (file);
  dlog << "+++ loaded curve from: " << filename << " +++" << endl;
}

void multi_curve::load (ifstream& file) {

  clear ();

  string ignore;

  file >> ignore >> name;

  int nvertices;
  file >> ignore >> nvertices;

  for (int i = 0; i < nvertices; ++i) {

    float x, y;
    file >> ignore >> x >> y;
    add_vertex (x, y);
    file >> ignore >> x >> y;
    add_left_tangent (x, y);
    file >> ignore >> x >> y;
    add_right_tangent (x, y);

  }

  file >> ignore >> limit;

  float ir, ig, ib; file >> ignore >> ir >> ig >> ib;
  set_color (ir, ig, ib);

  file >> ignore >> shapeform;

  point<float>& lt0 = left_tangents[0];
  point<float>& v0 = vertices[0];
  int last = nvertices - 1;
  point<float>& rtl = right_tangents[last];
  point<float>& vl = vertices[last];
  lt0.x = v0.x; lt0.y = v0.y;
  rtl.x = vl.x; rtl.y = vl.y;

  evaluate ();

}

void multi_curve::save (const string& filename) {
  ofstream file ((user_data_dir + filename).c_str (), ios::out);
  if (!file) {
    dlog << "!!! failed writing curve to: " << filename << endl;
    return;
  }
  save (file);
  dlog << "+++ saved curve into: " << filename << " +++" << endl;
}

void multi_curve::save (ofstream& file) {
  string ignore;
	if (name == "") name = "nameless";
  file << "name " << name << eol;
  file << "num_vertices " << num_vertices << eol;
  for (int i = 0; i < num_vertices; ++i) {
    point<float>& v = vertices[i];
    point<float>& lt = left_tangents[i];
    point<float>& rt = right_tangents[i];
    file << "vertex_" << i << spc << v.x << spc << v.y << eol;
    file << "left_tangent_" << i << spc << lt.x << spc << lt.y << eol;
    file << "right_tangent_" << i << spc << rt.x << spc << rt.y << eol;
  }
  file << "limit " << limit << eol;
  file << "color " << r << spc << g << spc << b << eol;
  file << "shapeform " << shapeform << eol;
}

void create_polyline (multi_curve& crv, const points_array& pts) {
  int npts = pts.size ();
  if (npts < 2) return;
  crv.clear (0);
  for (int i = 0; i < npts; ++i) {
    float xi = pts[i].x, yi = pts[i].y;
    crv.add_vertex (xi, yi);
    crv.add_left_tangent (xi, yi);
    crv.add_right_tangent (xi, yi);
  }
  crv.evaluate ();
}

void convert2_polyline (multi_curve& crv) {
  for (int i = 0, j = crv.num_vertices; i < j; ++i) {
    point<float>& v = crv.vertices[i];
    crv.set_left_tangent (i, v.x, v.y);
    crv.set_right_tangent (i, v.x, v.y);
  }
  crv.evaluate ();
}

void convert2_catmull_rom (multi_curve& crv, float tangent_size) {

  int npts = crv.num_vertices;

  if (npts < 2) return;

  int last = npts - 1;
  point<float>& p0 = crv.vertices[0];
  point<float>& p1 = crv.vertices[1];
  point<float>& pl = crv.vertices[last];
  point<float>& pl1 = crv.vertices[last-1];

  // set tangents for 1st vertex
  float dx, dy; direction (dx, dy, p0.x, p0.y, p1.x, p1.y);
  float tx = tangent_size * dx, ty = tangent_size * dy;
  crv.set_left_tangent (0, p0.x - tx, p0.y - ty);
  crv.set_right_tangent (0, p0.x + tx, p0.y + ty);

  // set tangents for last vertex
  direction (dx, dy, pl.x, pl.y, pl1.x, pl1.y);
  tx = tangent_size * dx; ty = tangent_size * dy;
  crv.set_left_tangent (last, pl.x + tx, pl.y + ty);
  crv.set_right_tangent (last, pl.x - tx, pl.y - ty);

  // set left, right tangent for inbetween vertices
  for (int i = 1; i < last; ++i) {
    int l = i - 1, r = i + 1;
    point<float>& pi = crv.vertices[i];
    point<float>& pl = crv.vertices[l];
    point<float>& pr = crv.vertices[r];
    direction (dx, dy, pl.x, pl.y, pr.x, pr.y);
    crv.set_left_tangent (i, pi.x - tangent_size * dx, pi.y - tangent_size * dy);
    crv.set_right_tangent (i, pi.x + tangent_size * dx, pi.y + tangent_size * dy);
  }

  crv.evaluate ();

}

multi_curve* check_list (multi_curve** lst, int n, const string& name) {
  for (int m = 0; m < n; ++m) if (lst[m]->name == name) return lst[m];
  return 0;
}

vector<crvpt>& multi_curve::get_profile_points (int i) {
  if (shapeform) return curv[i].dpts; else return curv[i].vpts;
}

void multi_curve::set_shapeform (int what) {
  shapeform = what;
  require_eval ();
  evaluate ();
}

void multi_curve::rotate (float angle) { // rotate curve about centroid 
  for (int i = 0; i < num_vertices; ++i) {
    point<float>& vi = vertices[i];
    point<float>& lt = left_tangents[i];
    point<float>& rt = right_tangents[i];
    rotate (vi, angle);
    rotate (lt, angle);
    rotate (rt, angle);
  }
  require_eval ();
  evaluate ();
}

void multi_curve::rotate (point<float>& p, float angle) { // rotate point about centroid by angle
  float px = p.x - cx, py = p.y - cy;
  p.x = px * cos (angle) - py * sin (angle) + cx;
  p.y = px * sin (angle) + py * cos (angle) + cy;
}

void multi_curve::scale (float sx, float sy) {
  for (int i = 0; i < num_vertices; ++i) {
    point<float>& vi = vertices[i];
    point<float>& lt = left_tangents[i];
    point<float>& rt = right_tangents[i];
    scale (vi, sx, sy);
    scale (lt, sx, sy); 
    scale (rt, sx, sy); 
  }
  require_eval ();
  evaluate ();
}

void multi_curve::scale (point<float>& p, float sx, float sy) {
  float px = sx * (p.x - cx), py = sy * (p.y - cy);
  p.x = cx + px;
  p.y = cy + py;
}

void multi_curve::get_xy (double d, float& x, float& y) {
  for (int i = 0, j = curv.size (); i < j; ++i) {
    curve& c = curv[i];
    vector<crvpt>& vpts = c.vpts;
    vector<crvpt>& dpts = c.dpts;
    for (int m = 0, n = dpts.size () - 1; m < n; ++m) {
      crvpt& dm = dpts[m];
      crvpt& dn = dpts[m+1];
      crvpt& vm = vpts[m];
      crvpt& vn = vpts[m+1];
      if (d >= dm.x && d <= dn.x) {
        float amt = (d - dm.x) / (dn.x - dm.x);
        x = vm.x + amt * (vn.x - vm.x);
        y = vm.y + amt * (vn.y - vm.y);
        return;
      }
    }
  }
}

int multi_curve::get_total_points () {
	int total = 0;
  for (int i = 0, j = curv.size(); i < j; ++i) {
    vector<crvpt>& vpts = curv[i].vpts; 
		total = total + vpts.size () - 1;
	}
	total += 1;
	return total;
}

void multi_curve::get_profile_points (vector<crvpt>& profile) {
	profile.clear ();
  for (int i = 0, j = curv.size(), k = j - 1; i < j; ++i) {
    vector<crvpt>& vpts = curv[i].vpts; 
		int q = vpts.size() - 1; 
		if (i == k) ++q;
		for (int p = 0; p < q; ++p) profile.push_back (vpts[p]);
  }
}

extern void make_good_name (string& name);
void multi_curve::set_name (const string& _name) {
	name = _name;
	make_good_name (name);
}
