/*
 *  psychlops_figure_prototype.cpp
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2009/02/15 by Kenchi HOSOKAWA
 *  (C) 2009 Kenchi HOSOKAWA, Kazushi MARUYA, Takao SATO
 */

#include "psychlops_widget.h"

namespace Psychlops {


namespace Widgets {

	Theme Theme::current;
	bool Theme::initialized__ = false;
	Image Theme::default_button, Theme::default_horiz_grad;
	/*
	Theme::Theme(Color c1, Color c2, Stroke s1, Stroke s2)
	{
		normal_background = c1;
		over_background = c2;
		normal_stroke = s1;
		active_stroke = s2;
	}
	*/
	void Theme::initialize()
	{
		if(!initialized__) {
			for(int i=0; i<2; i++) {
				Theme::current.normal_foreground[i] = Color::black;
				Theme::current.active_foreground[i] = Color::black;
				Theme::current.normal_background[i] = Color(.85,.85,.85,.5);
				Theme::current.active_background[i] = Color(.70,1.0,.85,.65);
				Theme::current.over_background[i] = Color(.5,.5,1.0,.5);
				Theme::current.normal_stroke[i] = Stroke(Color(.35,.35,.35,.75), 1, Stroke::SOLID);
				Theme::current.active_stroke[i] = Stroke(Color(.35,.35,.35,.75), 2, Stroke::SOLID);
			}
			Theme::current.normal_background[ALERT] = Color(.8,.2,.1,.5);
			Theme::current.over_background[ALERT] = Color(1,.25,0,.5);

			default_button.set(1,20, Image::RGBA);
			for(int y= 0; y< 5; y++) { double level = 0.5-y*0.1; default_button.pix(0,y,Color::white); default_button.alpha(0,y,level); }
			for(int y= 5; y<15; y++) { default_button.pix(0,y,Color::white); default_button.alpha(0,y,0.1); }
			for(int y=15; y<20; y++) { double level = (y-14)*0.075; default_button.pix(0,y,Color(.8,.8,.8)); default_button.alpha(0,y,level); }
			Theme::current.button_back = &Theme::default_button;
			default_horiz_grad.set(40,1, Image::RGBA);
			for(int x= 0; x<15; x++) { double level = 0.25-x*0.01; default_horiz_grad.pix(x,0,Color::white); default_horiz_grad.alpha(x,0,level); }
			for(int x=15; x<35; x++) { default_horiz_grad.pix(x,0,Color::white); default_horiz_grad.alpha(x,0,0.1); }
			for(int x=35; x<40; x++) { double level = (x-33)*0.075; default_horiz_grad.pix(x,0,Color(.8,.8,.8)); default_horiz_grad.alpha(x,0,level); }
			Theme::current.horiz_grad = &Theme::default_horiz_grad;
			initialized__ = true;
		}
	}

	Point drawableMouse(Drawable &d){
		Canvas *cnvs = dynamic_cast<Canvas*>(&d);
		return (cnvs!=0) ? cnvs->mouse() : (Point)Mouse::uniPosition;
	}

	WidgetDatum::WidgetDatum() : datum(0,0,0) {}
	WidgetDatum::~WidgetDatum() {}
	const Point WidgetDatum::getDatum() const {
		return datum;
	}
	WidgetDatum& WidgetDatum::setDatum(const Point& p) {
		datum = p;
		return *this;
	}
	WidgetDatum& WidgetDatum::centering(const Point & p) {
		datum = p;
		return *this;
	}

	WidgetRect::WidgetRect()
	: mleft(Mouse::left)
	{
Theme::initialize();
		theme = &Theme::current;
		theme_type = Theme::NORMAL;
		pushed_ = false;
	}
	const Point WidgetRect::getDatum() const { return area.getDatum(); }
	WidgetRect& WidgetRect::setDatum(const Point& p) { area.setDatum(p); return *this; }
	WidgetRect& WidgetRect::centering(const Point& p) { area.centering(p); return *this; }

