#define _CRT_SECURE_NO_WARNINGS
#include "Config.h"
#include "../Utilities/Utilities.h"
#include "../../Backend/Globalincludes.h"
#include "../Main/Main.h"
#include <filesystem>
#include <array>

namespace {
	std::string color_to_string(const int col[4]) {
		return std::to_string(col[0]) + "," + std::to_string(col[1]) + "," + std::to_string(col[2]) + "," + std::to_string(col[3]);
	}

	std::array<int, 4> string_to_color(const std::string& s) {
		static auto split = [](const std::string& str, const char* del) -> std::vector<std::string>
		{
			char* pTempStr = _strdup(str.c_str());
			char* pWord = strtok(pTempStr, del);
			std::vector<std::string> dest;

			while (pWord != NULL)
			{
				dest.push_back(pWord);
				pWord = strtok(NULL, del);
			}

			free(pTempStr);

			return dest;
		};

		std::vector<std::string> col = split(s, ",");
		return { std::stoi(col.at(0)), std::stoi(col.at(1)), std::stoi(col.at(2)), std::stoi(col.at(3)) };
	}
}

void CConfig::GetFileName(char* _buffer, bool db) {
	strcpy_s(_buffer, 255, this->s["config_name"]);
}

void CConfig::GetFilePath(char* _buffer, bool db) {
	char buffer[MAX_PATH];
	char filename[255];
	GetFileName(filename);
	GetCurrentDirectoryA(MAX_PATH, buffer);

	if (!db)
		sprintf_s(buffer, "%s\\configs\\%s.cfg", buffer, filename);
	else
		sprintf_s(buffer, "%s\\configs", buffer);

	strcpy_s(_buffer, 260, buffer);
}

void CConfig::LoadDefaults() {
	Refresh();

	CConfig* cfg = CConfig::get();

	{
		cfg->b["isAdmin"] = IsRunningAsAdmin();
	}

	{
		cfg->b["time"] = false;
		cfg->i["time_format"] = 0;
		cfg->b["text_border"] = false;
		cfg->i["time_text_size"] = 45;
		cfg->i["time_text_character_spacing"] = 0;
		cfg->c["time_text_Color"][0] = 255;
		cfg->c["time_text_Color"][1] = 255;
		cfg->c["time_text_Color"][2] = 255;
		cfg->c["time_text_Color"][3] = 255;
		cfg->f["time_x_pos"] = 0.0f;
		cfg->f["time_y_pos"] = 0.0f;
		cfg->c["text_border_Color"][0] = 0;
		cfg->c["text_border_Color"][1] = 0;
		cfg->c["text_border_Color"][2] = 0;
		cfg->c["text_border_Color"][3] = 255;
	}

	{
		cfg->b["keyboard_control"] = false;
		cfg->i["Caps_control"] = 0;
		cfg->i["Num_control"] = 0;
		strncpy_s(cfg->s["keyboard_whitelist"], 255, "", 254);
	}

	{
		cfg->c["MenuColor"][0] = 163;
		cfg->c["MenuColor"][1] = 212;
		cfg->c["MenuColor"][2] = 31;
		cfg->c["MenuColor"][3] = 255;
		strncpy_s(cfg->s["menu_key_bind"], 255, "163,97", 254); // Ctrl + 1 (top row, NumLock-independent)
		GamesenseMenuFramework::Globals::MultiKeyCache("menu_key_bind") = "163,97";
		cfg->i["menu_keystyle"] = 4;
		cfg->i["menu_key"] = VK_INSERT; // fallback single-key
		cfg->i["menu_scale"] = 0;
		cfg->b["lock_menu_layout"] = true;
		cfg->b["show_console"] = true;
	}
}

// --- menu.cfg tracking helpers (must be before Load/Save) ---

static void GetMenuCfgPath(char* buf, int bufSize) {
	GetCurrentDirectoryA(bufSize, buf);
	sprintf_s(buf, bufSize, "%s\\configs\\menu.configs", buf);
}

static void WriteMenuCfg(const char* configName, bool hasManual) {
	char path[MAX_PATH];
	GetMenuCfgPath(path, MAX_PATH);
	WritePrivateProfileStringA("Config", "LastConfig", configName, path);
	WritePrivateProfileStringA("Config", "HasManualSave", hasManual ? "1" : "0", path);
}

static void ReadMenuCfg(char* outName, int outSize, bool* outHasManual) {
	char path[MAX_PATH];
	GetMenuCfgPath(path, MAX_PATH);
	GetPrivateProfileStringA("Config", "LastConfig", "", outName, outSize, path);
	*outHasManual = GetPrivateProfileIntA("Config", "HasManualSave", 0, path) != 0;
}

