/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include "Math.hpp"
#include <fmt/format.h>
#include <cstdint>
#include <type_traits>
#include <typeinfo>
#include <stdexcept>
#include <string>

namespace OpenVulkano::Math
{
	/**
	 * A templated vector implementation that allows for a lower resolution of the y axis.
	 *
	 * @tparam T backing datatype
	 * @tparam REDUCE_Y_RESOLUTION if the y resolution should be reduced
	 * @tparam ASSERT_INPUT_VALUES if input values should be validated before they are used
	 * @tparam BITS_PER_COMPONENT how many bits each component should use. -1 for automatic sizing
	 */
	template<typename T = int64_t,
			 bool REDUCE_Y_RESOLUTION = false,
			 bool ASSERT_INPUT_VALUES = false,
			 int BITS_PER_COMPONENT = -1,
			 typename = std::enable_if_t<std::is_signed_v<T> && std::is_integral_v<T>>>
	class DenseVector3i
	{
		typedef DenseVector3i<T, REDUCE_Y_RESOLUTION, ASSERT_INPUT_VALUES, BITS_PER_COMPONENT> DenseVec3;

		static constexpr int GetBitCount()
		{
			if (BITS_PER_COMPONENT > 0) return BITS_PER_COMPONENT;
			if constexpr (std::is_same_v<T, int8_t>)
			{
				return REDUCE_Y_RESOLUTION ? 3 : 2;
			}
			if constexpr (std::is_same_v<T, int16_t>)
			{
				return REDUCE_Y_RESOLUTION ? 6 : 5;
			}
			if constexpr (std::is_same_v<T, int32_t>)
			{
				return REDUCE_Y_RESOLUTION ? 11 : 10;
			}
			return REDUCE_Y_RESOLUTION ? 22 : 21;
		}

		static constexpr int GetBitCountY()
		{
			if (BITS_PER_COMPONENT > 0) return BITS_PER_COMPONENT;
			if (!REDUCE_Y_RESOLUTION) return GetBitCount();
			return sizeof(T) * 8 - GetBitCount() * 2;
		}

		static constexpr int BITS = GetBitCount();
		static constexpr int BITS_Y = GetBitCountY();
		static constexpr T BITMASK = (1 << BITS) - 1;
		static constexpr int MAX_VALUE = (1 << (BITS - 1)) - 1;
		static constexpr int MAX_VALUE_Y = (1 << (BITS_Y - 1)) - 1;
		static constexpr int MIN_VALUE = -MAX_VALUE - 1;
		static constexpr int MIN_VALUE_Y = -MAX_VALUE_Y - 1;

		static constexpr void AssertValue(int value, int maxValue = MAX_VALUE, int minValue = MIN_VALUE)
		{
			if (!ASSERT_INPUT_VALUES) return;
			if (value > maxValue || value < minValue)
				throw std::range_error("Value to big for DenseVector");
		}

		T data;

	public:
		constexpr explicit DenseVector3i(const Math::Vector3i& vec3) : DenseVector3i(vec3.x, vec3.y, vec3.z) {}
		constexpr explicit DenseVector3i(const Math::Vector3i_SIMD& vec3) : DenseVector3i(vec3.x, vec3.y, vec3.z) {}
		constexpr DenseVector3i(T x, T y, T z)
			: data((x & BITMASK) | ((y & BITMASK) << (2 * BITS)) | ((z & BITMASK) << BITS))
		{
			AssertValue(x);
			AssertValue(y, MAX_VALUE_Y, MIN_VALUE_Y);
			AssertValue(z);
		}

		constexpr T Data() const { return data; }

		[[nodiscard]] constexpr int X() const { T v = data & BITMASK; if (v > MAX_VALUE) v |= ~BITMASK; return reinterpret_cast<int&>(v); }
		[[nodiscard]] constexpr int Y() const { T v = (data >> (2 * BITS)) & BITMASK; if (v > MAX_VALUE_Y) v |= ~BITMASK; return reinterpret_cast<int&>(v); }
		[[nodiscard]] constexpr int Z() const { T v = (data >> BITS) & BITMASK; if (v > MAX_VALUE) v |= ~BITMASK; return reinterpret_cast<int&>(v); }

		void SetX(int x)
		{
			AssertValue(x);
			data = (data & !BITMASK) | (x & BITMASK);
		}

		void SetY(int y)
		{
			AssertValue(y, MAX_VALUE_Y, MIN_VALUE_Y);
			data = (data & !(BITMASK << (2 * BITS))) | ((y & BITMASK) << (2 * BITS));
		}

		void SetZ(int z)
		{
			AssertValue(z);
			data = (data & !(BITMASK << BITS)) | ((z & BITMASK) << BITS);
		}

		[[nodiscard]] std::string ToString(const std::string& separator) const
		{
			return std::to_string(X()) + separator + std::to_string(Y()) + separator + std::to_string(Z());
		}

		[[nodiscard]] std::string ToString() const
		{
			return fmt::format("{},{},{}", X(), Y(), Z());
		}

		//[[nodiscard]] constexpr operator T() const { return data; }

		[[nodiscard]] constexpr bool operator <(DenseVec3 rhs) const
		{
			return data < rhs.data;
		}

		[[nodiscard]] bool operator>(DenseVec3 rhs) const
		{
			return rhs < *this;
		}

		[[nodiscard]] bool operator>=(DenseVec3 rhs) const
		{
			return !(*this < rhs);
		}

		[[nodiscard]] bool operator<=(DenseVec3 rhs) const
		{
			return !(rhs < *this);
		}

		[[nodiscard]] constexpr bool operator ==(DenseVec3 rhs) const
		{
			return data == rhs.data;
		}

		[[nodiscard]] constexpr bool operator !=(DenseVec3 rhs) const
		{
			return data != rhs.data;
		}

		[[nodiscard]] explicit constexpr operator Math::Vector3i() const
		{
			return { X(), Y(), Z() };
		}

		[[nodiscard]] explicit constexpr operator Math::Vector3i_SIMD() const
		{
			return { X(), Y(), Z() };
		}
	};
}