	double WidgetRect::getWidth() const { return area.getWidth(); }
	double WidgetRect::getHeight() const { return area.getHeight(); }
	double WidgetRect::getLeft() const { return area.getLeft(); }
	double WidgetRect::getTop() const { return area.getTop(); }
	double WidgetRect::getRight() const { return area.getRight(); }
	double WidgetRect::getBottom() const { return area.getBottom(); }
	WidgetRect& WidgetRect::alignLeft(const double lef) { area.alignLeft(lef); return *this; }
	WidgetRect& WidgetRect::alignTop(const double to_) { area.alignTop(to_); return *this; }
	WidgetRect& WidgetRect::alignRight(const double rig) { area.alignRight(rig); return *this; }
	WidgetRect& WidgetRect::alignBottom(const double bot) { area.alignBottom(bot); return *this; }

	WidgetRect& WidgetRect::set(double wid, double hei)
	{
		area.set(wid, hei);
		label.font.size = hei-4;
		return *this;
	}
	WidgetRect& WidgetRect::set(std::wstring name)
	{
		return set(name, Font::default_font.size);
	}
	WidgetRect& WidgetRect::set(std::wstring name, double hei)
	{
		label.font.size = hei-4;
		label.setString(name);
		area.set(hei*label.str.length(), hei);
		return *this;
	}
	WidgetRect& WidgetRect::setLabel(std::wstring s)
	{
		label.font.size = area.getHeight()-4;
		label.setString(s);
		label.fill = theme->normal_foreground[theme_type];
		return *this;
	}
	WidgetRect& WidgetRect::draw(Drawable &target)
	{
		return *this;
	}
	bool WidgetRect::pushed()
	{
		bool v = pushed_;
		pushed_ = false;
		return v;
	}
	WidgetRect& WidgetRect::pushThis()
	{
		pushed_ = true;
		return *this;
	}

	StackPanel::StackPanel()
	{
		orientation = VERTICAL;
		pitch = 2;
	}
	StackPanel& StackPanel::setWidth(double wid)
	{
		area.set(wid, area.getHeight());
		forceBase = true;
		return *this;
	}
	StackPanel& StackPanel::setHeight(double hei)
	{
		area.set(area.getWidth(), hei);
		forceBase = true;
		return *this;
	}
	StackPanel& StackPanel::append(WidgetRect *target1, WidgetRect *target2, WidgetRect *target3, WidgetRect *target4, WidgetRect *target5)
	{
		if(target1!=0) { content.push_back(target1); }
		if(target2!=0) { content.push_back(target2); }
		if(target3!=0) { content.push_back(target3); }
		if(target4!=0) { content.push_back(target4); }
		if(target5!=0) { content.push_back(target5); }
		return *this;
	}
	void StackPanel::align(Orientation ori)
	{
		orientation = ori;
		/*
		double base = 0.0;
		switch(ori) {
		case VERTICAL:
			if(!content.empty()) {
				for(int i=0; i<content.size(); i++) {
					content[i]->alignTop(base);
				}
			}
			break;
		case HORIZONTAL:
		default:
			if(!content.empty()) {
				for(int i=0; i<content.size(); i++) {
					content[i]->alignLeft(base);
					base += content[i]->getWidth();
				}
			}
			break;
		}
		*/
	}
	StackPanel& StackPanel::draw(Drawable &target)
	{
		double dleft = getLeft(), dtop = getTop();
		switch(orientation) {
		case VERTICAL:
			if(!content.empty()) {
				for(int i=0; i<content.size(); i++) {
					content[i]->area.set(area.getWidth(), content[i]->area.getHeight());
					content[i]->alignLeft(dleft);
					content[i]->alignTop(dtop);
					content[i]->draw(target);
					dtop += content[i]->getHeight() + pitch;
				}
			}
			break;
		case HORIZONTAL:
		default:
			if(!content.empty()) {
				for(int i=0; i<content.size(); i++) {
					content[i]->alignLeft(dleft);
					content[i]->alignTop(dtop);
					content[i]->draw(target);
					dleft += content[i]->getWidth() + pitch;
				}
			}
			break;
		}
		return *this;
	}


	void TextBox::setBase()
	{
		cache.fill = Color::white;
		area.fill = Color::null_color;
		area.stroke = Stroke::hair_line;
	}
	TextBox::TextBox()
	{
		setBase();
	}
	TextBox::TextBox(std::wstring s)
	{
		setBase();
	}
	TextBox& TextBox::set(std::wstring s)
	{
		setBase();
		return *this;
	}
	TextBox& TextBox::draw(Drawable &target)
	{
		area.draw(target);
		cache.setString(content);
		cache.draw(target);
		return *this;
	}
	void TextBox::setText(const std::wstring &str)
	{
		content = str;
	}
	std::wstring& TextBox::getText()
	{
		return content;
	}


