/*
* drone.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 "din.h"
#include "drone.h"
#include "console.h"
#include "audio.h"
#include "utils.h"
#include "vector2d.h"
#include "menu.h"
#include <math.h>
using namespace std;

extern multi_curve drone_mod_am_crv, drone_mod_fm_crv;
extern solver warp_vol, warp_pitch;
extern const char spc;

extern void get (float& g, float& gx, float& gy, defvelaccel& dva);
extern int gethandlesize ();
extern int gettrailsize ();
extern void initlaunch (drone*);
extern const float TWO_PI;
extern float getlifetime ();

void drone::setis () {
  if (IS == DRONE_OR_NOISE) is = get_rand_bit (); else is = IS;
}

drone::drone (float bottom) : mod (&drone_mod_fm_crv, &drone_mod_am_crv), modv (this) {

  id = ++UID;
  setis ();
  step = vol = 0;
  range = 0;
  pos = 0.0f;
  r = g = b = 0;
  cx = cy = 0;
	x = y = 0;
  dy = 0;
  xv = 0; yv = 0;
  xa = 0; ya = 0;
	sx = 0;
  sel = 0;
  state = DEAD;
	frozen = 0;
	froze_at = 0;
  handle_size = gethandlesize (); 
  trail.set (gettrailsize());
  vrev = 1;
  get (V, vx, vy, v0); modv.val = V;
  get (A, ax, ay, a0);
  gravity = 0;
  attractor = 0;
	orbiting = 0;
  launcher = 0;
  tracking = 0;
  tracked_drone = 0;
  initlaunch (this);
	++ref;
  launched_by = 0;
	insert = INSERTTIME;
	target = 0;
	cur_target = 0;
	num_targets = 0;
  update_pv = UNSET;
	snap = 0;
	nconn = 0;
	type = CHAIN;
  gab.set (1.0f, 0.0f, 0, 1.0f);
  tovol = 0.0f;
  finl = 0;
  note_num = -1;
  reincarnate = 0;
  setlife (getlifetime());
  if (ARE == IMMORTAL) {
    birth = -1;
  } else {
    if (ARE == REINCARNATE) reincarnate = 1;
    birth = ui_clk ();
  }
}

drone::~drone () {
	--ref;
}


void drone::move_center () {
	cx += (x - xi); 
	cy += (y - yi);
}

drone* drone::eval_conns () {
	if (nconn) {
		int n = 0;
		proc_conn [this] = true;
		list<double>::iterator mi = mags.begin();
		drone_iterator p = connections.begin ();
		drone* c0 = *p;
		for (drone_iterator q = connections.end (); p != q; ++p, ++mi) {
			drone* pdc = *p;
			if (proc_conn[pdc] == false) {
				proc_conn [pdc] = true;
				drone& dc = *pdc;
				double now = magnitude (cx, cy, dc.cx, dc.cy);
				double org = *mi;
        if (now > org) {
          double lead = STIFFNESS * (now - org);
          float ux, uy; unit_vector<float, int> (ux, uy, dc.cx, dc.cy, cx, cy);
          dc.set_center (dc.cx + lead * ux, dc.cy + lead * uy);
          ++n;
        }
			}
		}
		if (n) return this; else return c0;
	}
	return 0;
}

void drone::remagconns () {
  list<double>::iterator mi = mags.begin();
  drone_iterator p = connections.begin ();
  for (drone_iterator q = connections.end (); p != q; ++p, ++mi) {
    drone* pd = *p;
    *mi = magnitude<double> (cx, cy, pd->cx, pd->cy);
  }
}

void drone::set_center (float xx, float yy, int e) {
	cx = xx;
	cy = yy;
	if (frozen) set_pos (cx + mod.fm.result, cy + mod.am.result);
	if (e) eval_conns ();
}

void drone::set_pos (float xx, float yy) {

	// position
	x = xx;
	y = yy;

  // position affects vectors?
  if (posafxvel.yes && !autorot.v.yes && !gravity && !tracking && !orbiting) {
    double mag = magnitude (posafxvel.pt.x, posafxvel.pt.y, x, y);
    if (mag > posafxvelt::minmag) {
      direction (vx, vy, posafxvel.pt.x, posafxvel.pt.y, x, y);
      vx /= mag;
      vy /= mag;
      ax = vy;
      ay = -vx;
      posafxvel.pt.x = x;
      posafxvel.pt.y = y;
    }
  }

  // locate range on microtonal-keyboard
  range = din0.find_range (x, range);
  ::range& dr = din0.ranges[range];

  // pitch position in range
  int delta_left = x - dr.extents.left;
  pos = delta_left * 1.0f / dr.extents.width;
  if (pos < 0 || pos > 1.0f) ; else pos = warp_pitch (pos);

  // pitch snap
  if (snap) { 
    if (pos <= din0.dinfo.snap.left) 
      note_num = 0; 
    else if (pos > din0.dinfo.snap.right) 
      note_num = 1;
    else 
      goto normal;
    if (note_num) { 
      pos = 1; 
      sx = dr.extents.right; 
    } else { 
      pos = 0; 
      sx = dr.extents.left;
    }
  } else {
    normal:
    note_num = -1;
    sx = x;
  }

	calc_handle (); 

  update_pv = EMPLACE; // update pitch volume b4 rendering drones

}

void drone::calc_handle () {
  handle (sx - handle_size, y - handle_size, sx + handle_size, y + handle_size);
}

void drone::update_pitch_volume () {

  ::range& dr = din0.ranges[range];
  dy = y - BOTTOM;
  float iv = dy * 1.0f / dr.extents.height;
  if (dy < 0) vol = -warp_vol (-iv); else vol = warp_vol (iv);

  if (note_num == -1) {
    float n0step = dr.notes[0].step, n1step = dr.notes[1].step;
    float nstep = n0step + pos * (n1step - n0step);
    if (nstep > 0) step = nstep;
  } else {
    step = dr.notes[note_num].step;
  }

  if (is == DRONE) {
    float v = mastervolume * fdr.amount * gab.amount * vol;
    player.set_interpolated_pitch_volume (step, v, 1);
  } else 
    setnoise ();

  --update_pv;

}


void drone::setnoise () {
  nsr.set_samples (1.0f / step);
  nsr.set_spread ( fabs(vol) );
}



void drone::change_bpm (mod_params& mp, float delta) {
	mp.bv.set_bpm (mp.bv.bpm + delta);
}

void drone::change_bpm (int i, int what, float delta) {
  mod_params* modpar [] = {&mod.am, &mod.fm};
  mod_params& modwhat = *modpar[what];
  change_bpm (modwhat, delta);
  static const char* whatstr [] = {", AM bpm = ", ", FM bpm = "};
  cons << "drone: " << i << whatstr[what] << modwhat.bv.bpm << eol;
}

void drone::change_depth (int i, int what, float delta) {
  float* whats [] = {&mod.am.depth, &mod.fm.depth};
  checkbutton* cbs [] = {MENUP.damd0, MENUP.dfmd0};
  float& whati = *whats[what];
  static const char* whatstr [] = {", AM depth = ", ", FM depth = "};
  whati += delta;
  if (cbs[what]->state && whati < 0.0f) whati = 0.0f;
  cons << "drone: " << i << whatstr[what] << whati << eol;
}

void drone::change_modpos (int i, int what, float val) {
  float* whats [] = {&mod.am.bv.now, &mod.fm.bv.now};
  float& whatv = *whats[what];
  whatv += val;
	clamp (0.0f, whatv, 1.0f);
  static const char* whatstr [] = {", AM position = ", ", FM position = "};
  cons << "drone: " << i << whatstr[what] << whatv << eol;
}

void drone::clear_targets () {
	targets.clear ();
	cur_target = 0;
	num_targets = 0;
}

void drone::handle_time_pass () {
  double now = ui_clk ();
	double tp = now - froze_at; 
  mod.t += tp;
  if (autorot.v.yes) autorot.v.tik.startt += tp;
  if (autorot.a.yes) autorot.a.tik.startt += tp;
	if (birth != -1) birth += tp;
	if (launcher) launch_every.startt += tp;
  fdr.start_time = now - fdr.alpha * fdr.delta_time;
  // cons << "fdr: alpha = " << fdr.alpha << " delta_time = " << fdr.delta_time << eol;
}

int drone::freeze () {
	++frozen;
	froze_at = ui_clk ();
	update_pv = drone::EMPLACE;
  if (chuck.yes && chuckt::autoresettrails && chuck.sat) chuck.resettrail (chuck.sat);
	return 1;
}

int drone::thaw () {
	if (--frozen < 1) {
		frozen = 0;
		handle_time_pass ();
    if (chuck.yes && chuckt::autoresettrails && chuck.sat) chuck.resettrail (chuck.sat);
		return 1;
	}
	return 0;
}

group::group (std::vector<drone*> mem, int _n) {
	n = _n;
	if (n) {
		drones.resize (n);
		for (int i = 0; i < n; ++i) drones[i] = mem[i];
	}
}

std::ostream& operator<< (std::ostream& f, drone::arrowt& aw) {
	f << aw.u << spc << aw.v << spc << aw.t << spc << aw.cap;
	return f;
}

std::istream& operator>> (std::istream& f, drone::arrowt& aw) {
  f >> aw.u >> aw.v >> aw.t >> aw.cap;
  return f;
}

std::ostream& operator<< (std::ostream& f, drone::chuckt& ch) {
  f << ch.len << spc << ch.speed << spc << ch.dir << spc << ch.ux << spc << ch.uy << spc;
  if (ch.sun) f << ch.sun->id << spc; else f << 0 << spc;
  if (ch.sat) f << ch.sat->id; else f << 0;
  return f;
}

std::istream& operator>> (std::istream& f, drone::chuckt& ch) {
  f >> ch.len >> ch.speed >> ch.dir >> ch.ux >> ch.uy;
  uintptr_t sun; f >> sun; ch.sun = (drone*) sun;
  uintptr_t sat; f >> sat; ch.sat = (drone*) sat;
  return f;
}

void drone::chuckt::re (drone& p) {
  if (sun) p.chuck.len = unit_vector (p.chuck.ux, p.chuck.uy, sun->cx, sun->cy, p.cx, p.cy);
}

void drone::chuckt::print () {
  stringstream ss;
  ss << " || Speeds: This = " << speed;
  if (sun) {
    ss << ", Previous = " << sun->chuck.speed;
  } else ss << ", Previous = none";
  if (sat) {
    ss << ", Next = " << sat->chuck.speed;
  } else 
    ss << ", Next = none";
  cons << ss.str ();
}

void drone::chuckt::resettrail (drone* d) {
  d->trail.reset ();
  drone* sat = d->chuck.sat;
  if (sat) sat->chuck.resettrail (sat);
}

void drone::setcolor (drone& d, float a) {
  if (is == drone::NOISE) 
    r = g = b = 1;
  else {
    rnd<float> rd (-a, a);
    r = d.r + rd() * d.r; clamp (0.0f, r, 1.0f);
    g = d.g + rd() * d.g; clamp (0.0f, g, 1.0f);
    b = d.b + rd() * d.b; clamp (0.0f, b, 1.0f);
  }
}

void drone::setlife (float l) {
	if (l <= 0) { l = 0.001f; life = 0.0f; } else life = l;
	modv._1life = 1.0 / l;
}

std::ostream& operator<< (std::ostream& f, anglet& a) {
  f << a.deg;
  return f;
}

std::istream& operator>> (std::istream& f, anglet& a) {
  f >> a.deg;
  a.torad ();
  return f;
}

std::ostream& operator<< (std::ostream& f, nnmaxt& a) {
  f << a.n << spc << a.max;
  return f;
}

std::istream& operator>> (std::istream& f, nnmaxt& a) {
  f >> a.n >> a.max;
  return f;
}
