/*
* keyboard_keyboard.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 "main.h"
#include "keyboard_keyboard.h"
#include "input.h"
#include "ui_list.h"
#include "audio.h"
#include "din.h"
#include "audio.h"
#include "midi_in.h"
#include <math.h>
#include "console.h"
#include "color.h"
#include "log.h"
#include <fstream>
using namespace std;

extern int mousex, mousey, mouseyy;

extern din din0;
extern beat2value octave_shift;

extern float ATTACK_TIME, DECAY_TIME;
extern float NOTE_VOLUME;

extern int PITCH_BEND;
extern float PITCH_BEND_PER_PIXEL;

extern audio_out aout;
extern viewport view;

extern curve_library wav_lib, attack_lib, decay_lib;

extern int line_height;

typedef list<triggered_note>::iterator triggered_note_iterator;

extern int UI_OFF;

extern const char* INDIAN_SWAR [];
extern map<string, string> INT2IND;

void keyboard_keyboard::setup () {

  vector<key_info> ki;

  Uint8 ky [] = { // the keyboard keys
    SDLK_t, SDLK_r, SDLK_e, SDLK_w, SDLK_q, SDLK_a, SDLK_s,
    SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l,
    SDLK_SEMICOLON, SDLK_QUOTE, SDLK_RIGHTBRACKET, SDLK_LEFTBRACKET, 
    SDLK_p, SDLK_o, SDLK_i, SDLK_u
  };

  char kz [] = { // the keys as chars
    't', 'r', 'e', 'w', 'q', 'a', 's',
    'd', 'f', 'g', 'h', 'j', 'k', 'l',
    ';', '\'', ']', '[', 'p', 'o', 'i', 
    'u'
  };

  // assign keys to scale
  for (int i = 1, j = 6, noct = 3; i < 13; ++i, --j) {
    if (i > 7) {
      j = 7;
      noct = 1;
    } 

    ki.clear ();
    for (int k = 0, l = noct * i + 1; k < l; ++k) {
      int jk = j + k;
      ki.push_back ( key_info (ky [jk], kz [jk]) );
    }
    scale2keybd [i] = ki;
  }  

	// setup waveform
  waved.add (&wave, &wavlis);
  waved.attach_library (&wav_lib);

  // setup attack curve
  attacked.add (&attackcrv, &attacklis);
  attacklis.inst = this;
  attacked.attach_library (&attack_lib);

  // setup decay curve
  decayed.add (&decaycrv, &decaylis);
  decaylis.inst = this;
  decayed.attach_library (&decay_lib);

  // setup velocity curve (used on MIDI triggered notes)
  velsol (&velcrv);
	veled.add (&velcrv, &vellis);
  veled.attach_library (&vellib);

  // initial mouse position
  prev_mousex = view.xmax / 2;
  prev_mousey = view.ymax / 2;

}

keyboard_keyboard::~keyboard_keyboard () {
	scaleinfo.save_scale ();
  wave.save ("keyboard-keyboard-waveform.crv");
  attackcrv.save ("attack.crv");
  decaycrv.save ("decay.crv");
  velcrv.save ("velocity.crv");
	ofstream file ((user_data_dir + fname).c_str(), ios::out);
	if (!file) {
		dlog << "!!! couldnt save : " << fname << endl;
	} else {
		file << "show_nearby_notes " << show_nearby_notes << endl;
		file << "attack_time " << ATTACK_TIME << endl;
		file << "decay_time " << DECAY_TIME << endl;
		file << "note_volume " << NOTE_VOLUME << endl;
		file << "pitch_bend " << PITCH_BEND << endl;
		file << "trig_what " << trig_what << endl;
	}
  dlog << "--- destroyed keyboard-keyboard ---" << endl;
}

void reduce (float& what, float& by, float limit) {
  what -= by;
  if (what <= limit) what = by;
}

void keyboard_keyboard::bg () {
  remove_finished_notes (); // kill notes decayed to silence
}

int keyboard_keyboard::handle_input () {

  din0.dinfo.delay = uis.cb_delay.state;

	mouse_bend (); // pitch bend via mouse x movement

  // trigger notes
  //
  for (int i = 0, j = keys.size (); i < j; ++i) {
    key_info& ki = keys[i];
    if (keypressed (ki.id)) { // pressed key of note
      attack_note (ki.id); // trigger note
      ki.attacked = 1;
    } else if (ki.attacked && !keydown (ki.id)) { // released key of note
      decay_note (ki.id); // decay
      ki.attacked = 0;
    }
  }

	if (keypressed (SDLK_RETURN)) {
		trig_what = !trig_what;
		extern const char* keys_trig_what [];
		set_label (uis.ol_trig_what.option, keys_trig_what, trig_what);
	}

  if (keypressed (SDLK_SPACE)) 
		turn_off_bend (); // turn of mouse based pitch bending

  // octave shift
  else if (keypressed (SDLK_z)) modulate_down ();
  else if (keypressed (SDLK_x)) modulate_up ();
  else if (keypressedd (SDLK_F11)) { // decrease octave shift bpm
    octave_shift.set_bpm (octave_shift.bpm - (float)MENU.sp_octave_shift_bpm.f_delta);
    cons << "octave shift bpm: " << octave_shift.bpm << eol;
  } else if (keypressedd (SDLK_F12)) { // increase octave bpm
    octave_shift.set_bpm (octave_shift.bpm + (float) MENU.sp_octave_shift_bpm.f_delta);
    cons << "octave shift bpm: " << octave_shift.bpm << eol;
  } 
  else if (keypressed (SDLK_n)) abort_octave_shift (this);

	// browse waveforms
  if (!mouse_slider0.active) {
    if (keypressedd (SDLK_9)) uis.wdl.clicked (uis.ab_prev_wav); else 
    if (keypressedd (SDLK_0)) uis.wdl.clicked (uis.ab_next_wav);
  }

  if (keypressed (SDLK_F1)) helptext(); // show help

  return 1;


}

void keyboard_keyboard::setup_notes (int overwrite) {

  notes.clear ();

  vector<std::string>& scalenotes = scaleinfo.notes;

  float tonic;
  int noct;
  int num_notes = scalenotes.size () - 1; // bcos note & octave included in scale definition
  if (num_notes > 7) { // 1 octave for > 7 notes
    noct = 1;
    tonic = scaleinfo.tonic;
  } else { // 3 octaves for < 7 notes 
    noct = 3;
    tonic = scaleinfo.tonic / 2.0f;
  }

  extern map<std::string, float> INTERVALS; // tuning
  extern const char* WESTERN_FLAT [];
	extern map<std::string, int> NOTE_POS; // inteval -> pos on chromatic scale
  extern int NOTATION; // current notation
  int western = scaleinfo.western; // index of key in the chromatic scale

  string tonic_name;
  if (NOTATION == WESTERN) {
    tonic_name = WESTERN_FLAT [western]; // note on the chromatic scale
  } else if (NOTATION == NUMERIC) tonic_name = "1"; 
  else tonic_name = INDIAN_SWAR[0];

  for (int i = 0; i < noct; ++i) {
    for (int j = 0; j < num_notes; ++j) {
      std::string& name = scalenotes [j];
      notes.push_back (note());
      note& last = notes [notes.size() - 1];
      if (NOTATION == WESTERN) {
			  int k = (western + NOTE_POS [name]) % 12;
        last.name = WESTERN_FLAT [k];
      } else if (NOTATION == NUMERIC) last.name = name;
      else last.name = INT2IND[name];
      last.set_freq (tonic * INTERVALS [name]);
    }
    tonic *= 2; // next octave
  }

  // bring up the rear
  notes.push_back (note());
  note& last = notes [notes.size() - 1];
  last.name = tonic_name;
  last.set_freq (tonic);

  memset (keybd2notes, 0, MAX_KEYS * sizeof (int));

  if (overwrite) keys = scale2keybd [num_notes];

  // green the tonic 
  for (int i = 0, j = keys.size (), k = 0; i < j; ++i) {
    key_info& ki = keys[i];
    keybd2notes [ki.id] = k;
    if (notes[k++].name == tonic_name) ki.r = ki.b = 0;
  }

}

void keyboard_keyboard::attack_note (int ky) { // also trigger note

  int ni = keybd2notes [ky];
  note& n = notes [ni];
  key_info& ki = keys [ni];
  recent_note_hz = n.hz;

  marker_x = mousex;

	int last_trig_x = ki.x;
  int last_trig_y = ki.y;
	if (show_nearby_notes) { // position note display at cursor
    last_trig_x = mousex; 
    last_trig_y = mouseyy;
		if (num_triggered_notes) turn_off_bend (); // stop bending all previous notes, will only bend the last note
  }

  triggered_notes.push_back (triggered_note (n, NOTE_VOLUME, last_trig_x, last_trig_y, trig_what, ky, marker_x));
	triggered_note& last_triggered_note = triggered_notes.back (); 
	last_triggered_note.setup (&wave, &attackcrv, &decaycrv);
  ++num_triggered_notes;
}

void keyboard_keyboard::decay_note (int ky) {
  for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
    triggered_note& ti = *i;
    if (ti.key == ky) ti.start_decay ();
  }
}

void keyboard_keyboard::update_attack () {
  for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) (*i).attack.update ();
}

void keyboard_keyboard::update_decay () {
  for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) (*i).decay.update ();
}

void keyboard_keyboard::update_waveform (multi_curve& src) {
  static const string nam ("keyboard-keyboard-waveform");
  for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) 
    (*i).update_solver (src, nam);
  uis.cd_waveform_display.setbbox (wave.shapeform);
}

int keyboard_keyboard::render_audio (float* L, float* R) {
	int ret = 0;
  for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
    triggered_note& ti = *i;
    if (ti.state != triggered_note::FINISHED) {
			ti.eval (L, R, aout.result, aout.vol, aout.samples_per_channel, ATTACK_TIME, DECAY_TIME, _gotog);
			ret += ti.player.mixer.active;
		}
  }
	return ret;
}

void keyboard_keyboard::remove_finished_notes () {
  for (triggered_note_iterator i = triggered_notes.begin (); i != triggered_notes.end();) {
    triggered_note& ti = *i;
    if (ti.state == triggered_note::FINISHED) {
      i = triggered_notes.erase (i);
      --num_triggered_notes;
    } else ++i;
  }
}

void keyboard_keyboard::calc_visual_params () {
	int nkeys = keys.size ();
	int key_width = 2 * fnt.charwidth.avg * fnt.cellsize.x + 3;
	int press_width = get_char_width ("Press  "); // in pixels
	int print = press_width + key_width * nkeys;
	pressx = (int) ((view.xmax - print) / 2 + 0.5f);
	hearx = pressx;
	int x = pressx + press_width;
  int half_height = view.ymax / 2;
  for (int i = 0; i < nkeys; ++i) {
    key_info& ki = keys[i];
    ki.x = x;
    ki.y = half_height;
    x += key_width;
  }

  extern float NOTE_VOLUME;
  arm = (int) (0.4f * view.ymax / NOTE_VOLUME + 0.5f);

  int lh = (int) (1.5f * line_height + 0.5f);
  ymarker = (int) (1.25f * lh + 0.5f);
	ynotes = (int)(ymarker + 0.75f * lh + 0.5f);
  ychars = (int)(ynotes + lh);

  if (nkeys) spacing = (view.width - 2 * keys[0].x) / 12;

}

void keyboard_keyboard::draw () {

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (0, view.xmax, 0, view.ymax, -1, 1);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();

	// draw triggerred notes
  glEnable (GL_BLEND);
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  const float alpha = 0.5f;
  for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {

    triggered_note& ti = *i;

    // fill
    glColor4f (ti.r, ti.g, ti.b, alpha);
    int arm1 = (int) (ti.volume.now * arm);
    glRecti (ti.x - arm1 - ti.bend_x, ti.y - arm1, ti.x + arm1 + ti.bend_x, ti.y + arm1);

    // outline
    glColor4f (1, 1, 1, alpha);
    int xp = ti.x + arm1 + ti.bend_x, xm = ti.x - arm1 - ti.bend_x, yp = ti.y + arm1, ym = ti.y - arm1;
    glLineWidth (2);
    pts[0]=xm;pts[1]=ym;
    pts[2]=xp;pts[3]=ym;
    pts[4]=xp;pts[5]=yp;
    pts[6]=xm;pts[7]=yp;
		glVertexPointer(2, GL_INT, 0, pts);
    glDrawArrays (GL_LINE_LOOP, 0, 4);
    glLineWidth (1);

  }
  glDisable (GL_BLEND);

	if (UI_OFF) return;

  if (midiin.available) { // mark midi notes
    glColor3f (0.25, 0.25, 0.25);
    int startx = keys[0].x, starty = keys[0].y;
    int marker = 15;
    glVertexPointer (2, GL_INT, 0, pts);
    for (int i = 0, num_notes = 12; i < num_notes; ++i) {
      pts[0]=startx-marker;pts[1]=starty;
      pts[2]=startx+marker;pts[3]=starty;
      pts[4]=startx;pts[5]=starty-marker;
      pts[6]=startx;pts[7]=starty+marker;
      glDrawArrays (GL_LINES, 0, 4);
      startx += spacing;
    }
  }

  glEnable (GL_LINE_STIPPLE);
  glLineStipple (1, 0xFFF0);
	glColor3f (0.75, 0.75, 0.75);

  if (num_triggered_notes) {
    if (show_nearby_notes) { // mark notes near to cursor
      for (int i = 0, j = notes.size (); i < j; ++i) {
        key_info& ki = keys [i];
        note& ni = notes[i];
        float dhz = ni.hz - recent_note_hz;
        int npix = (int) (dhz / PITCH_BEND_PER_PIXEL + 0.5);
        int marker_nx = marker_x + npix;
        /*pts[0]=marker_nx;pts[1]=0;
        pts[2]=marker_nx;pts[3]=view.ymax;
        glVertexPointer (2, GL_INT, 0, pts);
        glDrawArrays (GL_LINES, 0, 2);*/
        glBegin (GL_LINES);
          glVertex2i (marker_nx, 0);
          glVertex2i (marker_nx, view.ymax);
        glEnd ();
        glDisable (GL_LINE_STIPPLE);
          draw_string (ni.name, marker_nx, ki.y);
          fnt.draw_char (ki.ch, marker_nx, ki.y + line_height);
        glEnable (GL_LINE_STIPPLE);
    	}
  	} else {
    	// mark 0 pitch bend
    	/*pts[0]=marker_x;pts[1]=0;
    	pts[2]=marker_x;pts[3]=view.ymax;
    	glVertexPointer (2, GL_INT, 0, pts);
    	glDrawArrays (GL_LINES, 0, 2);*/
      glBegin (GL_LINES);
        glVertex2i (marker_x, 0);
        glVertex2i (marker_x, view.ymax);
      glEnd ();
  	}
  }
  glLineWidth (1);
  glDisable (GL_LINE_STIPPLE);

	// show press keys & hear notes
	static const string PRESS = "Press  ", HEAR = " Hear ";
	glColor3f (1, 1, 1);
		draw_string (PRESS, pressx, ychars);
		draw_string (HEAR, hearx, ynotes);
  for (int i = 0, j = keys.size (); i < j; ++i) {
    key_info& ki = keys[i];
		note& ni = notes[i];
    glColor3f (ki.r, ki.g, ki.b);
    fnt.draw_char (ki.ch, ki.x, ychars);
		draw_string (ni.name, ki.x, ynotes);
    if (ki.attacked) { // hilite key that triggered last note
      glBegin (GL_LINES);
        glVertex2i (ki.x, ymarker);
        glVertex2i (ki.x + fnt.charwidth.max, ymarker);
      glEnd ();
      /*pts[0]=ki.x;pts[1]=ymarker;
      pts[2]=ki.x+fnt.charwidth.max;pts[3]=ymarker;
      glVertexPointer (2, GL_INT, 0, pts);
      glDrawArrays (GL_LINES, 0, 2);*/
    }
  }

}

