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


#include "../../../psychlops_core.h"
#include "../widgets/psychlops_widget.h"
#include "../widgets/psychlops_widgets.h"
#include "psychlops_exp_psychophysics.h"

#include <algorithm>
#include <deque>
#include <fstream>
#include <typeinfo>
#include <stdio.h>
#include <string.h>


namespace Psychlops {

namespace ExperimentalMethods {


	SliderInterface::SliderInterface()
	{
		body = 0;
	}
	void SliderInterface::set(Variable* target)
	{
		if(body!=0) return;
		Widgets::Slider *s = new Widgets::Slider();
		s->linkTo(target);
		body = s;
		set(target->label);
		//set(target->label.length()*Font::default_font.size, Font::default_font.size+4);
	}
	SliderInterface::~SliderInterface()
	{
		if(body==0) return;
		Widgets::Slider *s = (Widgets::Slider *)body;
		delete s;
		body = 0;
	}
	SliderInterface& SliderInterface::set(double wid, double hei)
	{
		if(body==0) return *this;
		Widgets::Slider *s = (Widgets::Slider *)body;
		s->setSize(wid, hei);
		return *this;
	}
	SliderInterface& SliderInterface::set(std::string s, double hei)
	{
		if(body==0) return *this;
		std::wstring temp(s.length(),L' ');
		std::copy(s.begin(), s.end(), temp.begin());
		return set(temp, hei);
		return *this;
	}
	SliderInterface& SliderInterface::set(std::wstring ws, double hei)
	{
		if(body==0) return *this;
		Widgets::Slider *s = (Widgets::Slider *)body;
		s->setLabel(ws);
		//s->set(ws, hei);
		return *this;
	}
	SliderInterface& SliderInterface::centering(Drawable &target) { return centering(target.getCenter()); }
	SliderInterface& SliderInterface::centering(const Figure &target) { return centering(target.getDatum()); }
	SliderInterface& SliderInterface::centering(const Point &p) { return centering(p.x, p.y, p.z); }
	SliderInterface& SliderInterface::centering(const double x, const double y, const double z)
	{
		if(body==0) return *this;
		Widgets::Slider *s = (Widgets::Slider *)body;
		s->shift(x,y,z);
		return *this;
	}
	SliderInterface& SliderInterface::shift(const double x, const double y, const double z)
	{
		if(body==0) return *this;
		Widgets::Slider *s = (Widgets::Slider *)body;
		s->shift(x,y,z);
		return *this;
	}
	SliderInterface& SliderInterface::setLabel(std::wstring ws)
	{
		if(body==0) return *this;
		Widgets::Slider *s = (Widgets::Slider *)body;
		s->setLabel(ws);
		return *this;
	}
	SliderInterface& SliderInterface::draw(Drawable &target)
	{
		if(body==0) return *this;
		Widgets::Slider *s = (Widgets::Slider *)body;
		s->draw(target);
		return *this;
	}

	bool SliderInterface::changed()
	{
		if(body==0) return false;
		Widgets::Slider *s = (Widgets::Slider *)body;
		return s->changed();
	}
	SliderInterface::operator double()
	{
		if(body==0) return 0;
		Widgets::Slider *s = (Widgets::Slider *)body;
		return (*s);
	}
	void SliderInterface::appendTo(Group &target)
	{
		if(body==0) return;
		//std::cout << 0;
		Widgets::Slider *s = (Widgets::Slider *)body;
		target.append(s);
	}




	typedef std::deque<Variable *>::iterator VariableList;

	Variable::~Variable() {}
	namespace VariableInstanceImpl {
		template<> void initialize<bool>(Interval *i, std::vector<bool> *step_) { Interval intvl; 0<intvl<=1; *i=intvl; step_->push_back(true); }
		template<> void increment<bool>(bool* const link_, bool delta, Interval intvl) { *link_ = !(*link_); }
		template<> void decrement<bool>(bool* const link_, bool delta, Interval intvl) { *link_ = !(*link_); }
		template<> double getRatio<bool>(bool* const link_, Interval intvl) { return *link_ ? 1 : 0; }
		template<> void setByRatio<bool>(bool* const link_, double ratio, Interval intvl) { *link_ = ratio<0.5 ? false : true; }

