#pragma once

#include <psemek/math/detail/array.hpp>

namespace psemek::math::detail
{

	template <std::size_t R, std::size_t C>
	struct dynamic_dimensions_2d
	{
		dynamic_dimensions<R> rows = {};
		dynamic_dimensions<C> columns = {};
	};

	template <std::size_t R, std::size_t C>
	void check_dynamic_size(dynamic_dimensions_2d<R, C> d1, dynamic_dimensions_2d<R, C> d2)
	{
		check_dynamic_size(d1.rows, d2.rows);
		check_dynamic_size(d1.columns, d2.columns);
	}

	template <typename T, std::size_t R, std::size_t C>
	struct array_2d
	{
		struct type
		{
			T data[R * C];

			static constexpr std::size_t rows = R;
			static constexpr std::size_t columns = C;

			type(dynamic_dimensions_2d<R, C>) {}

			dynamic_dimensions_2d<R, C> dimensions() const { return {}; }

			T const * operator[](std::size_t row) const { return data + C * row; }
			T * operator[](std::size_t row) { return data + C * row; }
		};
	};

	template <typename T, std::size_t R>
	struct array_2d<T, R, 0>
	{
		struct type
		{
			static constexpr std::size_t rows = R;
			static constexpr std::size_t columns = 0;

			type(dynamic_dimensions_2d<R, 0>) {}

			dynamic_dimensions_2d<R, 0> dimensions() const { return {}; }

			T const * operator[](std::size_t) const { throw empty_array_exception{}; }
			T * operator[](std::size_t) { throw empty_array_exception{}; }
		};
	};

	template <typename T>
	struct array_2d<T, dynamic, 0>
	{
		struct type
		{
			std::size_t rows;
			static constexpr std::size_t columns = 0;

			type(dynamic_dimensions_2d<dynamic, 0> dimensions)
				: rows(dimensions.rows.size)
			{}

			dynamic_dimensions_2d<dynamic, 0> dimensions() const { return {.rows = {rows}}; }

			T const * operator[](std::size_t) const { throw empty_array_exception{}; }
			T * operator[](std::size_t) { throw empty_array_exception{}; }
		};
	};

	template <typename T, std::size_t C>
	struct array_2d<T, 0, C>
	{
		struct type
		{
			static constexpr std::size_t rows = 0;
			static constexpr std::size_t columns = C;

			type(dynamic_dimensions_2d<0, C>) {}

			dynamic_dimensions_2d<0, C> dimensions() const { return {}; }

			T const * operator[](std::size_t) const { throw empty_array_exception{}; }
			T * operator[](std::size_t) { throw empty_array_exception{}; }
		};
	};

	template <typename T>
	struct array_2d<T, 0, dynamic>
	{
		struct type
		{
			static constexpr std::size_t rows = 0;
			std::size_t columns;

			type(dynamic_dimensions_2d<0, dynamic> dimensions)
				: columns(dimensions.columns.size)
			{}

			dynamic_dimensions_2d<0, dynamic> dimensions() const { return {.columns = {columns}}; }

			T const * operator[](std::size_t) const { throw empty_array_exception{}; }
			T * operator[](std::size_t) { throw empty_array_exception{}; }
		};
	};

	template <typename T>
	struct array_2d<T, 0, 0>
	{
		struct type
		{
			static constexpr std::size_t rows = 0;
			static constexpr std::size_t columns = 0;

			type(dynamic_dimensions_2d<0, 0>) {}

			dynamic_dimensions_2d<0, 0> dimensions() const { return {}; }

			T const * operator[](std::size_t) const { throw empty_array_exception{}; }
			T * operator[](std::size_t) { throw empty_array_exception{}; }
		};
	};

	template <typename T, std::size_t R>
	struct array_2d<T, R, dynamic>
	{
		struct type
		{
			static constexpr std::size_t rows = R;
			std::size_t columns;
			std::unique_ptr<T[]> data;

			type(dynamic_dimensions_2d<R, dynamic> dimensions)
				: columns(dimensions.columns.size)
				, data(std::make_unique_for_overwrite<T[]>(rows * columns))
			{}

			dynamic_dimensions_2d<R, dynamic> dimensions() const { return {.columns = {columns}}; }

			T * operator[] (std::size_t row)
			{
				return data.get() + row * columns;
			}

			T const * operator[] (std::size_t row) const
			{
				return data.get() + row * columns;
			}

			type copy() const
			{
				type result;
				result.columns = columns;
				result.data = std::make_unique_for_overwrite<T[]>(rows * columns);
				std::copy(data.get(), data.get() + rows * columns, result.data.get());
				return result;
			}
		};
	};

	template <typename T, std::size_t C>
	struct array_2d<T, dynamic, C>
	{
		struct type
		{
			std::size_t rows;
			static constexpr std::size_t columns = C;
			std::unique_ptr<T[]> data;

			type(dynamic_dimensions_2d<dynamic, C> dimensions)
				: rows(dimensions.rows.size)
				, data(std::make_unique_for_overwrite<T[]>(rows * columns))
			{}

			dynamic_dimensions_2d<dynamic, C> dimensions() const { return {.rows = {rows}}; }

			T * operator[] (std::size_t row)
			{
				return data.get() + row * columns;
			}

			T const * operator[] (std::size_t row) const
			{
				return data.get() + row * columns;
			}

			type copy() const
			{
				type result;
				result.rows = rows;
				result.data = std::make_unique_for_overwrite<T[]>(rows * columns);
				std::copy(data.get(), data.get() + rows * columns, result.data.get());
				return result;
			}
		};
	};

	template <typename T>
	struct array_2d<T, dynamic, dynamic>
	{
		struct type
		{
			std::size_t rows;
			std::size_t columns;
			std::unique_ptr<T[]> data;

			type(dynamic_dimensions_2d<dynamic, dynamic> dimensions)
				: rows(dimensions.rows.size)
				, columns(dimensions.columns.size)
				, data(std::make_unique_for_overwrite<T[]>(rows * columns))
			{}

			dynamic_dimensions_2d<dynamic, dynamic> dimensions() const { return {.rows = {rows}, .columns = {columns}}; }

			T * operator[] (std::size_t row)
			{
				return data.get() + row * columns;
			}

			T const * operator[] (std::size_t row) const
			{
				return data.get() + row * columns;
			}

			type copy() const
			{
				type result;
				result.rows = rows;
				result.data = std::make_unique_for_overwrite<T[]>(rows * columns);
				std::copy(data.get(), data.get() + rows * columns, result.data.get());
				return result;
			}
		};
	};

}