// ---

void CConfig::AutoLoad() {
	char lastConfig[255] = "";
	bool hasManual;
	ReadMenuCfg(lastConfig, sizeof(lastConfig), &hasManual);
	this->hasManualSave = hasManual;

	// Try the last known config first
	if (lastConfig[0] != 0) {
		strcpy_s(this->s["config_name"], lastConfig);
		char filepath[MAX_PATH];
		GetFilePath(filepath);
		if (GetFileAttributesA(filepath) != INVALID_FILE_ATTRIBUTES) {
			Load();
			return;
		}
	}

	// Fallback: find an existing profile-*.cfg
	char configsPath[MAX_PATH];
	GetFilePath(configsPath, true);
	if (std::filesystem::exists(configsPath)) {
		std::string bestDefault;
		for (auto& entry : std::filesystem::directory_iterator(configsPath)) {
			if (entry.path().extension() == ".cfg") {
				std::string name = entry.path().stem().string();
				if (name.find("profile-") == 0 && name > bestDefault)
					bestDefault = name;
			}
		}
		if (!bestDefault.empty()) {
			strcpy_s(this->s["config_name"], bestDefault.c_str());
			Load();
			return;
		}
	}

	// Nothing found — use in-memory defaults (already set by LoadDefaults)
	this->s["config_name"][0] = 0;
}

void CConfig::AutoSave() {
	if (this->hasManualSave) {
		if (this->s["config_name"][0] != 0)
			Save();
	} else {
		if (this->s["config_name"][0] == 0)
			strcpy_s(this->s["config_name"], "profile-auto");
		Save();
	}
	WriteMenuCfg(this->s["config_name"], this->hasManualSave);
}

void CConfig::Load() {
	if (this->s["config_name"][0] == 0)
		return;
	this->Current = this->s["config_name"];
	Refresh();

	char file_path[MAX_PATH] = { 0 };
	GetFilePath(file_path);

	// Read b (bool) entries from INI
	for (auto& e : b) {
		if (e.first.find("_") == 0) continue;
		e.second = GetPrivateProfileIntA("b", e.first.c_str(), e.second, file_path) != 0;
	}

	// Read i (int) entries from INI
	for (auto& e : i) {
		if (e.first.find("_") == 0) continue;
		e.second = GetPrivateProfileIntA("i", e.first.c_str(), e.second, file_path);
	}

	// Read f (float) entries from INI
	for (auto& e : f) {
		if (e.first.find("_") == 0) continue;
		char buffer[64] = { 0 };
		GetPrivateProfileStringA("f", e.first.c_str(), "", buffer, 63, file_path);
		if (buffer[0] != 0)
			e.second = (float)atof(buffer);
	}

	// Read c (color) entries from INI
	for (auto& e : c) {
		if (e.first.find("_") == 0) continue;
		char buffer[64] = { 0 };
		GetPrivateProfileStringA("c", e.first.c_str(), "", buffer, 63, file_path);
		if (buffer[0] != 0) {
			auto col = string_to_color(buffer);
			e.second[0] = col[0];
			e.second[1] = col[1];
			e.second[2] = col[2];
			e.second[3] = col[3];
		}
	}

	// Read m (map) entries from INI
	for (auto& e : m) {
		if (e.first.find("_") == 0) continue;
		char buffer[4096] = { 0 };
		GetPrivateProfileStringA("m", e.first.c_str(), "", buffer, 4095, file_path);
		if (buffer[0] != 0) {
			e.second.clear();
			char* ctx = nullptr;
			char* token = strtok_s(buffer, "|", &ctx);
			while (token) {
				int key = 0, val = 0;
				if (sscanf_s(token, "%d:%d", &key, &val) == 2)
					e.second[key] = val != 0;
				token = strtok_s(nullptr, "|", &ctx);
			}
		}
	}

	// Read s (string) entries from INI
	for (auto& e : s) {
		if (e.first == "config_name") continue;
		GetPrivateProfileStringA("s", e.first.c_str(), e.second, e.second, 255, file_path);
	}

	// Sync MultiKeyCache with loaded s values so IsBindActive uses the correct bindings
	for (auto& e : s) {
		if (e.first.size() > 5 && e.first.compare(e.first.size() - 5, 5, "_bind") == 0) {
			GamesenseMenuFramework::Globals::MultiKeyCache(e.first) = e.second;
		}
	}

	WriteMenuCfg(this->s["config_name"], this->hasManualSave);
}

