/*
* mondrian.h
* inspired by the works of Piet Mondrian
* DIN Is Noise is released under GNU Public License 2.0
* DIN Is Noise is copyright (c) 2006-2024 Jagannathan Sampath
* For more information, please visit https://dinisnoise.org/
*/

#ifndef __mondrian__
#define __mondrian__

#include "point.h"
#include "viewwin.h"
#include "ui.h"
#include "box_selector.h"
#include "textboard.h"
#include "triggered_note.h"
#include "listeners.h"
#include "curve_editor.h"
#include "help.h"
#include "instrument.h"
#include "rect.h"
#include "ball.h"
#include "slit.h"
#include "alarm.h"
#include "spinner.h"
#include <string>
#include <vector>

typedef std::list<ball*>::iterator balls_iterator;

struct draw_ball_t {
	int position;
	int heading;
	int trails;
	draw_ball_t () { position = heading = trails = 1;}
};

struct field;
struct mondrian : instrument, scale_listener, region_listener, split_listener, typing_listener {

  mondrian ();
  ~mondrian ();

  void enter ();
  void leave ();
  void typing (field& f);

  multi_curve wave; // waveform shared by all balls
  curve_editor waved;  // waveform editor
	wave_listener wavlis;
	void update_waveform (multi_curve& crv);

	multi_curve attack, decay; // attack and decay curves shared by all balls
	curve_editor attacked, decayed; // attack and decay curve editors
	attack_listener attacklis;
	decay_listener decaylis;
	void update_attack ();
	void update_decay ();
	float delta_attack_time, delta_decay_time;

  window win; // edit window
	// object space <> window space mapping
	//
  point<int> win_chunk;
  point<float> obj_chunk;
  point<float> win_per_obj;
  point<float> obj_per_win;
  float win_resolution;
  float obj_resolution;
  void obj2win (const point<float>& p, float& wx, float& wy);
  void obj2win (const float& ox, const float& oy, float& wx, float& wy);
  void win2obj (const float& dx, const float& dy, float& cx, float& cy);

  int pan, zoom; 
  void do_panx (int dir);
  void do_pany (int dir);
  void do_zoom (int dir);

  void calc_win_mouse ();
	int lmb_clicked;

	//
	// file
  void load_settings ();
  void load_settings (std::ifstream& file);
  void save_settings (std::ofstream& file);
	box_from_disk_t get_box_from_disk (std::list<box_from_disk_t>& boxes, const std::string& id);
	void load_boxes_and_balls ();
	void save_boxes (std::ofstream& f, rect* R, std::string& id);
	void save_balls (std::ofstream& f);

  void setup ();

  int handle_input ();

	// draw
	//

	int draw__boxes;
	int fill_boxes;
	int label_notes;
	int label_hz_vol;
	int draw__notes;
	int draw_slit_cutter;
	draw_ball_t draw_ball;

	void draw_rect (rect* which);
	void draw_leaves ();
	void draw_balls ();
	void draw_notes ();
  void draw ();

	// boxes
	//

  rect* root; // the main box
	std::list <rect*> leaves; // boxes that dont have children and can bear slits
  rect* get_sibling (rect* R); // return sibling of R
  void find (rect* R, float x, float y, finding& f); // find box where x,y is found. starting with R and down
  rect* box_under_cursor ();
  finding make_finding (rect* R); // R and its sibling
  int bad_rect (rect* R) { return R == 0 || R == root; }

  // box splitting
  //

	rect* split_rect (int type, float where, rect* B = 0); // split box [type = horizontal or vertical]
	int num_boxes;
	int get_note_ids (std::pair<int, int>& nids, rect* R, int type);
  void get_note_ids_from_intervals (std::pair<int, int>& idp, const std::pair<float, float>& ip);
	int multi_split_rect (split_data& sd);
	void multi_split_rect (int n, int type, rect* B = 0, std::list<rect*> *pr = 0, split_listener* sl = 0); // split B [or current] into n boxes
	void multi_split_rect (int type, rect* B = 0, std::list<rect*> *lr = 0, split_listener* sl = 0); // split B [or current] at note intervals
  int splitting_rects;
  std::list<split_data> lsd;
  void split_over (split_data& sd);
  std::list<rect*> columns;
	void make_note_grid (); // split the root box both horizontally and vertically at the notes of the scale 
	void make_nxn_grid (); // split the root box into a N x N grid of boxes

  int nleaves;
	void add_leaf (rect* L); // splitting a box makes 2 leaves
	void remove_leaf (rect* L); 