	namespace Internal
	{
		void doNothing() {}
		void doNothingP(void *p) {}
	}

	Button::Button()
	{
		setBase();
	}
	Button::Button(std::wstring s)
	{
		set(s);
	}
	Button::Button(std::wstring s, double size)
	{
		set(s, size);
	}
	void Button::setBase() {
		onClick = 0;
		onClickP = 0;
		arg = 0;
	}
	Button& Button::set(std::wstring s, double size) {
		setBase();
		WidgetRect::set(s, size);
		return *this;
	}
	Button& Button::draw(Drawable &target) {
		Point mouse = drawableMouse(target);
		if(area.include(mouse)) {
			area.fill = theme->over_background[theme_type];
			if(mleft.pushed()) {
				pushThis();
				if(onClick!=0) onClick();
				if(onClickP!=0) onClickP(arg);
				label.fill = theme->active_foreground[theme_type];
				area.stroke = theme->active_stroke[theme_type];
			}
		} else {
			label.fill = theme->normal_foreground[theme_type];
			area.fill = theme->normal_background[theme_type];
			area.stroke = theme->normal_stroke[theme_type];
		}

		area.draw(target);
		Canvas* tag = dynamic_cast<Canvas*>(&target);
		if(tag!=0) tag->drawImage(*(theme->button_back), area);
		label.centering(area.getCenter().x, area.getBottom()-2);
		label.draw(target);
		return *this;
	}

	ToggleButton::ToggleButton()
	: Button(), checked_(false) {
	}
	ToggleButton::ToggleButton(std::wstring s)
	: Button(s), checked_(false) {
	}
	ToggleButton::ToggleButton(std::wstring s, double size)
	: Button(s, size), checked_(false) {
	}
	bool ToggleButton::toggle()
	{
		pushed_ = true;
		checked_ = !checked_;
		return checked_;
	}
	bool ToggleButton::toggle(bool on_off)
	{
		pushed_ = true;
		checked_ = on_off;
		return checked_;
	}
	bool ToggleButton::isChecked()
	{
		return checked_;
	}
	ToggleButton& ToggleButton::draw(Drawable &target)
	{
		Point mouse = drawableMouse(target);
		if(area.include(mouse)) {
			area.fill = isChecked() ? theme->active_background[theme_type] : theme->over_background[theme_type];
			if(mleft.pushed()) {
				toggle();
				pushed_ = true;
				if(onClick!=0) onClick();
				if(onClickP!=0) onClickP(arg);
				label.fill = theme->active_foreground[theme_type];
				area.stroke = theme->active_stroke[theme_type];
			}
		} else {
			if(isChecked()){
				label.fill = theme->active_foreground[theme_type];
				area.fill = theme->active_background[theme_type];
				area.stroke = theme->active_stroke[theme_type];
			} else {
				label.fill = theme->normal_foreground[theme_type];
				area.fill = theme->normal_background[theme_type];
				area.stroke = theme->normal_stroke[theme_type];
			}
		}

		area.draw(target);
		Canvas* tag = dynamic_cast<Canvas*>(&target);
		if(tag!=0) tag->drawImage(*(theme->button_back), area);
		label.centering(area.getCenter().x, area.getBottom()-2);
		label.draw(target);
		return *this;
	}



	Image ScreenShotButton::buffer;
	void ScreenShotButton::shoot(void *ffname)
	{
		Psychlops::Rectangle rect(Display::the_canvas->getWidth(), Display::the_canvas->getHeight());
		Display::the_canvas->to(buffer, rect);
		std::string fname("psychlops_");
		fname += (char*)ffname;
		fname += ".png";
		buffer.save(fname);
	}
	ScreenShotButton::ScreenShotButton(char *name__)
	: Button(L"Capture")
	{
		name = name__;
		arg = (void*)name.c_str();
		onClickP = &shoot;
	}