void CConfig::Save() {
	if (this->s["config_name"][0] == 0)
		return;

	char configs_path[260];
	GetFilePath(configs_path, true);
	std::filesystem::create_directories(configs_path);

	char file_path[MAX_PATH] = { 0 };
	GetFilePath(file_path);
	Misc::Utilities->Console_Log("Saving config: %s", file_path);

	for (auto e : b) {
		if (!std::string(e.first).find("_")) continue;
		char buffer[8] = { 0 }; _itoa(e.second, buffer, 10);
		WritePrivateProfileStringA("b", e.first.c_str(), std::string(buffer).c_str(), file_path);
	}

	for (auto e : i) {
		if (!std::string(e.first).find("_")) continue;
		char buffer[32] = { 0 }; _itoa(e.second, buffer, 10);
		WritePrivateProfileStringA("i", e.first.c_str(), std::string(buffer).c_str(), file_path);
	}

	for (auto e : f) {
		if (!std::string(e.first).find("_")) continue;
		char buffer[64] = { 0 }; sprintf(buffer, "%f", e.second);
		WritePrivateProfileStringA("f", e.first.c_str(), std::string(buffer).c_str(), file_path);
	}

	for (auto e : c) {
		if (!std::string(e.first).find("_")) continue;
		WritePrivateProfileStringA("c", e.first.c_str(), color_to_string(e.second).c_str(), file_path);
	}

	for (auto e : m) {
		if (!std::string(e.first).find("_")) continue;

		std::string vs = "";
		for (auto v : e.second)
			vs += std::to_string(v.first) + ":" + std::to_string(v.second) + "|";

		WritePrivateProfileStringA("m", e.first.c_str(), vs.c_str(), file_path);
	}

	for (auto e : s) {
		if (e.first == "config_name") continue;
		if (e.second[0] == 0) continue;
		WritePrivateProfileStringA("s", e.first.c_str(), e.second, file_path);
	}

	this->hasManualSave = true;
	WriteMenuCfg(this->s["config_name"], true);

	Refresh();
}

void CConfig::Delete() {
	if (this->s["config_name"][0] == 0)
		return;
	char path[260];
	GetFilePath(path);
	std::remove(path);
	Refresh();
}

void CConfig::Refresh() {
	this->List.clear();
	char path[260];
	GetFilePath(path, true);

	if (!std::filesystem::exists(path))
		return;

	for (auto& entry : std::filesystem::directory_iterator(path)) {
		if (entry.path().extension() == (".cfg")) {
			auto path = entry.path();
			auto path_text = entry.path().string();

			auto filename = path.filename().string();

			if (filename != "tempbuffer")
				this->List.push_back(filename.substr(0, filename.length() - 4));
		}
	}
}

bool CConfig::IsBindActive(std::string key) {
	auto g = GamesenseMenuFramework::Globals::Gui_Ctx;
	if (!g) return false;

	switch (this->i[key + "style"]) {
	case 0:
		// Always on
		return true;
	case 1:
		// On hotkey (held)
		return g->KeyState[this->i[key]];
	case 2:
		// Toggle (rising edge)
		return g->KeyState[this->i[key]] && !g->PrevKeyState[this->i[key]];
	case 3:
		// Off hotkey (not held)
		return !g->KeyState[this->i[key]];
	case 4:
	{
		// Multi-key: all stored keys must be held simultaneously
		std::string multiStr = GamesenseMenuFramework::Globals::MultiKeyCache(key + "_bind");
		if (multiStr.empty()) {
			multiStr = this->s[key + "_bind"];
			if (!multiStr.empty() && multiStr[0] != 0)
				GamesenseMenuFramework::Globals::MultiKeyCache(key + "_bind") = multiStr;
		}
		if (multiStr.empty() || multiStr[0] == 0) return false;
		const char* p = multiStr.c_str();
		while (*p) {
			int vk = atoi(p);
			if (vk > 0 && vk < 256) {
				bool held = g->KeyState[vk];
				if (!held) {
					// Accept either left/right modifier
					int pair = 0;
					if      (vk == 162) pair = 163;
					else if (vk == 163) pair = 162;
					else if (vk == 160) pair = 161;
					else if (vk == 161) pair = 160;
					else if (vk == 164) pair = 165;
					else if (vk == 165) pair = 164;
					if (pair) held = g->KeyState[pair];
				}
				if (!held) return false;
			}
			const char* comma = strchr(p, ',');
			if (!comma) break;
			p = comma + 1;
		}
		return true;
	}
	default:
		return true;
	}
}
