/*************************************************************************/
/*  scroll_container.cpp                                                 */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                    http://www.godotengine.org                         */
/*************************************************************************/
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include "scroll_container.h"
#include "os/os.h"
bool ScrollContainer::clips_input() const {

	return true;
}

Size2 ScrollContainer::get_minimum_size() const {


	Size2 min_size;

	for(int i=0;i<get_child_count();i++) {

		Control *c = get_child(i)->cast_to<Control>();
		if (!c)
			continue;
		if (c->is_set_as_toplevel())
			continue;
		if (c == h_scroll || c == v_scroll)
			continue;
		Size2 minsize = c->get_combined_minimum_size();


		if (!scroll_h) {
			min_size.x = MAX(min_size.x, minsize.x);
		}
		if (!scroll_v) {
			min_size.y = MAX(min_size.y, minsize.y);

		}
	}

	if (h_scroll->is_visible()) {
		min_size.y+=h_scroll->get_minimum_size().y;
	}
	if (v_scroll->is_visible()) {
		min_size.x+=v_scroll->get_minimum_size().x;
	}
	return min_size;
};


void ScrollContainer::_cancel_drag() {
	set_fixed_process(false);
	drag_touching_deaccel=false;
	drag_touching=false;
	drag_speed=Vector2();
	drag_accum=Vector2();
	last_drag_accum=Vector2();
	drag_from=Vector2();
}

void ScrollContainer::_input_event(const InputEvent& p_input_event) {


	switch(p_input_event.type) {

		case InputEvent::MOUSE_BUTTON: {

			const InputEventMouseButton &mb=p_input_event.mouse_button;

			if (mb.button_index==BUTTON_WHEEL_UP && mb.pressed) {
				if (h_scroll->is_visible() && !v_scroll->is_visible()){
					// only horizontal is enabled, scroll horizontally
					h_scroll->set_value( h_scroll->get_value()-h_scroll->get_page()/8 );
				} else if (v_scroll->is_visible()) {
					v_scroll->set_value( v_scroll->get_value()-v_scroll->get_page()/8 );
				}
			}

			if (mb.button_index==BUTTON_WHEEL_DOWN && mb.pressed) {
				if (h_scroll->is_visible() && !v_scroll->is_visible()){
					// only horizontal is enabled, scroll horizontally
					h_scroll->set_value( h_scroll->get_value()+h_scroll->get_page()/8 );
				} else if (v_scroll->is_visible()) {
					v_scroll->set_value( v_scroll->get_value()+v_scroll->get_page()/8 );
				}
			}

			if(!OS::get_singleton()->has_touchscreen_ui_hint())
				return;

			if (mb.button_index!=BUTTON_LEFT)
				break;

			if (mb.pressed) {

				if (drag_touching) {
					set_fixed_process(false);
					drag_touching_deaccel=false;
					drag_touching=false;
					drag_speed=Vector2();
					drag_accum=Vector2();
					last_drag_accum=Vector2();
					drag_from=Vector2();
				}

				if (true) {
					drag_speed=Vector2();
					drag_accum=Vector2();
					last_drag_accum=Vector2();
					drag_from=Vector2(h_scroll->get_value(),v_scroll->get_value());
					drag_touching=OS::get_singleton()->has_touchscreen_ui_hint();
					drag_touching_deaccel=false;
					time_since_motion=0;
					if (drag_touching) {
						set_fixed_process(true);
						time_since_motion=0;

					}
				}


			} else {
				if (drag_touching) {

					if (drag_speed==Vector2()) {
						drag_touching_deaccel=false;
						drag_touching=false;
						set_fixed_process(false);
					} else {

						drag_touching_deaccel=true;
					}
				}
			}


		} break;
		case InputEvent::MOUSE_MOTION: {

			const InputEventMouseMotion &mm=p_input_event.mouse_motion;

			if (drag_touching && ! drag_touching_deaccel) {

				Vector2 motion = Vector2(mm.relative_x,mm.relative_y);
				drag_accum-=motion;
				Vector2 diff = drag_from+drag_accum;

				if (scroll_h)
					h_scroll->set_value(diff.x);
				else
					drag_accum.x=0;
				if (scroll_v)
					v_scroll->set_value(diff.y);
				else
					drag_accum.y=0;
				time_since_motion=0;
			}

		} break;
	}

}


