/*
 *  psychlops_exp_psychophysics.h
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2009/07/ by Kenchi HOSOKAWA
 *  (C) 2005 Kenchi HOSOKAWA, Kazushi MARUYA, Takao SATO
 */

#ifndef HEADER_PSYCHLOPS_EXPERIMENTAL_DESIGNS_PSYCHOPHYSICS
#define HEADER_PSYCHLOPS_EXPERIMENTAL_DESIGNS_PSYCHOPHYSICS

#include <deque>
#include <string>

#include "../../../core/graphic/psychlops_g_color.h"
#include "../../../core/graphic/psychlops_g_canvas.h"
#include "../widgets/psychlops_widget.h"

#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif

namespace Psychlops {


namespace ExperimentalMethods {

	class Variable;
	class SliderInterface : public FigureDatum
	{
	protected:
		void *body;
	public:
		SliderInterface();
		~SliderInterface();
		void set(Variable* target);
		virtual SliderInterface& set(double wid, double hei);
		virtual SliderInterface& set(std::string s, double hei = Font::default_font.size);
		virtual SliderInterface& set(std::wstring s, double hei = Font::default_font.size);
		virtual SliderInterface& setLabel(std::wstring s);

		virtual SliderInterface& centering(Drawable &target = *Drawable::prime);
		virtual SliderInterface& centering(const Figure &p);
		virtual SliderInterface& centering(const Point &p);
		virtual SliderInterface& centering(const double x, const double y, const double z = 0);
		virtual SliderInterface& shift(const double x, const double y, const double z = 0);
		virtual SliderInterface& draw(Drawable &target = *Drawable::prime);
		bool changed();
		operator double();
		void appendTo(Group &target);
	};

	class Variable {
		public:
		SliderInterface slider;

		std::string label;
		virtual ~Variable();
		virtual void* addr() const = 0;
		virtual std::string to_str() const = 0;
		virtual void to_str(std::ostream & outstream) const = 0;
		virtual void to_str_ColumnCell_at(int index, std::ostream & outstream) const = 0;
		//virtual void setByValue(double value) = 0;
		virtual void setByRatio(double ratio) = 0;
		virtual void setByRatioInStep(double ratio) = 0;
		virtual void setByLevel(int level) = 0;
		virtual void increment(int modulation = 0) = 0;
		virtual void decrement(int modulation = 0) = 0;
		virtual bool changed() = 0;
		//virtual void onChange(void (*callback)()) = 0;
		virtual double getRatio() const = 0;
		virtual Interval getInterval() const = 0;
		virtual Interval setInterval(const Interval &itvl) = 0;
		virtual int getNumberOfLevels() const = 0;
		virtual int getNumberOfSteps() const = 0;
		virtual void setColumn(int number_of_cells) = 0;
		virtual void setColumnCellByCurrentValue(int index) = 0;
		virtual void setColumnCellByLevel(int index, int level) = 0;
	};
	namespace VariableInstanceImpl {
		template<typename T> void initialize(Interval *i, std::vector<T> *step_) { *i=Interval(); step_->empty(); }
		template<typename T> void increment(T* const link_, T delta, Interval intvl) { if(intvl.includes(*link_+delta)) *link_ += delta; }
		template<typename T> void decrement(T* const link_, T delta, Interval intvl) { if(intvl.includes(*link_-delta)) *link_ -= delta; }
		template<typename T> double getRatio(T* const link_, Interval intvl) { return ( intvl.bounded() ? ((*link_-intvl.int_floor()) / (double)(intvl.int_ceil()-intvl.int_floor())) : 0); }
		template<typename T> void setByRatio(T* const link_, double ratio, Interval intvl) { *link_ = (T)Math::round(ratio*(intvl.int_ceil()-intvl.int_floor())+intvl.int_floor()); }
		template<typename T> void setByValue(T* const link_, double value) { *link_ = (T)value; }

		template<> void initialize<bool>(Interval *i, std::vector<bool> *step_);
		template<> void increment<bool>(bool* const link_, bool delta, Interval intvl);
		template<> void decrement<bool>(bool* const link_, bool delta, Interval intvl);
		template<> double getRatio<bool>(bool* const link_, Interval intvl);
		template<> void setByRatio<bool>(bool* const link_, double ratio, Interval intvl);

