#include <psemek/ui/rich_image_view.hpp>

#include <psemek/math/contains.hpp>

namespace psemek::ui
{

	void rich_image_view::set_image(std::shared_ptr<gfx::texture_2d> image)
	{
		image_ = std::move(image);
		post_reshape();
	}

	void rich_image_view::set_zoom_range(math::interval<float> range)
	{
		float z = zoom();
		zoom_range_ = range;
		set_zoom(z);
	}

	void rich_image_view::set_center(math::point<float, 2> const & center)
	{
		center_ = center;
		if (!allow_overflow_)
		{
			float const w = shape_.box[0].length() / zoom_ / 2.f;
			float const h = shape_.box[1].length() / zoom_ / 2.f;

			math::box<float, 2> b;
			b[0] = {w, image_->width() - w};
			b[1] = {h, image_->height() - h};
			if (b[0].empty()) std::swap(b[0].min, b[0].max);
			if (b[1].empty()) std::swap(b[1].min, b[1].max);

			center_ = math::clamp(center_, b);
		}
		post_region_changed();
	}

	void rich_image_view::set_zoom(float zoom)
	{
		zoom = math::clamp(zoom, zoom_range_);

		// Screen -> Texcoords
		// mouse |-> (mouse - bbox.center) / zoom + center
		// (mouse - bbox.center) / zoom0 + center0 = (mouse - bbox.center) / zoom1 + center1
		// center1 - center0 = (mouse - bbox.center) * (1 / zoom0 - 1 / zoom1)

		zoom_tgt_ = zoom;
		zoom_ = zoom;
		set_center(center_);
	}

	math::box<float, 2> rich_image_view::region() const
	{
		float const w = shape_.box[0].length() / zoom_ / 2.f;
		float const h = shape_.box[1].length() / zoom_ / 2.f;

		math::box<float, 2> r;
		r[0] = {center_[0] - w, center_[0] + w};
		r[1] = {center_[1] - h, center_[1] + h};
		return r;
	}

	void rich_image_view::set_allow_overflow(bool value)
	{
		allow_overflow_ = value;
		set_center(center_);
	}

	bool rich_image_view::on_event(mouse_move  const & e)
	{
		mouseover_ = math::contains(shape_.box, math::cast<float>(e.position));
		mouse_ = e.position;
		if (drag_)
		{
			set_center(center_ + math::cast<float>(*drag_ - e.position) / zoom_);
			drag_ = e.position;
		}
		return false;
	}

	bool rich_image_view::on_event(mouse_click const & e)
	{
		if (e.button == mouse_button::right)
		{
			if (e.down && mouse_ && mouseover_)
			{
				drag_ = *mouse_;
				return true;
			}
			else if (!e.down)
			{
				drag_ = std::nullopt;
				return mouseover_;
			}
		}
		return false;
	}

	bool rich_image_view::on_event(mouse_wheel const & e)
	{
		if (mouseover_)
		{
			zoom_tgt_ *= std::pow(1.25f, e.delta);
			zoom_tgt_ = math::clamp(zoom_tgt_, zoom_range_);
			set_center(center_);
			return true;
		}
		return false;
	}

	bool rich_image_view::on_event(key_press   const & e)
	{
		if (mouseover_ && e.down)
		{
			if (e.key == SDLK_KP_PLUS)
			{
				on_event(mouse_wheel{1});
				return true;
			}
			if (e.key == SDLK_KP_MINUS)
			{
				on_event(mouse_wheel{-1});
				return true;
			}
		}
		return false;
	}

	void rich_image_view::reshape(math::box<float, 2> const & bbox)
	{
		shape_.box = bbox;
		post_region_changed();
	}

	void rich_image_view::update(float dt)
	{
		float new_zoom = zoom_ + (zoom_tgt_ - zoom_) * std::min(1.f, dt * 20.f);

		auto new_center = center_;

		if (mouse_)
			new_center += (math::cast<float>(*mouse_) - shape_.box.center()) * (1.f / zoom_ - 1.f / new_zoom);

		zoom_ = new_zoom;

		set_center(new_center);
	}

	void rich_image_view::draw(painter & p) const
	{
		auto st = merged_own_style();

		if (image_)
		{
			auto box = shape_.box;
			auto reg = region();
			if (!allow_overflow_)
			{
				auto new_reg = reg;
				new_reg[0].min = std::max(0.f, new_reg[0].min);
				new_reg[1].min = std::max(0.f, new_reg[1].min);
				new_reg[0].max = std::min<float>(image_->width(), new_reg[0].max);
				new_reg[1].max = std::min<float>(image_->height(), new_reg[1].max);

				auto new_box = box;
				new_box[0].min = math::lerp(box[0], math::unlerp(reg[0], new_reg[0].min));
				new_box[0].max = math::lerp(box[0], math::unlerp(reg[0], new_reg[0].max));
				new_box[1].min = math::lerp(box[1], math::unlerp(reg[1], new_reg[1].min));
				new_box[1].max = math::lerp(box[1], math::unlerp(reg[1], new_reg[1].max));

				box = new_box;
				reg = new_reg;
			}

			if (*st->shadow_offset != math::vector{0, 0})
				p.draw_rect(box + math::cast<float>(*st->shadow_offset), *st->shadow_color);

			p.draw_image(box, {image_.get(), reg}, {color_});
		}
	}

	void rich_image_view::post_region_changed()
	{
		// Clang-12.0.0 doesn't have deduction guides for weak_ptr
		auto self = std::dynamic_pointer_cast<rich_image_view>(shared_from_this());
		auto weak_self = std::weak_ptr<rich_image_view>{self};
		post([weak_self]{
			auto self = weak_self.lock();
			if (!self) return;
			self->on_region_changed();
		});
	}

}
