#pragma once

#include <psemek/gfx/pixmap.hpp>
#include <psemek/pcg/seamless.hpp>

#include <vector>
#include <type_traits>

namespace psemek::pcg
{

	// TODO: return a proper generator instead of a lambda
	template <typename Gen, typename F>
	auto map_(Gen && gen, F && f)
	{
		return [gen = std::forward<Gen>(gen), f = std::forward<F>(f)](float x, float y)
		{
			return f(gen(x, y));
		};
	}

	template <typename T, typename F, typename R = decltype(std::declval<F>()(std::declval<T>())),
		typename = std::enable_if_t<std::is_same_v<T, R>>>
	auto map(gfx::basic_pixmap<T> p, F && f)
	{
		for (std::size_t j = 0; j < p.height(); ++j)
		{
			for (std::size_t i = 0; i < p.width(); ++i)
			{
				p(i, j) = f(p(i, j));
			}
		}

		return p;
	}

	template <typename T, typename F, typename R = decltype(std::declval<F>()(std::declval<T>())),
		typename = std::enable_if_t<!std::is_same_v<T, R>>>
	auto map(gfx::basic_pixmap<T> const & p, F && f)
	{
		gfx::basic_pixmap<R> result({p.width(), p.height()});

		for (std::size_t j = 0; j < p.height(); ++j)
		{
			for (std::size_t i = 0; i < p.width(); ++i)
			{
				result(i, j) = f(p(i, j));
			}
		}

		return result;
	}

	template <typename T, typename F>
	auto map_neumann(gfx::basic_pixmap<T> const & pixmap, F && f, seamless_tag)
	{
		std::vector<T> n;
		n.reserve(4);

		using R = decltype(f(pixmap(0, 0), n));

		std::size_t const w = pixmap.width();
		std::size_t const h = pixmap.height();

		gfx::basic_pixmap<R> result({w, h});

		for (std::size_t j = 0; j < h; ++j)
		{
			for (std::size_t i = 0; i < w; ++i)
			{
				n.push_back(pixmap((i + 1) % w, j));
				n.push_back(pixmap((i + w - 1) % w, j));
				n.push_back(pixmap(i, (j + 1) % h));
				n.push_back(pixmap(i, (j + h - 1) % h));

				result(i, j) = f(pixmap(i, j), n);

				n.clear();
			}
		}

		return result;
	}

	template <typename T, typename F>
	auto map_moore(gfx::basic_pixmap<T> const & pixmap, F && f, seamless_tag)
	{
		std::vector<T> n;
		n.reserve(8);

		using R = decltype(f(pixmap(0, 0), n));

		std::size_t const w = pixmap.width();
		std::size_t const h = pixmap.height();

		gfx::basic_pixmap<R> result({w, h});

		for (std::size_t j = 0; j < h; ++j)
		{
			for (std::size_t i = 0; i < w; ++i)
			{
				n.push_back(pixmap((i + 1) % w, j));
				n.push_back(pixmap((i + 1) % w, (j + 1) % h));
				n.push_back(pixmap(i, (j + 1) % h));
				n.push_back(pixmap((i + w - 1) % w, (j + 1) % h));
				n.push_back(pixmap((i + w - 1) % w, j));
				n.push_back(pixmap((i + w - 1) % w, (j + h - 1) % h));
				n.push_back(pixmap(i, (j + h - 1) % h));
				n.push_back(pixmap((i + 1) % w, (j + h - 1) % h));

				result(i, j) = f(pixmap(i, j), n);

				n.clear();
			}
		}

		return result;
	}

}
