#include <psemek/ui/element.hpp>

#include <psemek/util/recursive.hpp>
#include <psemek/util/range.hpp>

#include <psemek/log/log.hpp>

#include <SDL2/SDL_keyboard.h>

namespace psemek::ui
{

	void element::set_parent(element * parent)
	{
		assert(!parent_ || !parent);

		parent_ = parent;
		if (loop())
			post_delayed_callbacks();

		style_updated();
	}

	element * element::root()
	{
		element * r = this;
		while (r->parent()) r = r->parent();
		return r;
	}

	element const * element::root() const
	{
		element const * r = this;
		while (r->parent()) r = r->parent();
		return r;
	}

	async::event_loop * element::loop() const
	{
		element const * e = this;
		while (!e->loop_ && e->parent()) e = e->parent();
		return e->loop_;
	}

	void element::set_loop(async::event_loop * loop)
	{
		loop_ = loop;
		post_delayed_callbacks();
	}

	math::box<float, 2> element::events_bbox() const
	{
		return math::box<float, 2>::full();
	}

	void element::reshape()
	{
		reshape(shape().bbox());
	}

	math::box<float, 2> element::size_constraints() const
	{
		static float const inf = std::numeric_limits<float>::infinity();
		return {{{0.f, inf}, {0.f, inf}}};
	}

	math::interval<float> element::width_constraints(float) const
	{
		return size_constraints()[0];
	}

	math::interval<float> element::height_constraints(float) const
	{
		return size_constraints()[1];
	}

	std::shared_ptr<style> element::set_style(std::shared_ptr<struct style> st)
	{
		if (style_)
			style_->use_as_style.erase(this);

		std::swap(st, style_);

		if (style_)
			style_->use_as_style.insert(this);

		style_updated();

		return st;
	}

	std::shared_ptr<struct style> element::set_own_style(std::shared_ptr<struct style> st)
	{
		if (style_)
			style_->use_as_own_style.erase(this);

		std::swap(st, own_style_);

		if (style_)
			style_->use_as_own_style.insert(this);

		own_style_updated();

		return st;
	}

	void element::style_updated() const
	{
		merged_style_ = nullptr;
		own_style_updated();
		for (auto c : children())
			if (c) c->style_updated();
	}

	void element::own_style_updated() const
	{
		merged_own_style_ = nullptr;
	}

	std::shared_ptr<struct style> element::merged_style() const
	{
		if (!merged_style_)
		{
			ui::style result;

			if (style_)
				merge(result, *style_);

			if (parent())
				merge(result, *parent()->merged_style());

			merge(result, default_style());

			merged_style_ = std::make_shared<ui::style>(result);
		}

		return merged_style_;
	}

	std::shared_ptr<struct style> element::merged_own_style() const
	{
		if (!merged_own_style_)
		{
			ui::style result;

			if (own_style_)
				merge(result, *own_style_);

			merge(result, *merged_style());

			merged_own_style_ = std::make_shared<ui::style>(result);
		}

		return merged_own_style_;
	}

	bool element::in_text_input()
	{
		return SDL_IsTextInputActive() == SDL_TRUE;
	}

	void element::start_text_input()
	{
		SDL_StartTextInput();
	}

	void element::stop_text_input()
	{
		SDL_StopTextInput();
	}

	std::optional<std::string> element::set_id(std::optional<std::string> id)
	{
		std::swap(id_, id);
		return id;
	}

	std::optional<std::string> element::set_hint(std::optional<std::string> hint)
	{
		std::swap(hint_, hint);
		return hint;
	}

	element::~element()
	{
		set_style(nullptr);
		set_own_style(nullptr);
	}

	void element::post(util::function<void()> f) const
	{
		auto l = loop();
		if (l)
			l->post(std::move(f));
		else
			delayed_callbacks_.push_back(std::move(f));
	}

	void element::post_reshape()
	{
		if (auto p = parent())
		{
			p->post_reshape();
			return;
		}

		if (reshape_posted_)
			return;

		if (!loop_)
			return;

		reshape_posted_ = true;

		auto weak_self = weak_from_this();

		post([weak_self]{
			if (auto self = weak_self.lock())
			{
				self->reshape_posted_ = false;
				if (!self->parent())
					self->reshape();
			}
		});
	}

	void element::release_children()
	{
		for (auto c : children())
			if (c) c->set_parent(nullptr);
	}

	void element::post_delayed_callbacks() const
	{
		auto l = loop();
		assert(l);
		for (auto & c : delayed_callbacks_)
			l->post(std::move(c));
		delayed_callbacks_.clear();

		for (auto const & c : children())
			if (c)
				c->post_delayed_callbacks();
	}

	element * find_element_by_id(element * root, std::string_view id)
	{
		auto visitor = util::recursive([&](auto && self, element * e) -> element *
		{
			if (!e) return nullptr;

			if (e->id() && e->id() == id)
				return e;

			for (auto c : util::reversed(e->children()))
				if (auto r = self(c))
					return r;

			return nullptr;
		});

		return visitor(root);
	}

	void send_fake_mouse_move_event(element * e, bool mouseover)
	{
		auto const box = e->shape().bbox();
		if (mouseover)
			e->on_event(mouse_move{math::cast<int>(box.center())});
		else
			e->on_event(mouse_move{math::cast<int>(box.corner(0.f, 0.f) - math::vector{1000.f, 1000.f})});
	}

}
