/*
* solver.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 "solver.h"
#include "multi_curve.h"
#include "curve_editor.h"
#include <cmath>
#include "console.h"
#include "utils.h"
using namespace std;

void solver::init () {
  mcrv = 0;
  icurv = ncurvs = 0;
  iseg = 0;
  firstx = firsty = 0;
  lastx = lasty = 0;
  startx = starty = 0;
  endx = endy = 0;
  pinx = 0;
	ycomp = 0;
  result = 0;
}

solver::solver () {
  init ();
}

solver::solver (multi_curve* c) {
  init ();
  operator()(c);
}

void solver::operator() (multi_curve* c) {
  mcrv = c;
  update ();
}

void solver::update () {

  // curve valid?
  vector<curve>& curv = mcrv->curv;
  ncurvs = curv.size ();
	last_curv = ncurvs - 1;
  if (icurv > last_curv) icurv = 0;

  // segment valid?
  vector<crvpt>& vpts = mcrv->get_profile_points (icurv);
  int nsegs = vpts.size() - 1;

	if (iseg >= nsegs) iseg = 0;

  setseg (icurv, iseg);

  // first point
  vector<crvpt>& fpts = mcrv->get_profile_points (0);
  firstx = fpts[0].x;
  firsty = fpts[0].y;
  pinx = firstx;

  // last point
  vector<crvpt>& lpts = mcrv->get_profile_points (last_curv);
  int last_point = lpts.size () - 1;
  lastx = lpts[last_point].x;
	deltax = lastx - firstx;
	if (equals (deltax, 0.0f)) deltax = 0.000001f;
  lasty = lpts[last_point].y;

}

// solves y for x
float solver::operator() (float x) {
	float y = 0;
  if (x < startx) {
    if (!searchleft (x)) {
			y = firsty; 
			return y;
		}
  } else if (x > endx) {
    if (!searchright (x)) {
			y = lasty; 
			return y;
		}
  }
	y = ycomp + m * x;
  return y;
}

void solver::check (float& x, float& dx, xhandler& xmin, xhandler& xmax) {
  if (x < startx) {
    if (!searchleft (x)) xmin (*this, x, dx);
  } else if (x > endx) {
    if (!searchright (x)) xmax (*this, x, dx);
  }
}

float solver::operator() (float& x, float& dx, xhandler& xmin, xhandler& xmax) {
	check (x, dx, xmin, xmax);
  float y = ycomp + m * x;
  x += dx;
  return y;
}

// solves y for x with modulation of x
void solver::operator() (float& x, float& dx, int q, float* mod, float* y, xhandler& xmin, xhandler& xmax) {
  for (int p = 0; p < q; ++p) {
    x += (mod[p] + dx);
		check (x, dx, xmin, xmax);
    y[p] = ycomp + m * x;
  }
}

void solver::operator() (float& x, float* pdx, int q, float* mod, float* y, xhandler& xmin, xhandler& xmax) {
  float dx;
  for (int p = 0; p < q; ++p) {
    dx = pdx[p];
    x += (mod[p] + dx);
		check (x, dx, xmin, xmax);
    y[p] = ycomp + m * x;
  }
}

// solves y for x, constant dx
void solver::operator() (float& x, float& dx, int q, float* y, xhandler& xmin, xhandler& xmax) {
  for (int p = 0; p < q; ++p) {
    x += dx;
		check (x, dx, xmin, xmax);
    y[p] = ycomp + m * x;
  }
}

// solves y for x, array of dx
void solver::operator() (float& x, float* pdx, int q, float* y, xhandler& xmin, xhandler& xmax) {
  float dx;
  for (int p = 0; p < q; ++p) {
    dx = pdx[p];
    x += dx;
		check (x, dx, xmin, xmax);
    y[p] = ycomp + m * x;
  }
}

// solves array of x. stores solution ie y in the same array at corresponding location
void solver::operator() (float* ax, int q, xhandler& xmin, xhandler& xmax) {
  float x = 0, dx = 0;
  for (int p = 0; p < q; ++p) {
    x = ax[p];
		check (x, dx, xmin, xmax);
    ax[p] = ycomp + m * x;
  }
}

int solver::seg_lte_right (float x, int c, int i) {
  std::vector<crvpt>& pts = mcrv->get_profile_points (c); 
  float rightx = pts[i+1].x;
  if (rightx < pinx) {
    float dpin = pinx - rightx;
    rightx = pinx + dpin;
  }
  float xd = x;
  if (xd <= rightx) {
    setseg (c, i);
    return 1;
  }
  return 0;
}

int solver::searchright (float x) {
  int rseg = iseg, rcurv = icurv;
  std::vector<crvpt>& pts = mcrv->get_profile_points (rcurv); 
  pinx = pts[rseg].x;
  int rsegs = numsegs (rcurv);
  while (1) {
    if (++rseg < rsegs); else {
      ++rcurv;
      if (rcurv >= ncurvs) break;
      rsegs = numsegs (rcurv);
      rseg = 0;
    }
    if (seg_lte_right (x, rcurv, rseg)) return 1;
  }
  return 0;
}

int solver::seg_gte_left (float x, int c, int i) {
  std::vector<crvpt>& pts = mcrv->get_profile_points (c); 
  float leftx = pts[i].x;
  if (leftx > pinx) {
    float dpin = pinx - leftx;
    leftx = pinx + dpin;
  }
  float xd = x;
  if (xd >= leftx) {
    setseg (c, i);
    return 1;
  }
  return 0;
}

int solver::searchleft (float x) {
  int lseg = iseg, lcurv = icurv;
  std::vector<crvpt>& pts = mcrv->get_profile_points (lcurv); 
  pinx = pts[lseg].x;
  while (1) {
    if (--lseg < 0) {
      --lcurv;
      if (lcurv < 0) break;
      lseg = lastseg (lcurv);
    }
    if (seg_gte_left (x, lcurv, lseg)) return 1;
  }
  return 0;
}


int solver::findseg (float x) {
  int fcurv = 0, fseg = 0, nsegs = numsegs (fcurv);
  pinx = firstx;
  while (1) {
    if (seg_lte_right (x, fcurv, fseg)) 
      return 1; 
    else {
      ++fseg;
      if (fseg >= nsegs) {
        if (++fcurv >= ncurvs) { 
          // bad news
          // setseg (ncurvs - 1, nsegs - 1);
          // setseg (0, 0);
          ycomp = 0;
          m = 0;
          break;
        }
        fseg = 0;
        nsegs = numsegs (fcurv);
      }
    }
  }
  return 0;
}

void solver::setseg (int c, int i) {
  std::vector<crvpt>& vpts = mcrv->get_profile_points (c);
  crvpt& start = vpts[i];
  crvpt& end = vpts[i+1];
  startx = start.x;
  starty = start.y;
  endx = end.x;
  endy = end.y;
  m = start.m;
  inf = start.inf;
  if (inf == 1) ycomp = endy; else ycomp = starty - m * startx;
  icurv = c;
  iseg = i;
}

gotog::gotog (float gg, curve_editor* e) : ed (e) {
	set (gg);
}

void gotog::set (float gg) {
	g = gg;
	ed->update_sustain (g);
}

void gotog::operator () (solver& s, float& x, float& dx) {
  x = g;
  if (!s.findseg (x)) {
    x = s.lastx;
		set (x);
  }
}

void tomax::operator() (solver& s, float& x, float& dx) {
	float xd = s.firstx - x;
	float d = xd / s.deltax;
	int i = d;
	x = s.lastx - d + i;
	s.findseg (x);
}

void atmin::operator() (solver& s, float& x, float& dx) {
  x = s.firstx;
  s.setseg (0, 0);
}

void atmax::operator () (solver& s, float& x, float& dx) {
  x = s.lastx;
  s.setseg (s.last_curv, s.lastseg (s.last_curv));
}

void tomin::operator() (solver& s, float& x, float& dx) {
	float xd = (x - s.lastx);
	float d = xd / s.deltax;
	int i = d;
	x = s.firstx + d - i;
	s.findseg (x);
}

void loopmin::operator() (solver& s, float& x, float& dx) {
  atmin::operator() (s, x, dx);
  if (dx < 0) dx = -dx;
}

void loopmax::operator() (solver& s, float& x, float& dx) {
  tomin::operator() (s, x, dx);
  if (dx < 0) dx = -dx;
}


void pongmax::operator () (solver& s, float& x, float& dx) {
	atmax::operator() (s, x, dx);
  if (dx > 0) dx = -dx;
}

void pongmin::operator () (solver& s, float& x, float& dx) {
	atmin::operator() (s, x, dx);
  if (dx < 0) dx = -dx;
}