		template<> double getRatio<float>(float* const link_, Interval intvl) { return ( intvl.bounded() ? ((*link_-intvl.begin.value) / (intvl.end.value-intvl.begin.value)) : 0); }
		template<> void setByRatio<float>(float* const link_, double ratio, Interval intvl) { *link_ = (float)(ratio*(intvl.end.value-intvl.begin.value)+intvl.begin.value); }
		template<> double getRatio<double>(double* const link_, Interval intvl) { return ( intvl.bounded() ? ((*link_-intvl.begin.value) / (intvl.end.value-intvl.begin.value)) : 0); }
		template<> void setByRatio<double>(double* const link_, double ratio, Interval intvl) { *link_ = (double)(ratio*(intvl.end.value-intvl.begin.value)+intvl.begin.value); }
/*
		template<> double getRatio<float>(float* const link_, Interval intvl) {
			if(levels_.size()<2) { return ( intvl.bounded() ? ((*link_-intvl.begin.value) / (intvl.end.value-intvl.begin.value)) : 0); }
			else { return (double)current_level_ / levels_.size(); }
		}
		template<> void setByRatio<float>(float* const link_, double ratio, Interval intvl) {
			if(levels_.size()<2) { *link_ = (float)(ratio*(intvl.end.value-intvl.begin.value)+intvl.begin.value); }
			else { current_level_ = (int)Math::round(ratio*levels_.size()); setByLevel(current_level_); }
		}
		template<> double getRatio<double>(double* const link_, Interval intvl) {
			if(levels_.size()<2) { return ( intvl.bounded() ? ((*link_-intvl.begin.value) / (intvl.end.value-intvl.begin.value)) : 0); }
			else { return (double)current_level_ / levels_.size(); }
		}
		template<> void setByRatio<double>(double* const link_, double ratio, Interval intvl) {
			if(levels_.size()<2) { *link_ = (double)(ratio*(intvl.end.value-intvl.begin.value)+intvl.begin.value); }
			else { current_level_ = (int)Math::round(ratio*levels_.size()); setByLevel(current_level_); }
		}
*/
	}


	////////	ExperimentalVariables	////////

	Variables::Variables()
	{
		proc___ = 0;
		slider_switch_ = false;
	}
	Variables::~Variables() {
		if(!variables.empty()) {
			for(VariableList i = variables.begin(); i!=variables.end(); i++) {
				delete *i;
			}
		}
	}
	Variable& Variables::operator [](const size_t index) {
		return *(variables.at(index));
	}
	Variable& Variables::operator [](const std::string &index) {
		return operator [](index.c_str());
	}
	Variable& Variables::operator [](const char* index) {
		for(size_t i=0; i<variables.size(); i++) {
			if(0==strcmp(variables.at(i)->label.c_str(), index)) return *(variables.at(i));
		}
		std::string err("No variable named as ");
		err.append(index);
		err.append(" was found.");
		throw new Exception(err);
	}
	Variable& Variables::operator [](const void* index) {
		for(size_t i=0; i<variables.size(); i++) {
			if(variables.at(i)->addr()==index) return *(variables.at(i));
		}
		throw new Exception("No variable which has designated address was found.");
	}
	bool Variables::changed() {
		bool flag = false;
		for(size_t i=0; i<variables.size(); i++) flag |= variables.at(i)->changed();
		return flag;
	}
	void Variables::console(bool on_off)
	{
		if(proc___!=0)
		{
			Procedure * p = (Procedure *)proc___;
			p->console(on_off);
		}
	}
	void Variables::slider(bool on_off)
	{
		if(slider_switch_==false)
		{
			if(!variables.empty()) {
				for(VariableList i = variables.begin(); i!=variables.end(); i++) {
					(**i).slider.set(*i);
					sliders_.append((**i).slider);
				}
			}
			if(Drawable::prime==Drawable::dummy) {
				Drawable::billboard.append(sliders_);
			} else {
				dynamic_cast<Canvas*>(Drawable::prime)->billboard.push_back(&sliders_);
			}
			Mouse::show();
			slider_switch_ = true;
		}
	}


