/* 
* binaural_drones.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 "binaural_drones.h"
#include "console.h"
#include "curve_listener.h"
#include "audio.h"
#include "input.h"
#include "viewwin.h"
#include "fft.h"
#include "utils.h"
#include "ui_list.h"
#include <algorithm>
#include "log.h"

extern string user_data_dir;
extern int mousex, mousey;
extern atmin _atmin; 
extern atmax _atmax;
extern int SAMPLE_RATE;
extern char BUFFER[];
extern const char spc;
extern int FPS;

void waveform_listener::edited (curve_editor* ed, int i) {
  if (fft0.folded () == 0) fft0.go (ed->curveinfo[i].curve);
	sb->update_players (ed->mix);
	curve_listener::edited (ed, i);
}

fade_info::fade_info () {
	xt = dxt = xui = 0.0f;
	active = 0;
	after = 0;
}

void fade_info::start (const string& _name) {
	name = _name;
	xt = xui = 0.0f;
	after = 0;
	active = 1;
}

void fade_info::set_duration (float d) {
	if (d < 0) return;
	duration = d;
	if (equals (d, 0.0f)) d = 0.001f;
	float du = 0.1 * d;
	dxt = SAMPLE_DURATION * 1.0 / d;
	xu = SAMPLE_DURATION * du;
}

int fade_info::eval (float* out) {
	int u = 0;
	if (active) {
		sol (xt, dxt, aout.samples_per_channel, out, _atmin, _atmax);
		if (xt >= 1.0f) {
			complete ();
			u = 1;
		} else {
			if ((xt - xui) >= xu) {
				xui = xt;
				u = 1;
			}
		}
	}
	return u;
}

void fade_info::complete () {
	xt = 1.0f;
	active = 0;
	after = 1;
}

void fade_info::abort () {
	active = 0;
	after = 0;
}

void i_binaural_drones::load () {
  ifstream file ((user_data_dir + "binaural_drones").c_str (), ios::in);
	string ignore;
	file >> ignore >> master_volume;
	file >> ignore >> starting_pitch;
	file >> ignore >> separation;
	file >> ignore >> pairs;
	file >> ignore >> spacing;
	float d;
	file >> ignore >> d; vol_fader.set_duration (d);
	file >> ignore >> d; pitch_fader.set_duration (d);
	file >> ignore >> just;
	file >> ignore >> keynote;
	file >> ignore >> close_octave;
	file >> ignore >> resize_separation;
	file >> ignore >> modulation_amount;
	int n; file >> ignore >> n;
	if (n) {
		for (int i = 0; i < n; ++i) {
			float lhz, rhz, vol, sep, lx, rx;
			int just;
			file >> lhz >> rhz >> vol >> just >> sep >> lx >> rx;
			add (lhz, rhz, vol, just, sep, lx, rx); 
		}
		dlog << "+++ loaded " << n << " binaurals +++" << endl;
	}
}

void i_binaural_drones::save () {
  ofstream file ((user_data_dir + "binaural_drones").c_str (), ios::out);
	file << "master_volume " << master_volume << endl;
	file << "starting_pitch " << starting_pitch << endl;
	file << "separation " << separation << endl;
	file << "pairs " << pairs << endl;
	file << "spacing " << spacing << endl;
	file << "volume_fade_duration " << vol_fader.duration << endl;
	file << "pitch_fade_duration " << pitch_fader.duration << endl;
	file << "justification " << just << endl;
	file << "keynote " << keynote << endl;
	file << "close_octave " << close_octave << endl;
	file << "resize_separation " << resize_separation << endl;
	file << "modulation_amount " << modulation_amount << endl;
	file << "num_binaural_drones " << num_binaural_drones << endl; 
	for (int i = 0; i < num_binaural_drones; ++i) {
		binaural_drone* bi = binaural_drones[i];
		file << bi->l_hz << spc << bi->r_hz << spc << bi->vol << spc << bi->just << spc << bi->sep << spc << bi->playl.x << spc << bi->playr.x << endl;
	}
	dlog << "+++ saved all binaurals +++" << endl;
}

i_binaural_drones::~i_binaural_drones () {
	for (int i = 0; i < num_binaural_drones; ++i) delete binaural_drones[i];
	wav.save ("binaural-drones-waveform.crv");
	dlog << "--- destroyed binaural_drones ---" << endl;
}

int i_binaural_drones::add (float lhz, float rhz, float vol, int just, float sep, float lx, float rx) {
	binaural_drone* bd = new binaural_drone (&wav, lhz, rhz, vol, just, sep, lx, rx);
	vol_fader.start ("Volume fade-in");
	int id = 0;
	if (num_binaural_drones) { // insert sorted by left hz
		typedef std::vector<binaural_drone*>::iterator binaural_drone_iterator;
		binaural_drone_iterator bd_begin = binaural_drones.begin ();
		for (int i = 0; i < num_binaural_drones; ++i) {
			binaural_drone* bi = binaural_drones [i];
			if (bi->l_hz >= bd->l_hz) { 
				binaural_drone_iterator bd_insert = bd_begin + i;
				binaural_drones.insert (bd_insert, bd);
				MENU.il_binaural_drones.insert (i, bd->name);
				++num_binaural_drones;
				id = i;
				return id;
			}
		}
	} 
	binaural_drones.push_back (bd);
	id = num_binaural_drones++;
	MENU.il_binaural_drones.add (bd->name);
	return id;
}

void i_binaural_drones::remove (int which) {
	if (which < num_binaural_drones) {
		binaural_drone* bw = binaural_drones [which];
		binaural_drones.erase (binaural_drones.begin()+which); 
		--num_binaural_drones;
		delete bw;
		MENU.il_binaural_drones.remove (which);
	} else cons << RED << "bad binaural drone id: " << which << eol;
}

void i_binaural_drones::sync (int n, const string& lst) {
	if (n == -1) { // sync all
		for (int i = 0; i < num_binaural_drones; ++i) {
			binaural_drone* bi = binaural_drones[i];
			bi->sync ();
		}
	} else { // sync list
		tokenizer tz (lst);
		for (int i = 0; i < n; ++i) {
			int j; tz >> j;
			binaural_drone* bj = binaural_drones[j];
			bj->sync ();
		}
	}
}

void i_binaural_drones::list () { 
	cons << YELLOW;
	for (int i = 0; i < num_binaural_drones; ++i) {
		binaural_drone* pbd = binaural_drones[i];
		sprintf (BUFFER, "Pair %d: %s", i, pbd->name.c_str()); 
		cons << BUFFER << eol;
	}
	sprintf (BUFFER, "%d pairs in total", num_binaural_drones);
	cons << GREEN << BUFFER << eol;
}

string i_binaural_drones::get_sel_vol () {
	stringstream ss;
	for (int i = 0; i < num_binaural_drones; ++i) {
		binaural_drone* bi = binaural_drones[i];
		if (bi->sel) ss << bi->vol << spc;
	}
	return ss.str();
}

int i_binaural_drones::render_audio (float* L0, float* R0) {

	int uv = 0, uh = 0;
	if (vol_fader.active) uv = vol_fader.eval (aout.vol);
	if (pitch_fader.active) uh = pitch_fader.eval (aout.fdr2);
	int uvh = uv | uh;

	for (int i = 0; i < num_binaural_drones; ++i) {
		binaural_drone* bd = binaural_drones[i];
		float* L = L0, *R = R0;
		bd->render (L, R, aout.vol, aout.fdr2, uv, uh, abort);
		if (uvh) MENU.il_binaural_drones.items[i].name = bd->name;
	}

	if (abort) {
		vol_fader.abort ();
		pitch_fader.abort ();
		abort = 0;
		cons << RED << "Aborted" << eol;
	} else {

		if (vol_fader.after) {
			for (int i = 0; i < num_binaural_drones; ++i) binaural_drones[i]->fading.vol = binaural_drone::fade_flags::NONE;
			cons << GREEN << vol_fader.name << " complete." << eol;
			vol_fader.after = 0;
			vol_fader.name = "";
			cons ("after-fade");
		}

		if (pitch_fader.after) {
			for (int i = 0; i < num_binaural_drones; ++i) {
				binaural_drone* bi = binaural_drones[i];
				if (bi->fading.hz) {
					bi->fill_hz ();
					bi->fading.hz = binaural_drone::fade_flags::NONE;
				}
			}
			cons << GREEN << pitch_fader.name << " complete." << eol;
			pitch_fader.after = 0;
			pitch_fader.name = "";
			cons ("after-fade");
		}

	}

	return 1;

}

void i_binaural_drones::update_players (multi_curve& mx) {
	int canmix = mx.num_vertices;
	for (int i = 0; i < num_binaural_drones; ++i) {
		binaural_drone* s = binaural_drones[i];
		s->soll.update ();
		s->solr.update ();
	}
	if (canmix) {
		for (int i = 0; i < num_binaural_drones; ++i) {
			binaural_drone* s = binaural_drones[i];
			s->playl.set_mix (mx, "binaural_drones_waveform");
			s->playr.set_mix (mx, "binaural_drones_waveform");
		}
	} 	
}

int i_binaural_drones::handle_input () {
	prev_mousex = mousex;
	prev_mousey = mousey;
	if (keypressed (SDLK_F1)) hlp();
	if (keypressed (SDLK_F3)) aborted ();
	return 1;
}

void i_binaural_drones::enter () {
	extern void show_menu ();
	show_menu ();
}

int i_binaural_drones::change_justification (int d) {
	just += d;
	if (just < binaural_drone::LEFT) just = binaural_drone::CENTER; 
	else if (just > binaural_drone::CENTER) just = binaural_drone::LEFT;
	return just;
}

int i_binaural_drones::change_key_note (int d) {
	keynote += d;
	if (keynote < START_PITCH) keynote = FROM_SCALE; 
	else if (keynote > FROM_SCALE) keynote = START_PITCH;
	return keynote;
}

int i_binaural_drones::busy () { return vol_fader.active || pitch_fader.active; }

int i_binaural_drones::aborted () {
	if (busy()) abort = 1;
	return abort;
}
