#pragma once

#include <util/types.hpp>
#include <Utilities/geometry.h>
#include "gcm_enums.h"
#include "Utilities/StrFmt.h"

namespace rsx
{
	enum texture_control_bits : u32
	{
		GAMMA_A = 0,
		GAMMA_R,
		GAMMA_G,
		GAMMA_B,
		ALPHAKILL,
		RENORMALIZE,
		EXPAND_A,
		EXPAND_R,
		EXPAND_G,
		EXPAND_B,
		SEXT_A,
		SEXT_R,
		SEXT_G,
		SEXT_B,
		DEPTH_FLOAT,
		DEPTH_COMPARE_OP,
		DEPTH_COMPARE_1,
		DEPTH_COMPARE_2,
		FILTERED_MAG,
		FILTERED_MIN,
		UNNORMALIZED_COORDS,
		CLAMP_TEXCOORDS_BIT,
		WRAP_S,
		WRAP_T,
		WRAP_R,
		FF_SIGNED_BIT,
		FF_BIASED_RENORM_BIT,
		FF_GAMMA_BIT,
		FF_16BIT_CHANNELS_BIT,

		// Meta
		GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A),
		GAMMA_RGB_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B),
		GAMMA_OFFSET = GAMMA_A,

		EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A),
		EXPAND_OFFSET = EXPAND_A,

		SEXT_MASK = (1 << SEXT_R) | (1 << SEXT_G) | (1 << SEXT_B) | (1 << SEXT_A),
		SEXT_OFFSET = SEXT_A,

		FORMAT_FEATURES_OFFSET = FF_SIGNED_BIT,
	};

	struct texture_channel_remap_t
	{
		u32 encoded = 0xDEAD;
		std::array<u8, 4> control_map;
		std::array<u8, 4> channel_map;

		template <typename T>
		std::array<T, 4> remap(const std::array<T, 4>& components, T select_zero, T select_one) const
		{
			ensure(encoded != 0xDEAD, "Channel remap was not initialized");

			std::array<T, 4> remapped{};
			for (u8 channel = 0; channel < 4; ++channel)
			{
				switch (control_map[channel])
				{
				default:
					[[fallthrough]];
				case CELL_GCM_TEXTURE_REMAP_REMAP:
					remapped[channel] = components[channel_map[channel]];
					break;
				case CELL_GCM_TEXTURE_REMAP_ZERO:
					remapped[channel] = select_zero;
					break;
				case CELL_GCM_TEXTURE_REMAP_ONE:
					remapped[channel] = select_one;
					break;
				}
			}
			return remapped;
		}


		/**
		 * Remap color channel bits based on a remap vector. The output is a normalized selector of each color channel with spread.
		 * The input bits are an action selector. e.g a mask of channels that need to be interpreted as SNORM or BX2
		 * The output is a final mask on which post-sampling channels the operation applies to.
		 * Examples:
		 * - If we have remap as [ 1 R R R ] and mask of R (0010) then we get 1110. Remapper spreads 'R' action to all channels where it should apply.
		 */
		u32 shuffle_mask_bits(u32 bits) const
		{
			if (!bits || encoded == RSX_TEXTURE_REMAP_IDENTITY) [[likely]]
			{
				return bits;
			}

			u32 result = 0;
			for (u8 channel = 0; channel < 4; ++channel)
			{
				if (control_map[channel] != CELL_GCM_TEXTURE_REMAP_REMAP ||    // Channel not read from input
					(bits & (1u << channel_map[channel])) == 0)                // Input channel is not enabled in the mask
				{
					continue;
				}
				result |= (1u << channel);
			}
			return result;
		}

		template <typename T>
			requires std::is_integral_v<T> || std::is_floating_point_v<T>
		std::array<T, 4> remap(const std::array<T, 4>& components) const
		{
			return remap(components, static_cast<T>(0), static_cast<T>(1));
		}

		template <typename T>
		color4_base<T> remap(const color4_base<T>& components)
		{
			const std::array<T, 4> values = { components.a, components.r, components.g, components.b };
			const auto shuffled = remap(values, T{ 0 }, T{ 1 });
			return color4_base<T>(shuffled[1], shuffled[2], shuffled[3], shuffled[0]);
		}

		template <typename T>
			requires std::is_integral_v<T> || std::is_enum_v<T>
		texture_channel_remap_t with_encoding(T encoding) const
		{
			texture_channel_remap_t result = *this;
			result.encoded = encoding;
			return result;
		}
	};

	static const texture_channel_remap_t default_remap_vector =
	{
		.encoded = RSX_TEXTURE_REMAP_IDENTITY,
		.control_map = { CELL_GCM_TEXTURE_REMAP_REMAP, CELL_GCM_TEXTURE_REMAP_REMAP, CELL_GCM_TEXTURE_REMAP_REMAP, CELL_GCM_TEXTURE_REMAP_REMAP },
		.channel_map = { CELL_GCM_TEXTURE_REMAP_FROM_A, CELL_GCM_TEXTURE_REMAP_FROM_R, CELL_GCM_TEXTURE_REMAP_FROM_G, CELL_GCM_TEXTURE_REMAP_FROM_B }
	};

	static inline texture_channel_remap_t decode_remap_encoding(u32 remap_ctl)
	{
		// Remapping tables; format is A-R-G-B
		// Remap input table. Contains channel index to read color from
		texture_channel_remap_t result =
		{
			.encoded = remap_ctl
		};

		result.channel_map =
		{
			static_cast<u8>(remap_ctl & 0x3),
			static_cast<u8>((remap_ctl >> 2) & 0x3),
			static_cast<u8>((remap_ctl >> 4) & 0x3),
			static_cast<u8>((remap_ctl >> 6) & 0x3),
		};

		// Remap control table. Controls whether the remap value is used, or force either 0 or 1
		result.control_map =
		{
			static_cast<u8>((remap_ctl >> 8) & 0x3),
			static_cast<u8>((remap_ctl >> 10) & 0x3),
			static_cast<u8>((remap_ctl >> 12) & 0x3),
			static_cast<u8>((remap_ctl >> 14) & 0x3),
		};

		return result;
	}

	// Convert color write mask for G8B8 to R8G8
	static inline u32 get_g8b8_r8g8_clearmask(u32 mask)
	{
		u32 result = 0;
		if (mask & RSX_GCM_CLEAR_GREEN_BIT) result |= RSX_GCM_CLEAR_GREEN_BIT;
		if (mask & RSX_GCM_CLEAR_BLUE_BIT) result |= RSX_GCM_CLEAR_RED_BIT;

		return result;
	}

	static inline void get_g8b8_r8g8_colormask(bool& red, bool/*green*/, bool& blue, bool& alpha)
	{
		red = blue;
		blue = false;
		alpha = false;
	}

	static inline void get_g8b8_clear_color(u8& red, u8 /*green*/, u8 blue, u8 /*alpha*/)
	{
		red = blue;
	}

	static inline u32 get_abgr8_clearmask(u32 mask)
	{
		u32 result = 0;
		if (mask & RSX_GCM_CLEAR_RED_BIT) result |= RSX_GCM_CLEAR_BLUE_BIT;
		if (mask & RSX_GCM_CLEAR_GREEN_BIT) result |= RSX_GCM_CLEAR_GREEN_BIT;
		if (mask & RSX_GCM_CLEAR_BLUE_BIT) result |= RSX_GCM_CLEAR_RED_BIT;
		if (mask & RSX_GCM_CLEAR_ALPHA_BIT) result |= RSX_GCM_CLEAR_ALPHA_BIT;
		return result;
	}

	static inline void get_abgr8_colormask(bool& red, bool /*green*/, bool& blue, bool /*alpha*/)
	{
		std::swap(red, blue);
	}

	static inline void get_abgr8_clear_color(u8& red, u8 /*green*/, u8& blue, u8 /*alpha*/)
	{
		std::swap(red, blue);
	}

	template <typename T, typename U>
		requires std::is_integral_v<T>&& std::is_integral_v<U>
	u8 renormalize_color8(T input, U base)
	{
		// Base will be some POT-1 value
		const int value = static_cast<u8>(input & base);
		return static_cast<u8>((value * 255) / base);
	}

	static inline void get_rgb565_clear_color(u8& red, u8& green, u8& blue, u8 /*alpha*/)
	{
		// RSX clear color is just a memcpy, so in this case the input is ARGB8 so only BG have the 16-bit input
		const u16 raw_value = static_cast<u16>(green) << 8 | blue;
		blue = renormalize_color8(raw_value, 0x1f);
		green = renormalize_color8(raw_value >> 5, 0x3f);
		red = renormalize_color8(raw_value >> 11, 0x1f);
	}

	static inline void get_a1rgb555_clear_color(u8& red, u8& green, u8& blue, u8& alpha, u8 alpha_override)
	{
		// RSX clear color is just a memcpy, so in this case the input is ARGB8 so only BG have the 16-bit input
		const u16 raw_value = static_cast<u16>(green) << 8 | blue;
		blue = renormalize_color8(raw_value, 0x1f);
		green = renormalize_color8(raw_value >> 5, 0x1f);
		red = renormalize_color8(raw_value >> 10, 0x1f);

		// Alpha can technically be encoded into the clear but the format normally just injects constants.
		// Will require hardware tests when possible to determine which approach makes more sense.
		// alpha = static_cast<u8>((raw_value & (1 << 15)) ? 255 : 0);
		alpha = alpha_override;
	}

	static inline u32 get_b8_clearmask(u32 mask)
	{
		u32 result = 0;
		if (mask & RSX_GCM_CLEAR_BLUE_BIT) result |= RSX_GCM_CLEAR_RED_BIT;
		return result;
	}

	static inline void get_b8_colormask(bool& red, bool& green, bool& blue, bool& alpha)
	{
		red = blue;
		green = false;
		blue = false;
		alpha = false;
	}

	static inline void get_b8_clear_color(u8& red, u8 /*green*/, u8& blue, u8 /*alpha*/)
	{
		std::swap(red, blue);
	}

	static inline color4f decode_border_color(u32 colorref)
	{
		color4f result;
		result.b = (colorref & 0xFF) / 255.f;
		result.g = ((colorref >> 8) & 0xFF) / 255.f;
		result.r = ((colorref >> 16) & 0xFF) / 255.f;
		result.a = ((colorref >> 24) & 0xFF) / 255.f;
		return result;
	}

	static inline u32 encode_color_to_storage_key(const color4f& color)
	{
		const u32 r = static_cast<u8>(color.r * 255);
		const u32 g = static_cast<u8>(color.g * 255);
		const u32 b = static_cast<u8>(color.b * 255);
		const u32 a = static_cast<u8>(color.a * 255);

		return (a << 24) | (b << 16) | (g << 8) | r;
	}

	static inline const std::array<bool, 4> get_write_output_mask(rsx::surface_color_format format)
	{
		constexpr std::array<bool, 4> rgba = { true, true, true, true };
		constexpr std::array<bool, 4> rgb = { true, true, true, false };
		constexpr std::array<bool, 4> rg = { true, true, false, false };
		constexpr std::array<bool, 4> r = { true, false, false, false };

		switch (format)
		{
		case rsx::surface_color_format::a8r8g8b8:
		case rsx::surface_color_format::a8b8g8r8:
		case rsx::surface_color_format::w16z16y16x16:
		case rsx::surface_color_format::w32z32y32x32:
			return rgba;
		case rsx::surface_color_format::x1r5g5b5_z1r5g5b5:
		case rsx::surface_color_format::x1r5g5b5_o1r5g5b5:
		case rsx::surface_color_format::r5g6b5:
		case rsx::surface_color_format::x8r8g8b8_z8r8g8b8:
		case rsx::surface_color_format::x8r8g8b8_o8r8g8b8:
		case rsx::surface_color_format::x8b8g8r8_z8b8g8r8:
		case rsx::surface_color_format::x8b8g8r8_o8b8g8r8:
			return rgb;
		case rsx::surface_color_format::g8b8:
			return rg;
		case rsx::surface_color_format::b8:
		case rsx::surface_color_format::x32:
			return r;
		default:
			fmt::throw_exception("Unknown surface format 0x%x", static_cast<int>(format));
		}
	}
}
