/*
* settings.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 "settings.h"
#include "viewwin.h"
#include "console.h"
#include "font.h"
#include "ui_list.h"
#include "tcl_interp.h"
#include "midi_in.h"
#include "audio.h"
#include "delay.h"
#include "main.h"
#include "instrument.h"
#include "curve_mixer.h"
#include "din.h"
#include <sstream>

#include "log.h"

extern string NEAREST_NOTE;
extern float NEAREST_NOTE_FREQUENCY;
extern float NEAREST_NOTE_DISTANCE;
extern int NUM_OCTAVES;
extern char BUFFER [];
extern int wheel, wheely;
extern din din0;
extern int line_height;
static const char* notation_types [] = {"numeric", "western", "indian"};

settings::settings () : ol_display_modes (1) {
  name = "settings";
	imode = -1;
	num_widgets = 0;
}

void settings::setup () {

	sp_key.set ("Key (Hz)", 1.0f, 0, MILLION, this, 0); 
	sp_num_octaves.set ("Number of Octaves", 1, 1, 15, this, 0);

  // setup separators
  separator* seps [] = {&h_sep1, &h_sep2, &h_sep3, &h_sep4, &h_sep5, &h_sep6, &h_sep7, &h_sep8, &h_sep9}; 
  for (int i = 0; i < 9; ++i) seps[i]->set_extents (480);

  // install into widget array
	widget* wa [] = {
		&b_go_back,
		&h_sep1,
		&sp_key,
		&ol_nearest_note,
		&h_sep2,
		&sn_scale_notes,
		&sp_num_octaves,
		&ol_notation,
		&ol_tuning,
		&h_sep3,
		&l_delay_dur,
		&r_delay_dur,
		&cb_sync_delay,
		&h_sep4,
		&ol_audio_devices,
		&ol_sample_rates,
		&ol_buffer_sizes,
		&b_refresh_audio_devices,
		&h_sep5,
		&ol_midi_devices,
		&b_refresh_midi_devices,
		&cb_show_midi_messages,
		&h_sep6,
		&ol_display_modes,
		&h_sep7,
		&b_factory_reset,
		&h_sep8,
		&sp_fps,
		&sp_ips,
		&h_sep9,
		&lf_mixing_time,
		&lf_mixing_samples,
		&b_set_to_audio_buffer_time,
	};

	int nw = 33;
	widgets.resize (nw);
	for (int i = 0; i < nw; ++i) widgets[i] = wa[i];
	
	num_widgets = widgets.size ();
  lnspc.resize (num_widgets);

  // setup widget listeners
  b_go_back.set_listener (this);
  b_factory_reset.set_listener (this);
  ol_notation.set_listener (this);
  ol_tuning.set_listener (this);
  ol_midi_devices.set_listener (this);
  cb_show_midi_messages.set_listener (this);
  b_refresh_midi_devices.set_listener (this);
  ol_audio_devices.set_listener (this);
  ol_sample_rates.set_listener (this);
  ol_buffer_sizes.set_listener (this);
  b_refresh_audio_devices.set_listener (this);
  l_delay_dur.fld.change_lsnr = this;
  r_delay_dur.fld.change_lsnr = this;
	cb_sync_delay.set_listener (this);
  ol_nearest_note.set_listener (this);
  sp_num_octaves.set_listener (this);
	ol_display_modes.set_listener (this);
	ol_display_modes.apply.set_listener (this);

	spinner<int>* spn[] = {&sp_fps, &sp_ips};
	const char* lspn[] = {"Frames Per Second", "Inputs Per Second"};
	for (int i = 0; i < 2; ++i) {
		spinner<int>* si = spn[i];
		si->set (lspn[i], 1, 1, MILLION, this, 0);
	}

	lf_mixing_time.set_listener (this);
	lf_mixing_time.fld.fmt = field::inf;
	lf_mixing_time.set_label ("Curve mixing time = ");
	lf_mixing_samples.set_listener (this);
	lf_mixing_samples.set_label ("Curve mixing samples = ");

	b_set_to_audio_buffer_time.set_listener (this);

  cb_show_midi_messages.set_state (0);

  i_current_tuning = 0;
  i_current_midi = 0;

  // build audio buffer sizes list
  int bs = 64;
  for (int j = 0; j < 7; ++j) {
    buffer_sizes.push_back (bs);
    bs *= 2;
  }

  i_buffer_size = 0;

	cb_sync_delay.set_text ("Sync");
	cb_sync_delay.turn_on ();

}

void settings::enter () {

	// called b4 settings page is displayed
	// refresh settings

	get_current_instrument ()->scaleinfo.update_settings ();

  if (prev_mousex == -1) {
    prev_mousex = view.xmax / 2;
    prev_mousey = view.ymax / 2;
  }
  
	// get current notation
  extern int NOTATION;
	sprintf (BUFFER, " notation = %s", notation_types[NOTATION]);
  ol_notation.option.set_text (BUFFER);

	// get current tuning
  tunings.clear ();
  interpreter ("tuning list");
  tokenizer tz (interpreter.result);
  while (1) {
    string name; tz >> name;
    if (name == "") break;
    tunings.push_back (name);
  }
  num_tunings = tunings.size ();
  interpreter ("tuning get");
  s_current_tuning = interpreter.result;
  find_current_tuning ();
  ol_tuning.option.set_text (" tuning = " + s_current_tuning + " ");

  // number of octaves
  //

  sp_num_octaves.set_value (NUM_OCTAVES);

	// load l & r delay durations
  const char* const chan [] = {"left", "right"};
  label_field* flds [] = {&l_delay_dur, &r_delay_dur};
  for (int i = 0; i < 2; ++i) {
		stringstream ss_gd;
		ss_gd << "get-delay " << chan[i];
		interpreter (ss_gd.str());
		stringstream ss_res; ss_res << interpreter.result;
		int msecs; ss_res >> msecs;
		flds[i]->fld.set_text (msecs / 1000.0f);
	}

  ui::enter ();

	cons.rollup (1);

	cons << YELLOW << "Use mouse wheel to scroll" << eol;

}

void settings::update_widgets () {

	int lh = (int)(1.25f * line_height);
  int x1 = (int)(0.25f * view.xmax), y1 = view.ymax - 2.3 * lh;
	
	for (int i = 0; i < num_widgets; ++i) lnspc [i] = lh;
	lnspc[0]=lnspc[3]=lnspc[8]=lnspc[12]=lnspc[17]=lnspc[21]=lnspc[23]=lnspc[25]=lnspc[28]=lnspc[32]=10;

	sn_scale_notes.update ();

	b_go_back.set_text ("Go back");
  b_factory_reset.set_text ("Reset to Factory Settings!");
  sp_num_octaves.update ();
  cb_show_midi_messages.set_text ("Show incoming MIDI messages");
  b_refresh_midi_devices.set_text ("Refresh MIDI devices");
  b_refresh_audio_devices.set_text ("Refresh Audio Devices");
	l_delay_dur.lbl.set_text ("L delay duration");
	r_delay_dur.lbl.set_text ("R delay duration");

	sp_fps.update ();
	sp_ips.update ();

	float t = get_tonic (get_current_instrument());
	sp_key.set_value (t);

	NEAREST_NOTE_FREQUENCY = t;
	find_nearest_note (NEAREST_NOTE, NEAREST_NOTE_FREQUENCY, NEAREST_NOTE_DISTANCE);
	sprintf (BUFFER, " nearest note = %s @ %0.3f Hz", NEAREST_NOTE.c_str(), NEAREST_NOTE_FREQUENCY);
	ol_nearest_note.set_text (BUFFER);
	ol_nearest_note.set_click_repeat (1);
	for (int i = 0; i < num_widgets; ++i) {
    widgets[i]->set_pos (x1, y1);
    y1 -= lnspc[i];
  }

	i_current_midi = midiin.input_port;
  ol_midi_devices.option.set_text (" midi = " + midiin.get_name (i_current_midi));
	i_current_device = aout.current_device;
  ol_audio_devices.option.set_text (" audio = " + aout.names [aout.current_device]);
	sprintf (BUFFER, " sample rate = %d", aout.sample_rate);
  ol_sample_rates.option.set_text (BUFFER);
	set_buffer_size (aout.samples_per_channel);

	b_set_to_audio_buffer_time.set_text ("Set to Audio Buffer Time");

}

void settings::set_buffer_size (int bs) {
  for (size_t i = 0, j = buffer_sizes.size (); i < j; ++i) {
    if (buffer_sizes[i] == bs) {
      i_buffer_size = i;
      break;
    }
  }
  sprintf (BUFFER, " buffer size = %d", buffer_sizes [i_buffer_size]);
  ol_buffer_sizes.option.set_text (BUFFER);
}

void settings::find_current_tuning () {
  for (int i = 0; i < num_tunings; ++i) {
    if (s_current_tuning == tunings[i]) {
      i_current_tuning = i;
    }
  }
}

int settings::handle_input () {
  if (wheel && !widget::HOVER) move_widgets (wheel * wheely);
  for (int i = 0; i < num_widgets; ++i) if (widgets[i]->handle_input ()) return 1;
	return 0;
}

void settings::draw () {
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (0, view.xmax, 0, view.ymax, -1, 1);
  for (int i = 0; i < num_widgets; ++i) widgets[i]->draw ();
}

void settings::move_widgets (int dy) {
	for (int i = 0; i < num_widgets; ++i) widgets[i]->move (0, dy);
}

void settings::clicked (button& b) {
	if (&b == &b_set_to_audio_buffer_time) {
		lf_mixing_time.fld.set_text (aout.samples_per_channel * 1.0f / aout.sample_rate);
		changed (lf_mixing_time.fld);
	} else if (&b == &b_factory_reset) {
    cons ("factory-reset");
  } else if (&b == &b_go_back) {
		uis.set_current (uis.prev);
  } else if (&b == &b_refresh_midi_devices) {
    midiin.probe ();
    ol_midi_devices.option.set_text (" midi = " + midiin.get_name (i_current_midi));
    midiin.open (i_current_midi);
  } else if (&b == &b_refresh_audio_devices) {
    aout.close ();
    aout.probe ();
    aout.open ();
    aout.start ();
    ol_audio_devices.option.set_text (" audio = " + aout.names [i_current_device]);
    sprintf (BUFFER, " sample rate = %d", aout.sample_rate);
    ol_sample_rates.option.set_text (BUFFER);
    set_buffer_size (aout.samples_per_channel);
  } else if (&b == &ol_display_modes.apply) {
		display_mode& dm = display_modes [imode];
		extern int FULL_SCREEN; FULL_SCREEN = dm.fullscreen;
		setup_video_mode (dm.w, dm.h, dm.w, dm.h, dm.fullscreen);
	}
}

void settings::picked (label& l, int d) {
	static const float TWELFTH_ROOT_OF_2 = 1.0594630943f;
	if (&l == &ol_nearest_note.option) {
		float tonic = get_tonic (get_current_instrument ());
		if (d > 0) {
			tonic *= TWELFTH_ROOT_OF_2;
		} else {
			tonic /= TWELFTH_ROOT_OF_2;
		}
		set_tonic (get_current_instrument (), tonic);
	} else if (&l == &ol_notation.option) {
    extern int NOTATION;
    NOTATION += d;
    wrap<int> (NUMERIC, NOTATION, INDIAN);
		set_notation (NOTATION);
		sprintf (BUFFER, " notation = %s", notation_types [NOTATION]);
		ol_notation.option.set_text (BUFFER);
  } else if (&l == &ol_tuning.option) {
    i_current_tuning += d;
    if (i_current_tuning < 0) i_current_tuning = num_tunings - 1; else
    if (i_current_tuning >= num_tunings) i_current_tuning = 0;
    s_current_tuning = tunings[i_current_tuning];
    interpreter ("tuning set " + s_current_tuning);
    ol_tuning.option.set_text (" tuning = " + s_current_tuning + " ");
  } else if (&l == &ol_midi_devices.option) {
    if (midiin.num_ports) {
      i_current_midi += d;
      if (i_current_midi < 0) i_current_midi = midiin.num_ports - 1; 
      else if (i_current_midi >= midiin.num_ports) i_current_midi = 0;
      midiin.open (i_current_midi);
      ol_midi_devices.option.set_text (" midi = " + midiin.get_name (i_current_midi));
    }
  } else if (&l == &ol_audio_devices.option) {
    if (aout.goto_next_device (d)) {
			sprintf (BUFFER, "set-audio device %d", aout.current_device);
			cons (BUFFER);
			ol_audio_devices.option.set_text (" audio = " + aout.names [aout.current_device]);
		}
  } else if (&l == &ol_sample_rates.option) {
    int srate = aout.goto_next_sample_rate_id (d);
    sprintf (BUFFER, " sample rate = %d", srate);
    ol_sample_rates.option.set_text (BUFFER);
    sprintf (BUFFER, "set-audio sample_rate %d", srate);
    cons (BUFFER);
  } else if (&l == &ol_buffer_sizes.option) {
    i_buffer_size += d;
    int n = buffer_sizes.size ();
    if (i_buffer_size < 0) i_buffer_size = n - 1; else if (i_buffer_size >= n) i_buffer_size = 0;
    int bs = buffer_sizes [i_buffer_size];
    sprintf (BUFFER, " buffer size = %d", bs);
    ol_buffer_sizes.option.set_text (BUFFER);
    sprintf (BUFFER, "set-audio samples_per_channel %d", bs);
    cons (BUFFER);
  } else if (&l == &ol_display_modes.option) {
		imode += d;
		if (imode >= num_modes) imode = 0; else if (imode < 0) imode = num_modes - 1;
		update_mode_display ();
	}

}

void settings::changed (checkbutton& cb) {
  int state = cb_show_midi_messages.state;
	if (state) interpreter ("load-patch midimap 1"); else interpreter ("unload_midimap");
}

void settings::do_delay_dur (delay& d, field& f) {
	float v = f;
	if (v <= 0) {
		cons << RED << "bad delay duration" << eol;
		d.get (v);
		v /= 1000.0;
		f.set_text (v);
		return;
	}
	string cmd ("set-delay ");
	if (cb_sync_delay.state) {
		l_delay_dur.fld.set_text (v);
		r_delay_dur.fld.set_text (v);
		cmd += "all ";
	} 
	else if (&f == &l_delay_dur.fld) cmd += "left ";
	else cmd += "right "; 
	float msecs = v * 1000.0f;
	stringstream ss; ss << msecs;
	cmd += ss.str();
	cons (cmd);
}

void settings::changed (field& f) {
	float value = f;
	if (&f == &l_delay_dur.fld) {
		do_delay_dur (left_delay, l_delay_dur.fld);
	} else if (&f == &r_delay_dur.fld) {
		do_delay_dur (right_delay, r_delay_dur.fld);
	} else if (&f == &sp_key.f_value) {
		set_tonic (get_current_instrument (), value);
	} else if (&f == &sp_num_octaves.f_value) {
    set_num_octaves (f);
  } else if (&f == &sp_fps.f_value) {
		extern int FPS;
		extern double TIME_PER_FRAME;
		FPS = int (f);
		TIME_PER_FRAME = 1.0 / FPS;
	} else if (&f == &sp_ips.f_value) {
		extern int IPS;
		extern double TIME_PER_INPUT;
		IPS = (int) f;
		TIME_PER_INPUT = 1.0 / IPS;
	} else if (&f == &lf_mixing_time.fld) {
		float mixt = f;
		if (mixt <= 0) {
			cons << RED << "Bad mix time" << eol;
			return;
		}
		curve_mixer::TIME = f;
		curve_mixer::SAMPLES = curve_mixer::TIME * SAMPLE_RATE;
		lf_mixing_samples.fld.set_text (curve_mixer::SAMPLES);
	} else if (&f == &lf_mixing_samples.fld) {
		int s = f;
		if (s < 1) {
			cons << RED << "Bad mix samples" << eol;
			return;
		}
		curve_mixer::SAMPLES = s;
		curve_mixer::TIME = curve_mixer::SAMPLES * 1.0f / SAMPLE_RATE;
		lf_mixing_time.fld.set_text (curve_mixer::TIME);
	}
}

void settings::load_fullscreen_modes () {

	dlog << "*** loading fullscreen modes ***" << endl;

	SDL_Rect **modes;

	// get available full screen modes
	modes = SDL_ListModes (0, SDL_FULLSCREEN | SDL_OPENGL | SDL_HWSURFACE);

	// Check is there are any modes available
	if (modes == (SDL_Rect **) 0){
  	dlog << "No full-screen modes available" << endl;
		return;
	}

	// Check if our resolution is restricted
	if (modes == (SDL_Rect **) -1) {
	} else {
		static const int fullscreen = 1;
		static const int min_width = 1024;
  	for(int i =0; modes[i]; ++i) {
			SDL_Rect* moder = modes[i];
			if (moder->w >= min_width) {
				display_modes.push_back (display_mode(moder->w, moder->h, fullscreen)); 
				dlog << " found mode: " << moder->w << 'x' << moder->h << endl;
			}
		}
	}

	num_modes = display_modes.size ();

	dlog << "number of fullscreen modes = " << num_modes << endl;
	dlog << "+++ loaded all full screen modes +++" << endl;

}

void settings::add_display_mode (int w, int h) {
	display_mode dm (w, h);
	display_modes.push_back (dm);
	num_modes = display_modes.size ();
}

void settings::update_windowed_mode (int w, int h) {
	int last_mode = num_modes - 1;
	display_mode& dm = display_modes [last_mode];
	dm.w = w; dm.h = h;
	if (last_mode == imode) update_mode_display ();
}

void settings::update_mode_display () {
	display_mode& dm = display_modes [imode];
	static const char* const fs_str[] = {"windowed", "full screen"};
	sprintf (BUFFER, " display = %d x %d, %s", dm.w, dm.h, fs_str[dm.fullscreen]);
	ol_display_modes.set_text (BUFFER);
}

void settings::sample_rate_changed () {
	curve_mixer::SAMPLES = curve_mixer::TIME * SAMPLE_RATE;
	lf_mixing_samples.fld.set_text (curve_mixer::SAMPLES);
}
