#include <psemek/vecr/renderer.hpp>
#include <psemek/math/swizzle.hpp>

namespace psemek::vecr
{

	void renderer::reset(math::vector<std::size_t, 2> const & size, std::size_t samples, gfx::color_rgba const & color)
	{
		samples_ = samples;
		canvas_.assign({size[0] * samples, size[1] * samples}, color);
		result_.assign({size[0], size[1]}, color);
		need_resolve_ = false;
	}

	void renderer::reset(gfx::pixmap_rgba image, std::size_t samples)
	{
		result_ = std::move(image);
		canvas_.resize({result_.width() * samples, result_.height() * samples});
		for (auto idx : canvas_.indices())
			canvas_(idx) = result_({idx[0] / samples, idx[1] / samples});
		need_resolve_ = false;
	}

	math::vector<std::size_t, 2> renderer::size() const
	{
		return {result_.width(), result_.height()};
	}

	std::size_t renderer::samples() const
	{
		return samples_;
	}

	gfx::pixmap_rgba const & renderer::result() const
	{
		resolve();
		return result_;
	}

	gfx::pixmap_rgba renderer::release()
	{
		resolve();
		canvas_.clear();
		return std::move(result_);
	}

	gfx::pixmap_rgba const & renderer::canvas() const
	{
		return canvas_;
	}

	gfx::pixmap_rgba renderer::release_canvas()
	{
		result_.clear();
		need_resolve_ = false;
		return std::move(canvas_);
	}

	void renderer::clear(gfx::color_rgba const & color)
	{
		canvas_.fill(color);
		result_.fill(color);
		need_resolve_ = false;
	}

	void renderer::set_pixel(math::vector<std::size_t, 2> const & coords, gfx::color_rgba const & color)
	{
		for (std::size_t ty = 0; ty < samples_; ++ty)
		{
			for (std::size_t tx = 0; tx < samples_; ++tx)
			{
				canvas_(coords[0] * samples_ + tx, coords[1] * samples_ + ty) = color;
			}
		}
	}

	void renderer::draw(primitive const & primitive)
	{
		float const aa = primitive.blur / 2.f;

		auto const box = math::expand(bbox(primitive.mask), aa);

		int xmin = std::floor(std::max(0.f, box[0].min * samples_));
		int xmax = std::floor(std::min(canvas_.width() - 1.f, box[0].max * samples_));
		int ymin = std::floor(std::max(0.f, box[1].min * samples_));
		int ymax = std::floor(std::min(canvas_.height() - 1.f, box[1].max * samples_));

		for (int y = ymin; y <= ymax; ++y)
		{
			for (int x = xmin; x <= xmax; ++x)
			{
				math::point const center{(x + 0.5f) / samples_, (y + 0.5f) / samples_};

				auto const filter_sample = sdf(primitive.filter, center);

				if (filter_sample.value > aa) continue;

				auto const sample = sdf(primitive.mask, center);

				if (sample.value > aa) continue;

				float blur = 1.f;
				if (sample.value > - aa)
					blur = (aa - sample.value) / (2.f * aa);

				auto color = colorize(primitive.colorizer, center, sample);
				color[3] *= blur;
				color[3] *= primitive.alpha;

				canvas_(x, y) = gfx::to_coloru8(primitive.blend(gfx::to_colorf(canvas_(x, y)), color));
			}
		}

		need_resolve_ = true;
	}

	void renderer::resolve() const
	{
		if (!need_resolve_) return;

		for (auto const & idx : result_.indices())
		{
			gfx::color_4f sum{0.f, 0.f, 0.f, 0.f};

			for (std::size_t y = 0; y < samples_; ++y)
			{
				for (std::size_t x = 0; x < samples_; ++x)
				{
					sum += gfx::premult(gfx::to_colorf(canvas_({idx[0] * samples_ + x, idx[1] * samples_ + y})));
				}
			}

			result_(idx) = gfx::to_coloru8(gfx::unpremult(sum / (1.f * samples_ * samples_)));
		}

		need_resolve_ = false;
	}

}