  rect* earliest_leaf ();
  rect* latest_leaf ();
  rect* rnd_leaf (); 
  rect* biggest_leaf ();
	rect* balled_leaf ();
  rect* pick_leaf (int& what);
	int split_leaf, delete_leaf;

	void recreate_slits (rect* b); // to recreate slits of a just deleted box in its two children ie leaves

	int delete_rect (const finding& f); // delete found box
  void delete_current_rect (); // delete box under cursor
	void update_children (rect* R); // update extents of children of box R
	void delete_children (rect* R); // delete children of box R
	void update_parent (rect* C); // update parent (and ancestors) of box C 
  int delete_all_rects; // when active, once every frame

  alarm_t auto_del_rect; // auto delete box?
  alarm_t auto_split_rect; // auto split box?

  int auto_split_orient;
  int auto_split_at;
  int auto_split_which;


  rect* hit; // box hit when clicked
  int edge; // edge hit
  void set_edge (rect* R, int e, float x, float y); // set vertical (using x) or horizontal edge (using y) of box R

	int sel_tar; // selection target
	enum {SELECT_SLITS = 0, SELECT_BALLS};

	// balls
  //
  std::list<ball*> balls; 
	int num_balls; 

  int adding_balls;
	int added_ball_type;
  int started_making_ball;
  ball* new_ball;
	void do_add_balls (int _type = ball::BOUNCER);

  void split_balls (rect* P, rect* C1, rect* C2); // split balls among children C1 and C2 when parent P is deleted

	void delete_ball (ball* b);
	void delete_all_balls ();
	void delete_selected_balls ();

  float delta_speed;
	void change_speed (spinner<float>& s, float d);

  // ball selection
  //
  std::list<ball*> selected_balls;
  int num_selected_balls;
  void select_balls (const box<float>& rgn);
  void select_box_balls ();
	void after_selection ();
	void select_type (int t);
	struct browse_t {
		int which, last, n;
		std::vector<ball*> balls;
		browse_t ();
		void clear ();
	} browse;
	void browse_ball (int i);

	std::list<ball*>& get_box_balls ();
  std::list<ball*>& get_balls ();
	std::list<ball*>& get_balls (int);
	ball* get_one_selected_ball ();
	void switch_balls_type ();

	// freeze and thaw
  void freeze_thaw_balls (std::list<ball*>& _balls);
  void freeze_balls (std::list<ball*>& _balls);
  void thaw_balls (std::list<ball*>& _balls);
  void freeze_all_balls ();

	void clear_modulations (std::list<ball*>& _balls);

	// ball movement
  int moving_balls;
  void move_balls (float dx, float dy);
  void do_move_balls ();
	void locate_ball (ball* b);

	// ball course
	float delta_rotate_velocity;
	void rotate_velocity (int dir);
	void toggle_auto_rotate (int ar);
	void set_auto_rotate (int ar);
	void flip_velocity ();
	void change_ball_dtheta (int dir);

	// ball misc
	void set_ball_param (int what, float value);
	void change_ball_vol_mult (spinner<float>& s);
	void clone_ball (ball* b);
	void toggle_triggered_sound ();

	// slits
	//

	enum {NOTHING, JUST_SLIT, ANIMATE_SLIT};
	int slitting;
	fader fdr; // for animating slit

	int num_slits; 
	std::list<slit*> slits; // all slits
	std::list<slit*> selected_slits;
	int num_selected_slits;

	void load_slits (std::ifstream& f, std::list<box_from_disk_t>& boxes);
	void save_slits (std::ofstream& f);
	int get_index_of_slit (slit* s);
	slit* get_slit_from_index (int q);

	rect* bxs[2]; // a slit can only be on 2 boxes 

	int find_hit_slit (float x, float y); // under cursor

	slit_lip_t slit_lip;
	int editing_slit;
	int editing_edge;

	void start_slitting ();
	int try_slitting ();
	int add_remove_slit (float x, float y, fader* fdr = 0, float sz = slit::HALF_SIZE);
	void change_slit_size (spinner<float>& s);
	void change_slit_anim_time (spinner<float>& s);
	void remove_all_slits ();
	void remove_selected_slits ();
	void remove_slit (slit* s);
	void remove_slits (rect* R);
	void remove_slits_on_edge (rect* R, int e);
	void remove_slits_on_current_edge ();
	void remove_slits_on_current_box ();
	void remove_slits_on_boxes_with_balls ();
	void select_box_slits ();
	void toggle_slit_anim ();
	void remove_slit_anim ();
	void start_slit_anim ();