void ScrollContainer::_update_scrollbar_pos() {

	Size2 hmin = h_scroll->get_combined_minimum_size();
	Size2 vmin = v_scroll->get_combined_minimum_size();

	v_scroll->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,vmin.width);
	v_scroll->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0);
	v_scroll->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,0);
	v_scroll->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0);

	h_scroll->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,0);
	h_scroll->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0);
	h_scroll->set_anchor_and_margin(MARGIN_TOP,ANCHOR_END,hmin.height);
	h_scroll->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0);

	h_scroll->raise();
	v_scroll->raise();

}


void ScrollContainer::_notification(int p_what) {

	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {

		call_deferred("_update_scrollbar_pos");
	};


	if (p_what==NOTIFICATION_SORT_CHILDREN) {

		child_max_size = Size2(0, 0);
		Size2 size = get_size();
		if (h_scroll->is_visible())
			size.y-=h_scroll->get_minimum_size().y;

		if (v_scroll->is_visible())
			size.x-=h_scroll->get_minimum_size().x;

		for(int i=0;i<get_child_count();i++) {

			Control *c = get_child(i)->cast_to<Control>();
			if (!c)
				continue;
			if (c->is_set_as_toplevel())
				continue;
			if (c == h_scroll || c == v_scroll)
				continue;
			Size2 minsize = c->get_combined_minimum_size();
			child_max_size.x = MAX(child_max_size.x, minsize.x);
			child_max_size.y = MAX(child_max_size.y, minsize.y);

			Rect2 r = Rect2(-scroll,minsize);
			if (!(scroll_h || h_scroll->is_visible())) {
				r.pos.x=0;
				if (c->get_h_size_flags()&SIZE_EXPAND)
					r.size.width=MAX(size.width,minsize.width);
				else
					r.size.width=minsize.width;
			}
			if (!(scroll_v || v_scroll->is_visible())) {
				r.pos.y=0;
				r.size.height=size.height;
				if (c->get_v_size_flags()&SIZE_EXPAND)
					r.size.height=MAX(size.height,minsize.height);
				else
					r.size.height=minsize.height;

			}
			fit_child_in_rect(c,r);
		}
		update();
	};

	if (p_what == NOTIFICATION_DRAW) {

		update_scrollbars();

		VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(),true);
	}

	if (p_what==NOTIFICATION_FIXED_PROCESS) {

		if (drag_touching)  {

			if (drag_touching_deaccel) {

				Vector2 pos = Vector2(h_scroll->get_value(),v_scroll->get_value());
				pos+=drag_speed*get_fixed_process_delta_time();

				bool turnoff_h=false;
				bool turnoff_v=false;

				if (pos.x<0) {
					pos.x=0;
					turnoff_h=true;
				}
				if (pos.x > (h_scroll->get_max()-h_scroll->get_page())) {
					pos.x=h_scroll->get_max()-h_scroll->get_page();
					turnoff_h=true;
				}

				if (pos.y<0) {
					pos.y=0;
					turnoff_v=true;
				}
				if (pos.y > (v_scroll->get_max()-v_scroll->get_page())) {
					pos.y=v_scroll->get_max()-v_scroll->get_page();
					turnoff_v=true;
				}

				if (scroll_h)
					h_scroll->set_value(pos.x);
				if (scroll_v)
					v_scroll->set_value(pos.y);

				float sgn_x = drag_speed.x<0? -1 : 1;
				float val_x = Math::abs(drag_speed.x);
				val_x-=1000*get_fixed_process_delta_time();

				if (val_x<0) {
					turnoff_h=true;
				}


				float sgn_y = drag_speed.y<0? -1 : 1;
				float val_y = Math::abs(drag_speed.y);
				val_y-=1000*get_fixed_process_delta_time();

				if (val_y<0) {
					turnoff_v=true;
				}


				drag_speed=Vector2(sgn_x*val_x,sgn_y*val_y);

				if (turnoff_h && turnoff_v) {
					set_fixed_process(false);
					drag_touching=false;
					drag_touching_deaccel=false;
				}


			} else {


				if (time_since_motion==0 || time_since_motion>0.1) {

					Vector2 diff = drag_accum - last_drag_accum;
					last_drag_accum=drag_accum;
					drag_speed=diff/get_fixed_process_delta_time();
				}

				time_since_motion+=get_fixed_process_delta_time();
			}
		}
	}

};

