#include <std_include.hpp>
#include "loader/component_loader.hpp"

#include "game/game.hpp"
#include "game/dvars.hpp"

#include <utils/hook.hpp>
#include <utils/string.hpp>

namespace colors
{
	namespace
	{
		std::vector<DWORD> color_table;

		DWORD hsv_to_rgb(const game::HsvColor hsv)
		{
			DWORD rgb;

			if (hsv.s == 0)
			{
				return RGB(hsv.v, hsv.v, hsv.v);
			}

			// converting to 16 bit to prevent overflow
			const unsigned int h = hsv.h;
			const unsigned int s = hsv.s;
			const unsigned int v = hsv.v;

			const auto region = static_cast<uint8_t>(h / 43);
			const auto remainder = (h - (region * 43)) * 6;

			const auto p = static_cast<uint8_t>((v * (255 - s)) >> 8);
			const auto q = static_cast<uint8_t>(
				(v * (255 - ((s * remainder) >> 8))) >> 8);
			const auto t = static_cast<uint8_t>(
				(v * (255 - ((s * (255 - remainder)) >> 8))) >> 8);

			switch (region)
			{
			case 0:
				rgb = RGB(v, t, p);
				break;
			case 1:
				rgb = RGB(q, v, p);
				break;
			case 2:
				rgb = RGB(p, v, t);
				break;
			case 3:
				rgb = RGB(p, q, v);
				break;
			case 4:
				rgb = RGB(t, p, v);
				break;
			default:
				rgb = RGB(v, p, q);
				break;
			}

			return rgb;
		}

		int color_index(const char c)
		{
			const auto index = c - 48;
			return index >= 0xC ? 7 : index;
		}

		char add(const uint8_t r, const uint8_t g, const uint8_t b)
		{
			const char index = '0' + static_cast<char>(color_table.size());
			color_table.push_back(RGB(r, g, b));
			return index;
		}

		void com_clean_name_stub(const char* in, char* out, const int out_size)
		{
			// Check that the name is at least 3 char without colors
			char name[16]{};

			game::I_strncpyz(out, in, std::min<int>(out_size, sizeof(name)));

			utils::string::strip(out, name, std::strlen(out) + 1);
			if (std::strlen(name) < 3)
			{
				game::I_strncpyz(out, "UnnamedPlayer", std::min<int>(out_size, sizeof(name)));
			}
		}

		char* i_clean_str_stub(char* string)
		{
			utils::string::strip(string, string, std::strlen(string) + 1);

			return string;
		}

		int cl_get_client_name_and_clan_tag_stub(const int local_client_num, const int index, char* name_buf, const int name_size, char* clan_tag_buf, int clan_tag_size)
		{
			// CL_GetClientNameAndClanTag -> CL_GetClientNameAndClanTagColorize
			const auto result = utils::hook::invoke<int>(0x1402CF790, local_client_num, index, name_buf, name_size, clan_tag_buf, clan_tag_size);

			utils::string::strip(name_buf, name_buf, name_size);

			return result;
		}

		int com_sprintf_stub(char* dest, int size, const char* fmt, const char* name)
		{
			const auto len = game::Com_sprintf(dest, size, fmt, name);
			if (len < 0)
			{
				game::I_strncpyz(dest, "UnnamedAgent", size);
				return len;
			}

			// This should inherit the name of the owner (a player) which already passed the length check in Com_CleanName
			utils::string::strip(dest, dest, len + 1);

			return len;
		}

		void rb_lookup_color_stub(const char index, DWORD* color)
		{
			*color = RGB(255, 255, 255);

			if (index == '8')
			{
				*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD958, 0x1480E85BC));
			}
			else if (index == '9')
			{
				*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD95C, 0x1480E85C0));
			}
			else if (index == ':')
			{
				*color = hsv_to_rgb({static_cast<uint8_t>((game::Sys_Milliseconds() / 100) % 256), 255, 255});
			}
			else if (index == ';')
			{
				*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD964, 0x1480E85C8));
			}
			else
			{
				*color = color_table[color_index(index)];
			}
		}
	}

	class component final : public component_interface
	{
	public:
		void post_unpack() override
		{
			if (game::environment::is_dedi())
			{
				return;
			}

			if (!game::environment::is_sp())
			{
				// allows colored name in-game
				utils::hook::call(0x1403881CF, com_clean_name_stub);
				utils::hook::call(0x140388224, com_clean_name_stub);

				// don't apply colors to overhead names
				utils::hook::call(0x14025CE79, cl_get_client_name_and_clan_tag_stub);

				// don't apply colors to overhead names of agents (like dogs or juggernauts)
				// hook Com_sprintf in CL_GetAgentName
				utils::hook::call(0x1402CF760, com_sprintf_stub);

				// patch I_CleanStr
				utils::hook::jump(0x1404F63C0, i_clean_str_stub);
			}

			// force new colors
			utils::hook::jump(SELECT_VALUE(0x14055DCC0, 0x14062AE80), rb_lookup_color_stub);

			// add colors
			add(0, 0, 0); // 0  - Black
			add(255, 49, 49); // 1  - Red
			add(134, 192, 0); // 2  - Green
			add(255, 173, 34); // 3  - Yellow
			add(0, 135, 193); // 4  - Blue
			add(32, 197, 255); // 5  - Light Blue
			add(151, 80, 221); // 6  - Pink
			add(255, 255, 255); // 7  - White

			add(0, 0, 0); // 8  - Team color (axis?)
			add(0, 0, 0); // 9  - Team color (allies?)

			add(0, 0, 0); // 10 - Rainbow (:)
			add(0, 0, 0);
			// 11 - Server color (;) - using that color in infostrings (e.g. your name) fails, ';' is an illegal character!
		}
	};
}

REGISTER_COMPONENT(colors::component)