	Slider::~Slider()
	{
		if(var==0) delete var;
	}
	Slider::Slider()
	: WidgetRect()
	{
		var = 0;
		setBase();
	}
	Slider::Slider(double wid, double hei)
	: WidgetRect()
	{
		var = 0;
		WidgetRect::set(wid, hei);
		setBase();
	}
	void Slider::setBase() {
		show_value = true;
		changed_ = true;
		area.fill = Color(0.3);
		internal.fill = Color::blue;
		dragged = false;
	}
	Slider& Slider::set(double wid, double hei)
	{
		WidgetRect::set(wid, hei);
		label.fill = Color::white;
		label.align = Letters::TEXT_ALIGN_LEFT;
		show_value = label.getString().length()*area.getHeight()*2/3 < area.getWidth();
		return *this;
	}
	Slider& Slider::set(std::wstring s, double hei)
	{
		WidgetRect::set(s, hei);
		label.fill = Color::white;
		label.align = Letters::TEXT_ALIGN_LEFT;
		show_value = label.getString().length()*area.getHeight()*2/3 < area.getWidth();
		return *this;
	}
	Slider& Slider::setLabel(std::wstring s)
	{
		WidgetRect::setLabel(s);
		label.fill = Color::white;
		label.align = Letters::TEXT_ALIGN_LEFT;
		show_value = label.getString().length()*area.getHeight()*2/3 < area.getWidth();
		return *this;
	}
	Slider& Slider::draw(Drawable &target)
	{
		Point mouse = drawableMouse(target);
		bool mouse_over = area.include(mouse);
		area.draw(target);

		if(mleft.pushed() && mouse_over) { dragged = true; pushThis(); }
		if(mleft.pressed() && dragged) {
			if(area.getWidth()>area.getHeight()) { var->setByRatio((mouse.x-area.getLeft())/area.getWidth()); changed_=true; }
			else { var->setByRatio((area.getBottom()-mouse.y)/area.getHeight()); changed_=true; }
		}
		if(Keyboard::left.pressed()  && mouse_over) { var->decrement(); changed_=true; }
		if(Keyboard::right.pressed() && mouse_over) { var->increment(); changed_=true; }
		if(!mleft.pressed()) dragged = false;

		if(area.getWidth()>area.getHeight()) {
			internal.set(area.getLeft()+1, area.getTop()+1, area.getLeft()+area.getWidth()*(var->getRatio()), area.getBottom()-1).draw(target);
		} else {
			internal.set(area.getLeft()+1, area.getBottom()-area.getHeight()*(var->getRatio()), area.getRight()-1, area.getBottom()-1).draw(target);
		}
		area.draw(mouse_over ? Stroke::hair_line : Stroke::null_line);
		label.locate(area.getLeft()+5, area.getBottom()-2);
		label.draw(Color::white, target);

		Canvas* cnvs = dynamic_cast<Canvas*>(&target);
		if(cnvs!=0 && show_value) cnvs->msg(var->to_str(), area.getRight()-2, area.getBottom()-5, Color::white, Letters::TEXT_ALIGN_RIGHT);

		return *this;
	}
	bool Slider::changed()
	{
		bool tmp = changed_;
		changed_ = false;
		return tmp;
	}
	Slider::operator double()
	{
		return local;
	}
	double Slider::operator =(double v)
	{
		return local = v;
	}
	void Slider::setByRatio(double ratio) { var->setByRatio(ratio); }
	double Slider::getRatio() const { return var->getRatio(); }
	Interval Slider::getInterval() const { return var->getInterval(); }
	Interval Slider::setInterval(const Interval &itvl) { return var->setInterval(itvl); }
	void Slider::increment(int modulation) { var->increment(modulation); }
	void Slider::decrement(int modulation) { var->decrement(modulation); }


