#include "STDInclude.hpp"

namespace Components
{
	Dvar::Var Colors::NewColors;
	std::vector<DWORD> Colors::ColorTable;

	DWORD Colors::HsvToRgb(Colors::HsvColor hsv)
	{
		DWORD rgb;
		unsigned char region, p, q, t;
		unsigned int h, s, v, remainder;

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

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

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

		p = static_cast<uint8_t>((v * (255 - s)) >> 8);
		q = static_cast<uint8_t>((v * (255 - ((s * remainder) >> 8))) >> 8);
		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;
	}

	void Colors::Strip(const char* in, char* out, int max)
	{
		if (!in || !out) return;

		max--;
		int current = 0;
		while (*in != 0 && current < max)
		{
			char index = *(in + 1);
			if (*in == '^' && (Colors::ColorIndex(index) != 7 || index == '7'))
			{
				++in;
			}
			else
			{
				*out = *in;
				++out;
				++current;
			}

			++in;
		}
		*out = '\0';
	}

	std::string Colors::Strip(std::string in)
	{
		char buffer[1000] = { 0 }; // Should be more than enough
		Colors::Strip(in.data(), buffer, sizeof(buffer));
		return std::string(buffer);
	}

	__declspec(naked) void Colors::ClientUserinfoChanged()
	{
		__asm
		{
			mov eax, [esp + 4h] // length
			sub eax, 1
			push eax

			push ecx // name
			push edx // buffer

			call strncpy

			add esp, 0Ch
			retn
		}
	}

	char* Colors::GetClientName(int localClientNum, int index, char *buf, size_t size)
	{
		Game::CL_GetClientName(localClientNum, index, buf, size);

		// Remove the colors
		strncpy_s(buf, size, Colors::Strip(buf).data(), size);

		return buf;
	}

	void Colors::PatchColorLimit(char limit)
	{
		Utils::Hook::Set<char>(0x535629, limit); // DrawText2d
		Utils::Hook::Set<char>(0x4C1BE4, limit); // No idea :P
		Utils::Hook::Set<char>(0x4863DD, limit); // No idea :P
		Utils::Hook::Set<char>(0x486429, limit); // No idea :P
		Utils::Hook::Set<char>(0x49A5A8, limit); // No idea :P
		Utils::Hook::Set<char>(0x505721, limit); // R_TextWidth
		Utils::Hook::Set<char>(0x505801, limit); // No idea :P
		Utils::Hook::Set<char>(0x50597F, limit); // No idea :P
		Utils::Hook::Set<char>(0x5815DB, limit); // No idea :P
		Utils::Hook::Set<char>(0x592ED0, limit); // No idea :P
		Utils::Hook::Set<char>(0x5A2E2E, limit); // No idea :P

		Utils::Hook::Set<char>(0x5A2733, limit - '0'); // No idea :P
	}

	char Colors::Add(uint8_t r, uint8_t g, uint8_t b)
	{
		char index = '0' + static_cast<char>(Colors::ColorTable.size());
		Colors::ColorTable.push_back(RGB(r, g, b));
		Colors::PatchColorLimit(index);
		return index;
	}

	unsigned int Colors::ColorIndex(unsigned char index)
	{
		unsigned int result = index - '0';
		if (result >= Colors::ColorTable.size() || result < 0) result = 7;
		return result;
	}

	void Colors::LookupColor(DWORD* color, char index)
	{
		*color = RGB(255, 255, 255);

		if (index == '8') // Color 8
		{
			*color = *reinterpret_cast<DWORD*>(0x66E5F70);
		}
		else if (index == '9') // Color 9
		{
			*color = *reinterpret_cast<DWORD*>(0x66E5F74);
		}
		else if (index == ':')
		{
			*color = Colors::HsvToRgb({ static_cast<uint8_t>((Game::Sys_Milliseconds() / 200) % 256), 255,255 });
		}
		else if (index == ';')
		{
			float fltColor[4];
			Game::Dvar_GetUnpackedColorByName("sv_customTextColor", fltColor);
			*color = RGB(fltColor[0] * 255, fltColor[1] * 255, fltColor[2] * 255);
		}
		else
		{
			int clrIndex = Colors::ColorIndex(index);

			// Use native colors
			if (clrIndex <= 7 && !Colors::NewColors.Get<bool>())
			{
				*color = reinterpret_cast<DWORD*>(0x78DC70)[index - 48];
			}
			else
			{
				*color = Colors::ColorTable[clrIndex];
			}
		}
	}

	char* Colors::CleanStrStub(char* string)
	{
		Colors::Strip(string, string, strlen(string) + 1);
		return string;
	}

	__declspec(naked) void Colors::LookupColorStub()
	{
		__asm
		{
			push ebx
			push [esp + 8h] // Index
			push esi        // Color ref
			call Colors::LookupColor
			add esp, 8h
			pop ebx
			retn
		}
	}

	Colors::Colors()
	{
		// Disable SV_UpdateUserinfo_f, to block changing the name ingame
		Utils::Hook::Set<BYTE>(0x6258D0, 0xC3);

		// Allow colored names ingame
		Utils::Hook(0x5D8B40, Colors::ClientUserinfoChanged, HOOK_JUMP).Install()->Quick();

		// Though, don't apply that to overhead names.
		Utils::Hook(0x581932, Colors::GetClientName, HOOK_CALL).Install()->Quick();

		// Patch RB_LookupColor
		Utils::Hook(0x534CD0, Colors::LookupColorStub, HOOK_JUMP).Install()->Quick();

		// Patch ColorIndex
		Utils::Hook(0x417770, Colors::ColorIndex, HOOK_JUMP).Install()->Quick();

		// Patch I_CleanStr
		Utils::Hook(0x4AD470, Colors::CleanStrStub, HOOK_JUMP).Install()->Quick();

		// Register dvar
		Colors::NewColors = Dvar::Register<bool>("cg_newColors", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use WarfareČ color code style.");
		Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::dvar_flag::DVAR_FLAG_REPLICATED, "Color for the extended color code.");

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

		Colors::Add(255, 255, 255); // 7  - White

		Colors::Add(0, 0, 0);       // 8  - Team color (axis?)
		Colors::Add(0, 0, 0);       // 9  - Team color (allies?)

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

	Colors::~Colors()
	{
		Colors::ColorTable.clear();
	}
}