	////////	VariableConsole	////////
	VariableConsole::VariableConsole(const Variables& v)
	 : active_(0), active_bar(0), o(v), bgcolor(Color(0.5,0.5,0.5,0.5)), barcolor(Color(0.2,0.2,8.0,0.4)), active_barcolor(Color(0.0,0.0,1.0,0.6)) {
		fgcolor[0] = Color::white;
		fgcolor[1] = Color(0.7);
	}
	VariableConsole::~VariableConsole() {}
	VariableConsole& VariableConsole::draw(Drawable &target) {
		const double x=datum.x, y=datum.y, z=datum.z;
		const int MAX_LETTERS=30;
		char buf[MAX_LETTERS];
		double content_left=x+10, content_right=x, current_y=y+15;
		Psychlops::Rectangle bgrect_, barrect;
		Point mouse = Widgets::drawableMouse(target);

		if(o.variables.empty()) {
//			throw new Exception("VariableConsole: No Variable is determined.");
			return *this;
		} else {
			if(Keyboard::right.pushed()) o.variables.at(active_)->increment(Input::get(Keyboard::shift, Keyboard::pressed));
			if(Keyboard::left.pushed())  o.variables.at(active_)->decrement(Input::get(Keyboard::shift, Keyboard::pressed));
			if(Keyboard::up.pushed())   if(--active_<0) active_ = o.variables.size()-1;
			if(Keyboard::down.pushed()) if(++active_>=o.variables.size()) active_ = 0;
			if(Mouse::left.pressed()) {
				size_t c = active_bar;
				double cratio = (mouse.x-content_left)/200.0;
				if(c<o.variables.size() && c>=0 && cratio>=0 && cratio<=1) { o.variables.at(c)->setByRatio(cratio); }
			} else {
				active_bar = (int)floor((mouse.y-y)/20);
			}

			bgrect_.set(220, 20*o.variables.size()).shift(x,y).draw(bgcolor, target);
			for(size_t i=0; i<o.variables.size(); i++ ) {
				barrect.set(content_left, current_y, content_left+(200*o.variables.at(i)->getRatio()), current_y-12);
				if(i==active_bar) barrect.draw(active_barcolor); else barrect.draw(barcolor);
				Display::msg(o.variables.at(i)->label   , content_left,  current_y, fgcolor[active_==i?0:1]);
				if(o.variables.at(i)->to_str().size()<MAX_LETTERS) sprintf(buf, "%30s", o.variables.at(i)->to_str().c_str());
				Display::msg(buf, content_right, current_y, fgcolor[active_==i?0:1]);
				current_y+=20;
			}
			return *this;
		}
	}



	////////	ExperimentalMethod	////////
	ExperimentalMethod::ExperimentalMethod() : result_file_location("%EXPNAME__%TIME_.dat"), ITERATION(1) {
	}
	ExperimentalMethod::~ExperimentalMethod() {
	}
	void ExperimentalMethod::run() {
		setExpname();
		initialize_default();
		trial_default();
		terminate_default();
	}
	void ExperimentalMethod::terminate() {
	}
	void ExperimentalMethod::initialize_wait() {
	}
	void ExperimentalMethod::setExpname() {
		experiment_name = typeid(*this).name();
		char first = experiment_name[0];
		if(!( (0x40<first && first<0x5B) || (0x60<first && first<0x7B) )) experiment_name.erase(0,2);
		AppInfo::expname = experiment_name;
	}



	////////	ExperimentalMethods::Demo	////////
	Demo::Demo() : ExperimentalMethod(), console(Independent) {
	}
	Demo::~Demo() {
		if(Drawable::prime_is_a_canvas()) {
			std::vector<Figure*> &b = dynamic_cast<Canvas *>(Drawable::prime)->billboard;
			for(std::vector<Figure*>::iterator i = b.begin(); i!=b.end(); i++) {
				if(*i==&console) b.erase(i);
			}
		}
	}
	void Demo::initialize_default() {
		initialize();
		if(!Independent.variables.empty()) {
			for(VariableList i=Independent.variables.begin(); i!=Independent.variables.end(); i++) {
				if((**i).getNumberOfLevels()>1) {
					(**i).setByLevel((**i).getNumberOfLevels()/2);
				} else {
					if((**i).getNumberOfLevels()==1) (**i).setByLevel(0);
				}
			}
		}
		if(Drawable::prime_is_a_canvas()) {
			dynamic_cast<Canvas *>(Drawable::prime)->billboard.push_back(&console);
		}
	}
	bool Demo::nextCondition() {
		return true;
	}
	void Demo::trial_default() {
		trial();
	}
	void Demo::terminate_default() {
		saveResults();
		terminate();
	}
	void Demo::terminate() {
	}
	void Demo::saveResults() {
	}