void keyboard_keyboard::setup_midi_notes () {

  const float middlec = 261.626f;
  float start = middlec / 32;

  extern int NUM_INTERVALS;
  extern std::vector<float> INTERVAL_VALUES;

  int id = 0;
  while (1) {
    int j = NUM_INTERVALS - 1;
    for (int i = 0; i < j; ++i) {
      if (id < MIDI_MAX) {
        midi_notes[id].set_freq (start *  INTERVAL_VALUES [i]);
        id++;
      } else return;
    }
    start = start * INTERVAL_VALUES [j];
  }

}

void keyboard_keyboard::note_on (unsigned char id, unsigned char vel) {

  if (vel == 0) {
    note_off (id);
    return;
  }

  note& n = midi_notes [id];
	int i12 = id % 12;

  marker_x = mousex;
  triggered_notes.push_back (triggered_note (n, velsol(vel) * NOTE_VOLUME, keys[0].x + i12 * spacing, keys[0].y, trig_what, id, marker_x));
	triggered_note& last_triggered_note = *(--triggered_notes.end());
	last_triggered_note.setup (&wave, &attackcrv, &decaycrv);
	++num_triggered_notes;

}

void keyboard_keyboard::note_off (unsigned char id) {
  decay_note (id);
}

void keyboard_keyboard::pitch_bend (float v) { 
	for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
    triggered_note& ti = *i;
    float pv = PITCH_BEND * v;
    ti.set_freq (ti.start_hz + pv);
    ti.bend_x = fabs (pv);
  }
}

void keyboard_keyboard::calc_mouse_bend (triggered_note& ti) {
  int delta_x = mousex - ti.prev_mousex;
  float pv = delta_x * PITCH_BEND_PER_PIXEL;
  ti.set_freq (ti.start_hz + pv);
  ti.bend_x = fabs (pv);
}

void keyboard_keyboard::mouse_bend () {
  if (mousex != prev_mousex) {
  	for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
    	triggered_note& ti = *i;
			if (ti.bend) calc_mouse_bend (ti);
		}
    prev_mousex = mousex;
  }
}

void keyboard_keyboard::enter () {
  SDL_WarpMouse (prev_mousex, prev_mousey);
}

void keyboard_keyboard::tonic_changed () {
	setup_notes (0);
	calc_visual_params ();
}

void keyboard_keyboard::scale_changed () {
	load_scale ();
}

void keyboard_keyboard::scale_loaded () {
	load_scale ();
}

void keyboard_keyboard::load_scale (int dummy) {
	setup_notes ();
	setup_midi_notes ();
	calc_visual_params ();
}

int keyboard_keyboard::turn_off_bend () {
  for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
    triggered_note& ti = *i;
    ti.bend = 0; // turn of mouse based pitch bending
  }
	return 0;
}
