#pragma once

#include <psemek/vecr/sdf.hpp>
#include <psemek/math/point.hpp>
#include <psemek/math/matrix.hpp>
#include <psemek/math/homogeneous.hpp>
#include <psemek/math/gauss.hpp>
#include <psemek/math/box.hpp>

namespace psemek::vecr
{

	template <typename Shape>
	struct translate
	{
		Shape shape;
		math::vector<float, 2> delta{0.f, 0.f};
	};

	template <typename Shape>
	struct rotate
	{
		Shape shape;
		float angle = 0.f;
		math::point<float, 2> origin{0.f, 0.f};
	};

	template <typename Shape>
	struct scale
	{
		Shape shape;
		math::vector<float, 2> factor{1.f, 1.f};
		math::point<float, 2> origin{0.f, 0.f};
	};

	template <typename Shape>
	struct transform
	{
		Shape shape;
		math::matrix<float, 3, 3> matrix = math::matrix<float, 3, 3>::identity();
	};

	template <typename Shape>
	sdf_sample sdf(translate<Shape> const & s, math::point<float, 2> const & p)
	{
		return sdf(s.shape, p - s.delta);
	}

	template <typename Shape>
	math::box<float, 2> bbox(translate<Shape> const & s)
	{
		return bbox(s.shape) + s.delta;
	}

	template <typename Shape>
	sdf_sample sdf(rotate<Shape> const & s, math::point<float, 2> const & p)
	{
		auto result = sdf(s.shape, s.origin + math::rotate(p - s.origin, -s.angle));
		result.gradient = math::rotate(result.gradient, s.angle);
		return result;
	}

	template <typename Shape>
	math::box<float, 2> bbox(rotate<Shape> const & s)
	{
		auto sbox = bbox(s.shape);

		math::box<float, 2> result;

		for (int y = 0; y <= 1; ++y)
			for (int x = 0; x <= 1; ++x)
				result |= s.origin + math::rotate(sbox.corner(x, y) - s.origin, s.angle);

		return result;
	}

	template <typename Shape>
	sdf_sample sdf(scale<Shape> const & s, math::point<float, 2> const & p)
	{
		auto result = sdf(s.shape, s.origin + math::pointwise_mult(p - s.origin, {1.f / s.factor[0], 1.f / s.factor[1]}));
		auto old_grad = math::length(result.gradient);
		result.gradient[0] /= s.factor[0];
		result.gradient[1] /= s.factor[1];
		if (auto new_grad = math::length(result.gradient); new_grad != 0.f)
			result.value *= (old_grad / new_grad);
		return result;
	}

	template <typename Shape>
	math::box<float, 2> bbox(scale<Shape> const & s)
	{
		auto sbox = bbox(s.shape);

		math::box<float, 2> result;

		for (int y = 0; y <= 1; ++y)
			for (int x = 0; x <= 1; ++x)
				result |= s.origin + (sbox.corner(x, y) - s.origin) * s.factor;

		return result;
	}

	template <typename Shape>
	sdf_sample sdf(transform<Shape> const & s, math::point<float, 2> const & p)
	{
		auto minv = *math::inverse(s.matrix);

		auto q = minv * math::homogeneous(p);

		auto result = sdf(s.shape, math::as_point(q));

		auto old_grad = math::length(result.gradient);

		math::matrix<float, 2, 2> mgrad;
		mgrad[0][0] = (minv[0][0] * q[2] - minv[2][0] * q[0]) / (q[2] * q[2]);
		mgrad[0][1] = (minv[1][0] * q[2] - minv[2][0] * q[1]) / (q[2] * q[2]);
		mgrad[1][0] = (minv[0][1] * q[2] - minv[2][1] * q[0]) / (q[2] * q[2]);
		mgrad[1][1] = (minv[1][1] * q[2] - minv[2][1] * q[1]) / (q[2] * q[2]);

		result.gradient = mgrad * result.gradient;
		if (auto new_grad = math::length(result.gradient); new_grad != 0.f)
			result.value *= (old_grad / new_grad);

		return result;
	}

	template <typename Shape>
	math::box<float, 2> bbox(transform<Shape> const & s)
	{
		auto sbox = bbox(s.shape);

		if (!math::isfinite(sbox))
			return math::box<float, 2>::full();

		math::box<float, 2> result;

		for (int y = 0; y <= 1; ++y)
			for (int x = 0; x <= 1; ++x)
				result |= math::as_point(s.matrix * math::homogeneous(sbox.corner(x, y)));

		return result;
	}

}
