#include <psemek/ui/slider.hpp>

namespace psemek::ui
{

	bool slider::on_event(mouse_move const & e)
	{
		bool const over = shape().contains(math::cast<float>(e.position));
		mouse_ = e.position;

		switch (state_) {
		case state_t::normal:
			if (over)
			{
				state_ = state_t::mouseover;
				on_state_changed(state_t::normal);
			}
			break;
		case state_t::mouseover:
			if (!over)
			{
				state_ = state_t::normal;
				on_state_changed(state_t::mouseover);
			}
			break;
		case state_t::mousedown:
			if (mouse_)
				set_value(compute_value((*mouse_)[0], *drag_range_));
			break;
		}

		return false;
	}

	bool slider::on_event(mouse_click const & e)
	{
		if (e.button != mouse_button::left) return false;

		switch (state_) {
		case state_t::normal:
			break;
		case state_t::mouseover:
			if (e.down)
			{
				state_ = state_t::mousedown;
				if (mouse_)
				{
					drag_range_ = slider_range();
					set_value(compute_value((*mouse_)[0], *drag_range_));
				}
				on_state_changed(state_t::mouseover);
				return true;
			}
			break;
		case state_t::mousedown:
			if (!e.down)
			{
				if (mouse_ && shape().contains(math::cast<float>(*mouse_)))
					state_ = state_t::mouseover;
				else
					state_ = state_t::normal;
				drag_range_ = std::nullopt;
				on_state_changed(state_t::mousedown);
				return true;
			}
			break;
		}

		return false;
	}

	bool slider::on_event(mouse_wheel const & e)
	{
		if (state_ == state_t::mouseover)
		{
			set_value(value_ + e.delta);
			return true;
		}

		return false;
	}

	void slider::set_value_range(math::interval<int> i, bool notify)
	{
		if (i.empty()) throw std::runtime_error("Empty value range for ui::slider");
		value_range_ = i;
		set_value(value_, notify);
	}

	void slider::set_value(int v, bool notify)
	{
		if (cyclic_)
			v = math::imod(v - value_range_.min, value_range_.length()) + value_range_.min;
		else
			v = math::clamp(v, value_range_);
		if (value_ != v)
		{
			value_ = v;
			if (notify)
				post_value_changed();
		}
	}

	void slider::on_value_changed(on_value_changed_callback callback, bool notify)
	{
		callback_ = std::move(callback);
		if (notify)
			post_value_changed();
	}

	math::interval<float> slider::slider_range() const
	{
		return shape().bbox()[0];
	}

	void slider::post_value_changed()
	{
		if (callback_)
			post([cb = callback_, value = value_]{
				cb(value);
			});
	}

	int slider::compute_value(int x, math::interval<float> const & range) const
	{
		return std::round(math::unlerp(range, x * 1.f) * value_range_.length()) + value_range_.min;
	}

}
