/*
* spinner2.h
* DIN Is Noise is copyright (c) 2006-2024 Jagannathan Sampath
* For more information, please visit http://dinisnoise.org/
*/


#ifndef __spinner22
#define __spinner22

#include "input.h"
#include "font.h"
#include "widget.h"
#include "label.h"
#include "arrow_button.h"
#include "checkbutton.h"
#include "field.h"
#include "utils.h"
#include "mouse_slider.h"
#include "tokenizer.h"

#include <string>
#include <typeinfo>

extern void abort_selectors ();
extern int SPACING;
extern const char spc;
extern char BUFFER[];

extern button& detach_from_menu (widget** wa, int n, int posx, int posy);
extern void attach_to_menu (widget** wa, int n);

template <typename T> struct spinner2 : widget, state_listener, change_listener<field>, typing_listener, mouse_slider_listener, nullt {

	template <typename Q> struct re_pre_lis : click_listener {
		spinner2<Q>& sp;
		click_listener* pre;
		re_pre_lis (spinner2<Q>& _sp, click_listener* _pre) : sp (_sp), pre (_pre) {}
		void clicked (button& b) {
      widget* w[] = {&sp};
			attach_to_menu (w, 1);
			LISTEN (sp.dec, pre)
			LISTEN (sp.inc, pre)
      if (sp.null) sp.set_value (0);
		}
	};

	template <typename Q> struct pre_lis : click_listener {
		spinner2<Q>& sp;
		click_listener *decl, *incl;
		pre_lis (spinner2<Q>& _sp, click_listener* _decl, click_listener* _incl) : sp (_sp), decl (_decl), incl (_incl) {}
		void clicked (button& b) { 
			sp.dec.set_listener (decl);
			sp.inc.set_listener (incl);
			widget* wa [] = {&sp};
			button& b_close = detach_from_menu (wa, 1, sp.dec.posx, sp.lbl.posy);
			LISTEN (b_close, &sp.reprel);
      b.call_listener ();
		}
	};

	template <typename Q> struct dec_lis : click_listener {
		spinner2<Q>& sp;
		dec_lis (spinner2<Q>& _sp) : sp (_sp) {}
		void clicked (button& b) {sp.decrease ();}
	};

	template <typename Q> struct inc_lis : click_listener {
		spinner2<Q>& sp;
		inc_lis (spinner2<Q>& _sp) : sp (_sp) {}
		void clicked (button& b) {sp.increase ();}
	};

	template <typename Q> struct more_lis : click_listener {
		spinner2<Q>& sp;
		more_lis (spinner2<Q>& _sp) : sp (_sp) {}
		void clicked (button& b) {sp.toggle_delta();}
	};

	template <typename Q> struct delta_lis : change_listener<field> {
		spinner2<Q>* sp;
		delta_lis (spinner2<Q>* _sp = 0) : sp (_sp) {}
		void changed (field& f) {
			sp->delta0 = sp->delta = f;
			sp->call_listener (1, f);
		}
	};

	template <typename Q> struct variance_lis : change_listener<field> {
		spinner2<Q>* sp;
		variance_lis (spinner2<Q>* _sp = 0) : sp (_sp) {}
		void changed (field& f) {
			tokenizer tz (f.text);
			float mn, mx; tz >> mn >> mx;
			sp->variance.setrd (mn, mx);
      sp->set_pos (sp->posx, sp->posy);
			sp->call_listener (2, f);
		}
	};

  checkbutton lbl; // main label

	label backlbl; // back label, after field
	int draw_backlbl;

  // increase/decrease buttons
  arrow_button dec, inc;
  void updowndecinc () {
    dec.dir = arrow_button::down;
    inc.dir = arrow_button::up;
  }

	dec_lis<T> decl;
	inc_lis<T> incl;
  int dir; // < 0 = decrease, > 0 = increase

  // pre listeners so can detach from menu
	int pre;
	pre_lis<T> prel;
	re_pre_lis<T> reprel;

  // value
  field f_value;
  T value;
	T lastv;

	int draw_more;
  arrow_button more;
	more_lis<T> mol;

  label l_delta;
  field f_delta;
	delta_lis<T> dell;

	struct variancet {
		checkbutton cb;
		label lbl;
		field fld;
		variance_lis<T> lis;
		rnd<float> rd;
		int ui;
		variancet (spinner2<T>* sp = 0) : lis (sp) {
			lbl.set_text (" ~ ");
			fld.change_lsnr = &lis;
			fld.expr = 0;
      cb.set_text ("~");
			ui = 1;
		}
		void setrd (float s, float t) {
			// assume s,t in %
			s /= 100.0f;
			t /= 100.0f;
			rd.set (s, t);
		}
    void setfld () {
      int i = 100 * rd.min, j = 100 * rd.max;
      sprintf (BUFFER, "%d %d", i, j);
      fld.set_text (BUFFER);
    }
	} variance;

  T variedval () {
    return variance.rd () * value;
  }

  int limits;
  T lo, hi;

  void set_limits (T _lo, T _hi) {
		limits = 1;
    lo = _lo;
    hi = _hi;
  }

  change_listener<field> *lis[3]; // 0 - f_value, 1 - f_delta, 2 - variance.fld = null

  spinner2 (const std::string& _name = "unknown") : 
	decl(*this), incl(*this), 
	pre(1),
	prel (*this, &decl, &incl), 
	reprel (*this, &prel),
	mol(*this),
	l_delta ("+-"),  dell(this), variance(this) {
		
#ifndef __WIDGET_MOVE__
		lbl.set_listener (this);
#endif

    widget* chld [] = {
			this, 
			&dec, 
			&inc, 
			&f_value, 
			&more, 
			&l_delta, 
			&f_delta, 
			&variance.cb, 
			&variance.lbl, 
			&variance.fld
		};
    for (int i = 0; i < 10; ++i) lbl.add_child (chld[i]);

    dec.set_dir (arrow_button::left);
    inc.set_dir (arrow_button::right);
		inc.click_repeat = 1;
		dec.click_repeat = 1;

		dir = 1;
		draw_more = 1;
    more.set_dir (arrow_button::right);

		set_pre (pre);

		LISTEN (more,&mol)

    l_delta.hide ();
    f_delta.hide ();

		lastv = value = 0;

    f_value.change_lsnr = this;
		f_delta.change_lsnr = &dell;


		f_value.typing_lsnr = variance.fld.typing_lsnr = this;

		lis [0]=lis[1]=lis[2]=0;

    limits = 0;
    lo = hi = 0;

		// see field::call_listener ()
		const std::type_info& ti = typeid (T);
		std::string tn (ti.name());
		if (tn == "i") 
			f_value.type = f_delta.type = "int";
		else if (tn == "f") 
			f_value.type = f_delta.type = "double";

		draw_backlbl = 0;

    vary = &variance.cb;

  }

  void set_pos (int x, int y) {
    widget::set_pos (x, y);
		int i = 0;
		if (draw_more == 0 || variance.ui == 0) i = 1; 
    widget* w [] = {&variance.cb, &lbl, &dec, &inc, &f_value, &more, &variance.lbl, &variance.fld, &l_delta, &f_delta, };
    int xshift [] = {0, 0, 0, -1, 1, -1, 0, 0, 5, 1};
    int lft [] = {0, 0, fnt.lift, fnt.lift, 0, fnt.lift, 0, 0, 0, 0};
    for (; i < 10; ++i) {
			x += xshift [i];
			widget* wi = w[i];
			wi->set_pos (x, y + lft[i]);
			advance_right (x, *wi, SPACING);
    }
		set_pos_backlbl ();
  }

	void set_pos_backlbl () {
		if (draw_backlbl) {
			widget* w = 0;
			if (f_delta.visible) w = &f_delta; 
			else if (draw_more) w = &more;
			else w = &f_value;
			backlbl.set_pos (w->extents.right + SPACING, w->extents.bottom - fnt.lift);
		}
	}

  void update () {
    lbl.update ();
		backlbl.update ();
    l_delta.update ();
    f_value.update ();
    f_delta.update ();
		variance.cb.update ();
		variance.lbl.update ();
		variance.fld.update ();
    set_pos (posx, posy);
		inc.update ();
		dec.update ();
		more.update ();
  }

  void toggle_delta () {
    if (more.dir == arrow_button::right) {
      l_delta.show ();
      f_delta.show ();
			if (variance.ui) {
				variance.lbl.show ();
				variance.fld.show ();
			}
      more.set_dir (arrow_button::left);
    } else {
      l_delta.hide ();
      f_delta.hide ();
			if (variance.ui) {
				variance.lbl.hide ();
				variance.fld.hide ();
			}
      more.set_dir (arrow_button::right);
    }
		set_pos_backlbl ();
		abort_selectors ();
  }
  
  void change_value (int _dir, double scl = 1.0) {
		dir = _dir;
		delta = scl * delta0;
		value += dir_delta (); 
		f_value = value;
		changed (f_value);
	}

	void decrease () {
		change_value (-1);
	}

	void increase () {
		change_value (+1);
	}

	spinner2<T>& operator++ () {
		change_value (+1);
		return *this;
	}

	spinner2<T>& operator-- () {
		change_value (-1);
		return *this;
	}

	void changed (checkbutton& cb) {
		if (orient == NONE) {
			cb.turn_off (0);
			if (mouse_slider0.active) cant_mouse_slide ();
		} else {
			if (cb.state) mouse_slider0.add (this); else mouse_slider0.remove (this);
			if (SHIFT == 0) activate_mouse_slider ();
		}
	}

  void changed (field& f) {
		if (limits) {
			T v = f;
			if (clamp<T>(lo, v, hi)) f = v;
		} 
		value = f;
		if (f.edited) {
			delta = value - lastv;
      if (delta > 0) {
        dir = 1;
      } else {
        dir = -1;
        delta = -delta;
      }
		}
		lastv = value;
		set_pos (posx, posy);
		call_listener (0, f);
  }

	void call_listener (int i, field& f) {
		change_listener<field>* lisi = lis[i];
		if (lisi) lisi->changed (f);
	}

  void typing (field& f) {
    f.update ();
    set_pos (posx, posy);
  }

	void moused (int _dir, double scl) {
		change_value (_dir, scl);
	}

	void after_slide () {
		lbl.turn_off (DONT_CALL_LISTENER);
    set_delta (delta0 * mouse_slider0.scale.value);
    if (null) set_value (0);
	}

  void draw () {
    glColor3f (clr.r, clr.g, clr.b);
		widget* w [] = {&lbl, &dec, &inc, &f_value};
		for (int i = 0, j = 4; i < j; ++i) w[i]->draw ();
		if (variance.ui) variance.cb.draw ();
		if (draw_more) {
			more.draw ();
			if (more.dir == arrow_button::left) {
				l_delta.draw ();
				f_delta.draw ();
				if (variance.ui) {
					variance.lbl.draw ();
					variance.fld.draw ();
				}
			}
		}
		if (draw_backlbl) backlbl.draw ();
  }

  void set_value (T t) {
		value = t;
		lastv = value;
    f_value = value;
		set_pos (posx, posy);
  }

  void set_delta (T t) {
		delta0 = delta = t;
    f_delta = (T) delta0;
		set_pos (posx, posy);
  }

  void set_listener (change_listener<field>* _lis, int id = 0) {
    lis [id] = _lis;
  }

  void set_text (const std::string& l, const std::string& bl = "") {
    lbl.set_text (l);
    set_name (l);
		mouse_slider_listener::name = l;
		if (bl != "") {
			backlbl.set_text (bl);
			draw_backlbl = 1;
		}
  }

	void set_moveable (int m, int mc = 0, int* pmb = &lmb) {lbl.set_moveable (m, mc, pmb);}

	int handle_input () {

		int r = lbl.handle_input (); 
		if (r) return r;

		if (variance.ui && variance.cb.handle_input()) return 1;

		int d1 = 0, i1 = 0, m1 = 0;
		d1 = dec.handle_input ();
		if (d1 == 0) {
			i1 = inc.handle_input ();
			if (i1 == 0) 
				m1 = more.handle_input ();
		}

		int c = d1 | i1 | m1;
		if (c) return c;

		int s = f_value.handle_input ();
		if (s) return s;

  	if (more.dir == arrow_button::left) {
			int fd = f_delta.handle_input ();
			if (fd) return fd;
			if (variance.ui) {
				if (variance.fld.handle_input()) return 1;
			}
		}

		return 0;

	}

	inline T dir_delta () {
		return dir * delta;
	}

	inline T operator() () {
    if (variance.cb.state) return ( variance.rd () * value ); else return value;
	}
	
	void set (const std::string& t, T d, T lmin, T lmax, change_listener<field>* l = 0, int _pre = 1) {
		set_text (t);
		set_delta (d);
		set_limits (lmin, lmax);
		set_listener (l);
		set_pre (_pre);
	}

	void set (const std::string& t, T d, change_listener<field>* l) {
		set_text (t);
		set_delta (d);
    set_listener (l);
	}

  void set (T d, T lmin, T lmax, change_listener<field>* l) {
    set_delta (d);
    set_limits (lmin, lmax);
    set_listener (l);
  }

	void set_pre (int _pre) {
		pre = _pre;
		if (pre) {
			LISTEN (dec,&prel)
			LISTEN (inc,&prel)
		} else {
			LISTEN (dec,&decl)
			LISTEN (inc,&incl)
		}
	}

};

template<> inline int spinner2<int>::operator() () {
  if (variance.cb.state) return ( variance.rd () * value + 0.5f); else return value;
}

template <typename T> ifstream& operator>> (ifstream& f, spinner2<T>& spn) {

  int state;
  float minn, maxx;

  f >> state >> minn >> maxx;

  spn.variance.cb.set_state (state, DONT_CALL_LISTENER);
  spn.variance.rd.set (minn, maxx);
  spn.variance.setfld ();

  return f;

}

template <typename T> ofstream& operator<< (ofstream& f, spinner2<T>& spn) {
  f << spn.variance.cb.state << spc << spn.variance.rd.min << spc << spn.variance.rd.max << spc;
  return f;
}

template <class P, class Q> Q getval (spinner2<P>& sp, Q& src) {
  typename spinner2<P>::variancet& var = sp.variance;
  if (var.cb.state) 
    return var.rd () * src;
  else
    return src;
}
#endif