		template<> double getRatio<float>(float* const link_, Interval intvl);
		template<> void setByRatio<float>(float* const link_, double ratio, Interval intvl);
		template<> double getRatio<double>(double* const link_, Interval intvl);
		template<> void setByRatio<double>(double* const link_, double ratio, Interval intvl);
	}

	template<class X> class VariableInstance : public Variable {
		private:
		X * const link_;
		const X backup_;
		std::vector<X> step_;
		std::vector<X> column_;
		std::vector<X> levels_;
		int current_level_;
		bool changed_flag;
		public:
		Interval rng_;

		public:
		VariableInstance<X>(X *link) : link_(link), backup_(*link), current_level_(0), changed_flag(false) { VariableInstanceImpl::initialize(&rng_, &step_); }
		virtual ~VariableInstance<X>() {}
		virtual std::string to_str() const {
			std::stringstream tmpstr;
			tmpstr << *link_;
			return tmpstr.str();
		}
		virtual void* addr() const { return (void*)link_; }
		virtual void to_str(std::ostream & outstream) const { outstream << *link_; }
		virtual void to_str_ColumnCell_at(int index, std::ostream & outstream) const { outstream << column_.at(index); }
		virtual void setByRatio(double ratio) {
			changed_flag = true;
			double r = std::min(1.0, std::max(0.0, ratio));
			if(levels_.size()>1) { setByLevel(Math::round(r*(levels_.size()-1))); return; }
			if(rng_.bounded()) { VariableInstanceImpl::setByRatio(link_, r, rng_); }
		}
		virtual void setByRatioInStep(double ratio) {
			changed_flag = true;
			double r = std::min(1.0, std::max(0.0, ratio));
			double s = 1;
			if(step_.size()==1) s = step_[0]; else if(step_.size()==2) s = std::min(step_[0], step_[1]);
			if(levels_.size()>1) { setByLevel(Math::round(r*(levels_.size()-1))); return; }
			if(rng_.bounded()) {
				double step_levels = Math::round( (rng_.end.value - rng_.begin.value) / s );
				//VariableInstanceImpl::setByRatio(link_, Math::round(step_levels*r)/step_levels , rng_);
				VariableInstanceImpl::setByValue(link_, Math::round(step_levels*r)*s + rng_.begin.value);
			}
		}
		virtual void setByLevel(int level) {
			changed_flag = true;
			*link_ = levels_.at(level);
		}
		virtual void increment(int modulation = 0) {
			changed_flag = true;
			if(levels_.size()<2) { if(modulation>=0 && modulation<step_.size()) VariableInstanceImpl::increment(link_, (X)step_[modulation], rng_); }
			else { ++current_level_; if(current_level_>=levels_.size()) current_level_ = 0; setByLevel(current_level_); }
		}
		virtual void decrement(int modulation = 0) {
			changed_flag = true;
			if(levels_.size()<2) { if(modulation>=0 && modulation<step_.size()) VariableInstanceImpl::decrement(link_, (X)step_[modulation], rng_); }
			else { --current_level_; if(current_level_<0) current_level_ = levels_.size()-1; setByLevel(current_level_); }
		}
		virtual bool changed() {
			if(changed_flag) {
				changed_flag = false;
				return true;
			} else {
				return false;
			}
		}
		virtual double getRatio() const { return VariableInstanceImpl::getRatio(link_, rng_); }
		virtual Interval getInterval() const { return rng_; }
		virtual Interval setInterval(const Interval &itvl) { return rng_ = itvl; }
		virtual int getNumberOfLevels() const { return levels_.size(); }
		virtual int getNumberOfSteps() const { return step_.size(); }
		virtual void setColumn(int number_of_cells) { column_.assign(number_of_cells, X()); }
		virtual void setColumnCellByCurrentValue(int index) { column_.at(index) = *link_; }
		virtual void setColumnCellByLevel(int index, int level) { column_.at(index) = levels_.at(level); }
		template<typename T> VariableInstance<X>& operator |(T arg) { return set(arg); }
		VariableInstance<X>& set(const char *str) { label = str; slider.set(label); return *this; }
		VariableInstance<X>& set(const std::string &str) { label = str; slider.set(label); return *this; }
		VariableInstance<X>& set(const Interval &rng) { rng_ = rng; return *this; }
		VariableInstance<X>& set(const X &d_step) {
			step_.push_back(d_step);
			return *this;
		}
		VariableInstance<X>& set(const std::string &str, const Interval &rng, const X &d_step, const X &e_step = 0) {
			set(str);
			set(rng);
			set(d_step);
			set(e_step);
			return *this;
		}
		VariableInstance<X>& setLevels(const X *arr, const int size) {
			for(int i=0; i<size; i++) {
				levels_.push_back(arr[i]);
			}
			return *this;
		}
		VariableInstance<X>& operator ,(const X &level) {
			levels_.push_back(level);
			return *this;
		}
	};