void ScrollContainer::update_scrollbars() {

	Size2 size = get_size();

	Size2 hmin = h_scroll->get_combined_minimum_size();
	Size2 vmin = v_scroll->get_combined_minimum_size();

	Size2 min = child_max_size;

	if (!scroll_v || min.height <= size.height - hmin.height) {

		v_scroll->hide();
		scroll.y=0;
	} else {

		v_scroll->show();
		scroll.y=v_scroll->get_value();

	}

	v_scroll->set_max(min.height);
	v_scroll->set_page(size.height - hmin.height);


	if (!scroll_h || min.width <= size.width - vmin.width) {

		h_scroll->hide();
		scroll.x=0;
	} else {

		h_scroll->show();
		h_scroll->set_max(min.width);
		h_scroll->set_page(size.width - vmin.width);
		scroll.x=h_scroll->get_value();
	}
}

void ScrollContainer::_scroll_moved(float) {

	scroll.x=h_scroll->get_value();
	scroll.y=v_scroll->get_value();
	queue_sort();

	update();
};


void ScrollContainer::set_enable_h_scroll(bool p_enable) {

	scroll_h=p_enable;
	queue_sort();
}

bool ScrollContainer::is_h_scroll_enabled() const{

	return scroll_h;
}

void ScrollContainer::set_enable_v_scroll(bool p_enable) {

	scroll_v=p_enable;
	queue_sort();
}

bool ScrollContainer::is_v_scroll_enabled() const{

	return scroll_v;
}

int ScrollContainer::get_v_scroll() const {


	return v_scroll->get_value();
}
void ScrollContainer::set_v_scroll(int p_pos) {

	v_scroll->set_value(p_pos);
	_cancel_drag();
}

int ScrollContainer::get_h_scroll() const {

	return h_scroll->get_value();
}
void ScrollContainer::set_h_scroll(int p_pos) {

	h_scroll->set_value(p_pos);
	_cancel_drag();

}


void ScrollContainer::_bind_methods() {

	ClassDB::bind_method(_MD("_scroll_moved"),&ScrollContainer::_scroll_moved);
	ClassDB::bind_method(_MD("_input_event"),&ScrollContainer::_input_event);
	ClassDB::bind_method(_MD("set_enable_h_scroll","enable"),&ScrollContainer::set_enable_h_scroll);
	ClassDB::bind_method(_MD("is_h_scroll_enabled"),&ScrollContainer::is_h_scroll_enabled);
	ClassDB::bind_method(_MD("set_enable_v_scroll","enable"),&ScrollContainer::set_enable_v_scroll);
	ClassDB::bind_method(_MD("is_v_scroll_enabled"),&ScrollContainer::is_v_scroll_enabled);
	ClassDB::bind_method(_MD("_update_scrollbar_pos"),&ScrollContainer::_update_scrollbar_pos);
	ClassDB::bind_method(_MD("set_h_scroll","val"),&ScrollContainer::set_h_scroll);
	ClassDB::bind_method(_MD("get_h_scroll"),&ScrollContainer::get_h_scroll);
	ClassDB::bind_method(_MD("set_v_scroll","val"),&ScrollContainer::set_v_scroll);
	ClassDB::bind_method(_MD("get_v_scroll"),&ScrollContainer::get_v_scroll);

	ADD_GROUP("Scroll","scroll_");
	ADD_PROPERTY( PropertyInfo( Variant::BOOL, "scroll_horizontal"), _SCS("set_enable_h_scroll"),_SCS("is_h_scroll_enabled"));
	ADD_PROPERTY( PropertyInfo( Variant::BOOL, "scroll_vertical"), _SCS("set_enable_v_scroll"),_SCS("is_v_scroll_enabled"));

};

ScrollContainer::ScrollContainer() {

	h_scroll = memnew(HScrollBar);
	h_scroll->set_name("_h_scroll");
	add_child(h_scroll);

	v_scroll = memnew(VScrollBar);
	v_scroll->set_name("_v_scroll");
	add_child(v_scroll);

	h_scroll->connect("value_changed", this,"_scroll_moved");
	v_scroll->connect("value_changed", this,"_scroll_moved");

	drag_speed=Vector2();
	drag_touching=false;
	drag_touching_deaccel=false;
	scroll_h=true;
	scroll_v=true;


};