	Dial::Dial() {
		setBase();
	}
	Dial::Dial(double r) {
		set(r);
		setBase();
	}
	void Dial::setBase() {
		changed_ = true;
		current_theta = factor = 0;
		pressed = dragged = false;
		ext.fill = Color(0.3);
		internal.fill = Color::black;
	}
	Dial& Dial::set(double r) {
		ext.set(r,r);
		internal.set(r/5, r/5);
		return *this;
	}
	Dial& Dial::draw(Drawable &target) {
		Point mouse = drawableMouse(target);
		ext.centering(getDatum());
		bool mouse_over = ext.include(mouse);
		target.ellipse(ext, ext.fill);
		if(!pressed && Mouse::left.pressed() && mouse_over) {
			dragged = true;
			former_theta = atan2(mouse.y-ext.getCenter().y, mouse.x-ext.getCenter().x);
		}
		if(mouse_over || dragged) {
			target.ellipse(ext, Stroke::hair_line);
			if(Keyboard::left.pressed()) { current_theta-=2*PI/36; (*var) = *var-factor/36; changed_=true; }
			if(Keyboard::right.pressed()) { current_theta+=2*PI/36; (*var) = *var+factor/36; changed_=true; }
		}
		pressed = Mouse::left.pressed();
		if(Mouse::left.pressed() && dragged) {
			current_theta = atan2(mouse.y-ext.getCenter().y, mouse.x-ext.getCenter().x);
			double delta_theta = Math::mod(current_theta-former_theta, 2*PI);
			if(delta_theta>PI) delta_theta-=2*PI;
			(*var) = *var+delta_theta/2/PI*factor;
			changed_ = true;
			former_theta = current_theta;
		} else {
			dragged = false;
		}
		internal.centering(ext).shift(ext.getWidth()/2.2*cos(current_theta), ext.getWidth()/2.2*sin(current_theta));
		target.ellipse(internal, internal.fill);
		let.centering(ext.getCenter().x, ext.getBottom());
		let.draw(target);
		return *this;
	}
	bool Dial::changed() {
		bool tmp = changed_;
		changed_ = false;
		return tmp;
	}
	Dial::operator double() {
		return local;
	}
	double Dial::operator =(double v) {
		return local = v;
	}


	SelectBox::SelectBox()
	: WidgetRect()
	{
		set(Font::default_font.size);
	}
	SelectBox::SelectBox(double size)
	: WidgetRect()
	{
		set(size);
	}
	SelectBox& SelectBox::set(double hei)
	{
		return set(0, hei);
	}
	SelectBox& SelectBox::set(double wid, double hei)
	{
		selected_ = 0;
		vertical_ = false;
		area.resize(wid, hei);
		return *this;
	}
	void SelectBox::makeVertical()
	{
		area.resize(area.getHeight(), area.getWidth());
		vertical_ = true;
	}
	void SelectBox::appendL(Letters &let)
	{
		area.resize(Math::max(area.getWidth(), let.getString().length()*Font::default_font.size), area.getHeight());
		let.fill = theme->normal_foreground[theme_type];
		let.font.size = area.getHeight() - 4;;
		item.push_back(let);
	}
	int SelectBox::getSelected() { return selected_; }
	void SelectBox::setSelected(int dd) { selected_ = Math::min( Math::max(dd, 0), item.size() ); }
	void SelectBox::next() { if(selected_<item.size()-1) { ++selected_; } else { selected_ = 0; }  }
	void SelectBox::retreat() { if(selected_>0) { --selected_; } else { selected_ = item.size()-1; } }
	void SelectBox::hid()
	{
		int wheel = Mouse::getWheelDelta().y;
		if(wheel<0) {
			pushThis();
			area.stroke = theme->active_stroke[theme_type];
			retreat();
		}
		if(wheel>0 || mleft.pushed()) {
			pushThis();
			area.stroke = theme->active_stroke[theme_type];
			next();
		}
	}
	SelectBox& SelectBox::draw(Drawable &target)
	{
		Point mouse = drawableMouse(target);
		if(area.include(mouse)) {
			area.stroke = theme->normal_stroke[theme_type];
			hid();
			area.fill = theme->over_background[theme_type];
		} else {
			area.stroke = theme->normal_stroke[theme_type];
			area.fill = theme->normal_background[theme_type];
		}

		area.draw(target);
		Canvas* tag = dynamic_cast<Canvas*>(&target);
		if(tag!=0) tag->drawImage(*(theme->button_back), area);
		if(!vertical_) {
			if(!item.empty()) item[selected_].locate(area.getLeft()+5, area.getBottom()-2).draw(target);
		} else {
			holder_.contents.clear();
			if(!item.empty()) {
				holder_.setDatum(Point(area.getLeft()+2, area.getTop()-5));
				holder_.append(item[selected_]);
				item[selected_].setDatum(Point(0,0)).shift(5, item[selected_].getFont().size);
				holder_.rotation = PI/2;
				holder_.draw(target);
			}
		}
		return *this;
	}


	TitleBar *TitleBar::default_titlebar;

}	/*	<- namespace Widgets 	*/
}	/*	<- namespace Psycholops 	*/