	void clear_selected_targets ();
	void select_all_targets ();
	void select_box_targets ();
	void invert_selected_targets ();
	void delete_selected_targets ();
	void delete_all_targets ();
  
  // visual
  //
  int n_pts;
  float* pts; // balls are points
	float* pts_d; // ball direction are lines
  float* pts_clr; // ball colors
  int n_mrk;
  float* mrk;
  float crsr [8];
  int cursor;
  textboard tb, tb_hz_vol;
  void make_notes ();
  void calc_visual_params ();
  void randomise_box_color (); // of the box under cursor
  void toggle_flag (int& flag, const std::string& poz, const std::string& neg = "");

  // opengl polygon stipple box fill
  //
  static unsigned char patbuf [1024];
  static std::string patstr;
  static int patstep, patlen;
  void fillpatbuf ();

	slit_drawer slit_drawerr;

	static const float gutter, gutter2; // for edge selection & slit making
  static float min_split_size; // for auto box splitting

	struct poly_t {
		std::vector<float> coss, sinn;
		int points;
		float radius;
		float delta_radius;
		poly_t () {
			radius = 0;
			delta_radius = 0;
			points = 0;
		}
	} poly;
	int set_note_poly_points (int p);
	int set_note_poly_radius (float r);

  // notes
  float note_volume;
	int voices;
	int min_voices;
	int auto_adjust_voices;
  note N;
	int num_triggered_notes;
  std::list<triggered_note> triggered_notes;
	std::list<ball*> triggering_balls; 
  void launch_note (ball* _ball, float t, float t0, float dt, const std::pair<float, float>& invl);
	void remove_finished_notes ();
	void print_num_triggered_notes ();
	int change_param (float& param, float value, float& recent);
	void change_attack_time_kb (spinner<float>& s); // using kb shortcut
	void change_decay_time_kb (spinner<float>& s);
	void change_trail_size (spinner<int>& s);
	void change_min_voices (int d);

	// others
	//

	int stop_editing_slit ();
	int stop_editing_edge ();
	int stop_adding_balls ();
	int stop_moving_balls ();
	int stop_slitting ();
	int stop_doing_stuff ();
  int recting () {return editing_edge || editing_slit || splitting_rects; }

	void scale_changed ();
	void scale_loaded ();
	void tonic_changed ();

	int modulate_balls (int p);

	void toggle_balls_type (int _type);

  int render_audio (float* L, float* R);

	void bg ();

	void mark_selected_slits ();

	box<float> rgn;
	void region_begin ();
	void region_end ();
	const box<float>& region_update ();

  help _help;

};

int edge_on_root (rect* root, rect** boxes, int* edges);
void draw_slit_cutter (int yesno);

extern mondrian mondrian0;

template<class T> void clear_selected (list<T*>& targets, int& num_targets, int aft_sel = 1) {
  for (typename list<T*>::iterator p = targets.begin (), q = targets.end (); p != q; ++p) {
    T* t = *p;
    t->select = 0;
  }
	targets.clear ();
	num_targets = 0;
	if (aft_sel) mondrian0.after_selection ();
}

template <class T> void select_all (list<T*>& items, list<T*>& selection, int& num_selected) {
	clear_selected<T> (selection, num_selected);
  for (typename list<T*>::iterator p = items.begin (), q = items.end (); p != q; ++p) {
		T* t = *p;
		t->select = 1;
		selection.push_back (t);
		++num_selected;
	}
	mondrian0.after_selection ();
}

template <class T> void invert_selection (list<T*>& items, list<T*>& selection, int& num_selected) {
	num_selected = 0;
	selection.clear ();
	for (typename list<T*>::iterator p = items.begin(), q = items.end(); p != q; ++p) {
    T* b = *p;
    b->select = !b->select;
		if (b->select) {
			selection.push_back (b);
			++num_selected;
		}
  }
	mondrian0.after_selection ();
}

template <class T> void select_using_modifiers (T* b, int ctrl, list<T*>& selection, int& num_selected) {
	typename list<T*>::iterator se = selection.end (), f = ::find (selection.begin (), se, b);
	if (f == se) {
		sel:
		b->select = 1;
		push_back (selection, b);
		++num_selected;
	} else {
		if (ctrl) {
			if (b->select) {
				b->select = 0;
				selection.erase (f);
				--num_selected;
			} else 
				goto sel;
		}
	}
}


#endif