	struct Variables {
		void* proc___;
		Group sliders_;
		bool slider_switch_;

		std::deque<Variable *> variables;
		Variables();
		~Variables();
		template <class X> VariableInstance<X>& append(X &item) {
			VariableInstance<X> * tmp = new VariableInstance<X>(&item);
			variables.push_back(tmp);
			return *tmp;
		}
		template <class X> VariableInstance<X>& operator <<(X &item) { return append(item); }
		Variable& operator [](const size_t index);
		Variable& operator [](const std::string &index);
		Variable& operator [](const char* index);
		Variable& operator [](const void* index);
		bool changed();
		void console(bool on_off);
		void slider(bool on_off);
	};

	struct VariableConsole : public FigureDatum {
		const Variables& o;
		mutable int active_, active_bar;
		Color fgcolor[2];
		Color bgcolor, barcolor, active_barcolor;

		VariableConsole(const Variables& d);
		virtual ~VariableConsole();
		virtual VariableConsole& draw(Drawable &target = *Drawable::prime);
	};


	class ExperimentalMethod {
		protected:
		std::map<std::string, Variable *> invariant_;
		Variables Independent, Dependent;
		std::string experiment_name, result_file_location, result_file_header;
		int ITERATION, NUM_TRIALS, CURRENT_TRIAL;

		virtual void initialize() = 0;
		virtual void initialize_default() = 0;
		virtual void trial() = 0;
		virtual void trial_default() = 0;
		virtual bool nextCondition() = 0;
		virtual void terminate();
		virtual void terminate_default() = 0;
		virtual void saveResults() = 0;

		virtual void initialize_wait();
		virtual void setExpname();

		public:
		ExperimentalMethod();
		virtual ~ExperimentalMethod();

		virtual void run();
	};


	class Demo : public ExperimentalMethod {
		protected:
		VariableConsole console;

		virtual void initialize() = 0;
		virtual void initialize_default();
		virtual void trial() = 0;
		virtual void trial_default();
		virtual bool nextCondition();
		virtual void terminate_default();
		virtual void terminate();
		virtual void saveResults();
//		virtual const Demo & draw();
//		virtual const Demo & draw(const double x, const double y, const double z = 0);

		Demo();
		virtual ~Demo();
	};

	class Constant : public ExperimentalMethod {
		protected:
		std::vector<int> conditions_;

		virtual void initialize() = 0;
		virtual void initialize_default();
		virtual void trial() = 0;
		virtual void trial_default();
		virtual bool nextCondition();
		virtual void terminate_default();

		virtual void saveResults();
		virtual void initialize_wait();

		//virtual int set(int vnum, int arraynum, double *array);
		//virtual void randomize(char* dataname=NULL);
		//virtual double get(int vnum, int trial_now);

		public:
		Constant();
		virtual ~Constant();
	};

}



	extern ExperimentalMethods::Variables Independent;
	class Procedure {
		public:
		enum DESIGN { DEMO };
		ExperimentalMethods::VariableConsole console_;

		private:
		void (*func_)();
		void (*func_canvas_)(Canvas &);
		int func_with_canvas_;
		DESIGN design_;

		public:
		Procedure();
		void setDesign(DESIGN d);
		void setProcedure(void (*func)());
		void setProcedure(void (*func)(Canvas &));
		void run();
		void run(Canvas &);
		void console(bool on_off);
	};



}	/*	<- namespace Psycholops 	*/


#endif
