#pragma once

#include <psemek/math/dynamic.hpp>
#include <psemek/util/exception.hpp>

#include <memory>

namespace psemek::math::detail
{

	struct empty_array_exception
		: util::exception
	{
		empty_array_exception(util::stacktrace stacktrace = {})
			: util::exception("Indexing into a zero-dimensional array", std::move(stacktrace))
		{}
	};

	template <std::size_t N>
	struct dynamic_dimensions
	{};

	template <>
	struct dynamic_dimensions<dynamic>
	{
		std::size_t size = 0;
	};

	template <std::size_t N>
	void check_dynamic_size(dynamic_dimensions<N> d1, dynamic_dimensions<N> d2)
	{
		if constexpr (N == dynamic)
		{
			if (d1.size != d2.size)
				throw dynamic_size_mismatch(d1.size, d2.size);
		}
	}

	template <typename T, std::size_t N>
	struct array
	{
		struct type
		{
			static constexpr std::size_t size = N;

			T data[N];

			type() = default;

			type(dynamic_dimensions<N>){}

			dynamic_dimensions<N> dimensions() const { return {}; }

			T const & operator[](std::size_t i) const { return data[i]; }
			T & operator[](std::size_t i) { return data[i]; }
		};
	};

	template <typename T>
	struct array<T, 0>
	{
		struct type
		{
			static constexpr std::size_t size = 0;

			type() = default;

			type(dynamic_dimensions<0>){}

			dynamic_dimensions<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<T, dynamic>
	{
		struct type
		{
			std::size_t size;
			std::unique_ptr<T[]> data;

			type(dynamic_dimensions<dynamic> dimensions)
				: size(dimensions.size)
				, data(std::make_unique_for_overwrite<T[]>(dimensions.size))
			{}

			dynamic_dimensions<dynamic> dimensions() const { return {.size = size}; }

			T const & operator[](std::size_t i) const { return data[i]; }
			T & operator[](std::size_t i) { return data[i]; }
		};
	};

	template <typename T, typename ... Args>
	struct all_convertible_to;

	template <typename T>
	struct all_convertible_to<T>
		: std::true_type
	{};

	template <typename T, typename H, typename ... Args>
	struct all_convertible_to<T, H, Args...>
		: std::bool_constant<std::is_convertible_v<H, T>
			&& all_convertible_to<T, Args...>::value>
	{};

}