	////////	ExperimentalMethods::Constant	////////
	Constant::Constant() {
	}
	Constant::~Constant() {
	}
	void Constant::initialize_default() {
		initialize();

		int num_trials = ITERATION;

		// loop for devide independent variables and invariants
		if(Independent.variables.empty()) throw Exception(typeid(*this), "VARIABLE REQUIRED", "No independent variable was set.");
		for(VariableList i=Independent.variables.begin(); i!=Independent.variables.end(); i++) {
			if((**i).getNumberOfLevels() <= 1) {
				if((**i).getNumberOfLevels() == 1) (**i).setByLevel(0);
				invariant_.insert( std::map<std::string, Variable *>::value_type((**i).label, *i) );
				Independent.variables.erase(i);
				i--;
				continue;
			}
			num_trials *= (**i).getNumberOfLevels();
		}
		for(VariableList i=Independent.variables.begin(); i!=Independent.variables.end(); i++) {
			(**i).setColumn(num_trials);
		}

		// loop for check dependent variables
		if(Dependent.variables.empty()) throw Exception(typeid(*this), "VARIABLE REQUIRED", "No dependent variable was set.");
		for(VariableList i=Dependent.variables.begin(); i!=Dependent.variables.end(); i++) {
			(**i).setColumn(num_trials);
		}

		// assign conditions for each trial
		conditions_.assign(num_trials, 0);
		for(size_t i=0; i<conditions_.size(); i++) conditions_.at(i) = i;
		int (*random_func)(int) = Psychlops::random;
		std::random_shuffle(conditions_.begin(), conditions_.end(), random_func);

		initialize_wait();
	}
	void Constant::initialize_wait() {
		Display::clear();
		Display::msg("Push space bar to start.", Display::getCenter().x/2-30, Display::getCenter().y/2-3,Color::white);
		Display::flip();
		while(!Input::get(Keyboard::spc)) ;
		Display::clear();
		Display::flip();
	}
	void Constant::trial_default() {
		int product_of_devided_conditions, index_of_this_condition;
		NUM_TRIALS = conditions_.size();

		// loop for each conditions
		for(size_t i=0; i<conditions_.size(); i++) {
			CURRENT_TRIAL = i+1;

			// Set independent variables for this trial
			product_of_devided_conditions = 1;
			for(VariableList ind=Independent.variables.begin(); ind!=Independent.variables.end(); ind++) {
				index_of_this_condition = conditions_.at(i) / product_of_devided_conditions % (**ind).getNumberOfLevels();
				product_of_devided_conditions *= (**ind).getNumberOfLevels();
				(**ind).setByLevel( index_of_this_condition );
				(**ind).setColumnCellByLevel( i, index_of_this_condition );
			}

			// trial
			trial();

			// Save result-of-this-trial into data column
			for(VariableList ind=Dependent.variables.begin(); ind!=Dependent.variables.end(); ind++) {
				(**ind).setColumnCellByCurrentValue(i);
			}
		}
	}
	bool Constant::nextCondition() {
		return false;
	}
	void Constant::terminate_default() {
		saveResults();
		terminate();
	}
	void Constant::saveResults() {
		std::string file_url = File::decodePath(result_file_location);
		std::ofstream f(file_url.c_str());
		char delimiter = '\t';
		f << experiment_name << std::endl;
		f << result_file_header << std::endl;

		//	put invariants
		if(invariant_.size()>0)
			for(std::map<std::string, Variable *>::iterator ind=invariant_.begin(); ind!=invariant_.end(); ind++)
				f << ind->second->label << delimiter << ind->second->to_str() << ";" << delimiter;
		f << std::endl;

		//	put column title
		f << "TRIAL#" << delimiter;
		if(!Independent.variables.empty()) for(VariableList ind=Independent.variables.begin(); ind!=Independent.variables.end(); ind++) f << (**ind).label << delimiter;
		if(!Dependent.variables.empty()) for(VariableList ind=Dependent.variables.begin(); ind!=Dependent.variables.end(); ind++) f << (**ind).label << delimiter;
		f << std::endl;

		//	put column cells
		for(size_t i=0; i<conditions_.size(); i++) {
			f << i+1 << delimiter;
			for(VariableList ind=Independent.variables.begin(); ind!=Independent.variables.end(); ind++) {
				(**ind).to_str_ColumnCell_at(i, f);
				f << delimiter;
			}
			for(VariableList ind=Dependent.variables.begin(); ind!=Dependent.variables.end(); ind++) {
				(**ind).to_str_ColumnCell_at(i, f);
				f << delimiter;
			}
			f << std::endl;
		}
		f.close();
	}

/*
	int Constant::set(int vnum, int arraynum, double *array)
	{
		Independent.append().setLevels(array, arraynum);
	}
	void Constant::randomize(char* dataname=NULL)
	{
		initialize_default();
	}
	double Constant::get(int vnum, int trial_now);
*/

}



	ExperimentalMethods::Variables Independent;

	Procedure::Procedure() : console_(Independent) {
		Independent.proc___ = this;
	}
	void Procedure::setDesign(DESIGN d) {
		design_ = d;
	}
	void Procedure::setProcedure(void (*func)()) {
		func_ = func;
		func_with_canvas_ = 1;
	}
	void Procedure::setProcedure(void (*func)(Canvas &)) {
		func_canvas_ = func;
		func_with_canvas_ = 2;
	}
	void Procedure::run() {
		if(func_with_canvas_==1) {
			if(design_==DEMO) {
				console(true);
			}
			func_();
		}
	}
	void Procedure::run(Canvas &cnv) {
		if(func_with_canvas_==2) {
			if(design_==DEMO) {
				cnv.billboard.push_back(&console_);
			}
			Mouse::show();
			func_canvas_(cnv);
		}
	}
	void Procedure::console(bool on_off) {
		if(Drawable::prime==Drawable::dummy) {
			Drawable::billboard.append(console_);
		} else {
			dynamic_cast<Canvas*>(Drawable::prime)->billboard.push_back(&console_);
		}
		Mouse::show();
	}


}	/*	<- namespace Psycholops 	*/



