#include <psemek/ui/screen.hpp>

namespace psemek::ui
{

	screen::screen()
		: container_(this)
	{}

	element::children_range screen::children() const
	{
		return container_.children();
	}

	bool screen::add_child(std::shared_ptr<element> c, x_policy x, y_policy y)
	{
		auto i = container_.add(std::move(c));
		if (i >= policies_.size())
			policies_.resize(i + 1);
		policies_[i] = {x, y};
		post_reshape();
		return true;
	}

	bool screen::add_child(std::shared_ptr<element> c)
	{
		return add_child(std::move(c), x_policy::floating, y_policy::floating);
	}

	bool screen::has_child(element * c) const
	{
		return static_cast<bool>(container_.find(c));
	}

	std::shared_ptr<element> screen::remove_child(element * c)
	{
		return container_.remove(c);
	}

	bool screen::move_to_top(element * c)
	{
		if (auto idx = container_.find(c))
		{
			auto e = container_.remove(*idx);
			auto new_idx = container_.size();
			container_.add(e, new_idx);
			policies_.resize(container_.size());
			policies_[new_idx] = policies_[*idx];
			return true;
		}
		return false;
	}

	void screen::reshape(math::box<float, 2> const & bbox)
	{
		shape_.box = bbox;
		auto const m = bbox.center();

		auto cs = children();
		for (std::size_t i = 0; i < cs.size(); ++i)
		{
			auto c = cs[i];
			if (!c) continue;

			auto sc = c->size_constraints();

			math::box<float, 2> cbox = c->shape().bbox();

			auto update_width = [&](float width)
			{
				width = std::min(bbox[0].length(), width);

				switch (policies_[i].x)
				{
				case x_policy::left:
					cbox[0] = {bbox[0].min, bbox[0].min + width};
					break;
				case x_policy::center:
					cbox[0] = {m[0] - width / 2.f, m[0] + width / 2.f};
					break;
				case x_policy::right:
					cbox[0] = {bbox[0].max - width, bbox[0].max};
					break;
				case x_policy::fill:
					cbox[0] = bbox[0];
					break;
				case x_policy::floating:
					cbox[0] = math::expand(cbox[0], (width - cbox[0].length()) / 2.f);

					if (cbox[0].min < bbox[0].min)
						cbox[0] += (bbox[0].min - cbox[0].min);
					else if (cbox[0].max > bbox[0].max)
						cbox[0] -= (cbox[0].max - bbox[0].max);
					break;
				}
			};

			auto update_height = [&](float height)
			{
				height = std::min(bbox[1].length(), height);

				switch (policies_[i].y)
				{
				case y_policy::top:
					cbox[1] = {bbox[1].min, bbox[1].min + height};
					break;
				case y_policy::center:
					cbox[1] = {m[1] - height / 2.f, m[1] + height / 2.f};
					break;
				case y_policy::bottom:
					cbox[1] = {bbox[1].max - height, bbox[1].max};
					break;
				case y_policy::fill:
					cbox[1] = bbox[1];
					break;
				case y_policy::floating:
					cbox[1] = math::expand(cbox[1], (height - cbox[1].length()) / 2.f);

					if (cbox[1].min < bbox[1].min)
						cbox[1] += (bbox[1].min - cbox[1].min);
					else if (cbox[1].max > bbox[1].max)
						cbox[1] -= (cbox[1].max - bbox[1].max);
					break;
				}
			};

			if (width_first())
			{
				update_width(sc[0].min);
				auto hc = c->height_constraints(cbox[0].length());
				update_height(hc.min);
			}
			else
			{
				update_height(sc[1].min);
				auto wc = c->width_constraints(cbox[1].length());
				update_width(wc.min);
			}

			c->reshape(cbox);
		}
	}

	math::box<float, 2> screen::size_constraints() const
	{
		math::box<float, 2> result = element::size_constraints();
		for (auto c : children())
			if (c) result &= c->size_constraints();
		return result;
	}

	screen::~screen()
	{
		release_children();
	}

}
