/********************************************************************
	Minecraft: Pocket Edition - Decompilation Project
	Copyright (C) 2023 iProgramInCpp
	
	The following code is licensed under the BSD 1 clause license.
	SPDX-License-Identifier: BSD-1-Clause
 ********************************************************************/

#include <fstream>

#include "Options.hpp"
#include "common/Util.hpp"
#include "compat/KeyCodes.hpp"
#include "client/app/Minecraft.hpp"

#include "client/renderer/PatchManager.hpp"
#include "client/renderer/GrassColor.hpp"
#include "client/renderer/FoliageColor.hpp"

Options::Option
	Options::Option::MUSIC            (0,  "options.music",          true,  false),
	Options::Option::SOUND            (1,  "options.sound",          true,  false),
	Options::Option::INVERT_MOUSE     (2,  "options.invertMouse",    false, true),
	Options::Option::SENSITIVITY      (3,  "options.sensitivity",    true,  false),
	Options::Option::RENDER_DISTANCE  (4,  "options.renderDistance", false, false),
	Options::Option::VIEW_BOBBING     (5,  "options.viewBobbing",    false, true),
	Options::Option::ANAGLYPH         (6,  "options.anaglyph",       false, true),
	Options::Option::LIMIT_FRAMERATE  (7,  "options.limitFramerate", false, true),
	Options::Option::DIFFICULTY       (8,  "options.difficulty",     false, false),
	Options::Option::GRAPHICS         (9,  "options.graphics",       false, false),
	Options::Option::AMBIENT_OCCLUSION(10, "options.ao",             false, true),
	Options::Option::GUI_SCALE        (11, "options.guiScale",       false, false);

void Options::_initDefaultValues()
{
	field_238 = 2;
	field_244 = 1.0f;
	m_bDontRenderGui = false;
	field_248 = 1.0f;
	m_bThirdPerson = false;
	field_0   = 1.0f;
	field_23E = 0;
	m_fMasterVolume = 1.0f;
	m_bFlyCheat = false;
	field_241 = 0;
	field_8   = 0.5f;
	field_24C = 0;
	m_bInvertMouse = false;
	m_bAnaglyphs = false;
	field_16  = 0;
	m_bAmbientOcclusion = Minecraft::useAmbientOcclusion;
	field_240 = 1;
	m_iViewDistance = 2;
	m_bViewBobbing  = 1;
	m_bAutoJump = true;
	m_bFancyGraphics = true;
	field_1C = "Default";
	m_playerName = "Steve";
	m_bServerVisibleDefault = true;
	m_bDebugText = false;
	m_bBlockOutlines = false;
	m_bFancyGrass = false;
	m_bBiomeColors = false;
	m_bSplitControls = false;
	field_19 = 1;

	// Win32 key codes are being used by default
#define KM(idx, name, code) m_keyMappings[idx] = KeyMapping(name, code)
	KM(KM_FORWARD,      "key.forward",       'W');
	KM(KM_LEFT,         "key.left",          'A');
	KM(KM_BACKWARD,     "key.back",          'S');
	KM(KM_RIGHT,        "key.right",         'D');
	KM(KM_JUMP,         "key.jump",          ' ');
	KM(KM_INVENTORY,    "key.inventory",     'E');
	KM(KM_DROP,         "key.drop",          'Q');
	KM(KM_CHAT,         "key.chat",          'T');
	KM(KM_FOG,          "key.fog",           'F');
	KM(KM_SNEAK,        "key.sneak",         0x10); // VK_SHIFT. In original, it's 10 (misspelling?)
	KM(KM_DESTROY,      "key.destroy",       'K'); // was 'X'
	KM(KM_PLACE,        "key.place",         'L'); // was 'C'
	KM(KM_MENU_NEXT,    "key.menu.next",     0x28); // VK_DOWN
	KM(KM_MENU_PREVIOUS,"key.menu.previous", 0x26); // VK_UP
	KM(KM_MENU_OK,      "key.menu.ok",       0x0D); // VK_RETURN
	KM(KM_MENU_CANCEL,  "key.menu.cancel",   0x1B); // VK_ESCAPE, was 0x08 = VK_BACK
	KM(KM_SLOT_1,       "key.slot.1",        '1');
	KM(KM_SLOT_2,       "key.slot.2",        '2');
	KM(KM_SLOT_3,       "key.slot.3",        '3');
	KM(KM_SLOT_4,       "key.slot.4",        '4');
	KM(KM_SLOT_5,       "key.slot.5",        '5');
	KM(KM_SLOT_6,       "key.slot.6",        '6');
	KM(KM_SLOT_7,       "key.slot.7",        '7');
	KM(KM_SLOT_8,       "key.slot.8",        '8');
	KM(KM_SLOT_9,       "key.slot.9",        '9');
	KM(KM_SLOT_L,       "key.slot.left",     'Y');
	KM(KM_SLOT_R,       "key.slot.right",    'U');
	KM(KM_TOGGLEGUI,    "key.fn.gui",        0x70); // VK_F1
	KM(KM_SCREENSHOT,   "key.fn.screenshot", 0x71); // VK_F2
	KM(KM_TOGGLEDEBUG,  "key.fn.debug",      0x72); // VK_F3
	KM(KM_TOGGLEAO,     "key.fn.ao",         0x73); // VK_F4
	KM(KM_TOGGLE3RD,    "key.fn.3rd",        0x74); // VK_F5
	KM(KM_FLY_UP,       "key.fly.up",        'X');
	KM(KM_FLY_DOWN,     "key.fly.down",      'C');
	KM(KM_CHAT_CMD,     "key.chat.cmd",      0xBF); // VK_OEM_2
#undef KM

#ifdef ORIGINAL_CODE
	m_iViewDistance = 2;
	m_bThirdPerson = 0;
	field_19 = 0;
#endif

	m_bFancyGraphics = 1;

#define KM(idx,code) m_keyMappings[idx].value = code
#ifdef USE_SDL
	KM(KM_FORWARD,       SDLVK_w);
	KM(KM_LEFT,          SDLVK_a);
	KM(KM_BACKWARD,      SDLVK_s);
	KM(KM_RIGHT,         SDLVK_d);
	KM(KM_JUMP,          SDLVK_SPACE);
	KM(KM_DESTROY,       SDLVK_x);
	KM(KM_PLACE,         SDLVK_c);
	KM(KM_MENU_NEXT,     SDLVK_DOWN);
	KM(KM_MENU_PREVIOUS, SDLVK_UP);
	KM(KM_MENU_OK,       SDLVK_RETURN);
	KM(KM_MENU_CANCEL,   SDLVK_ESCAPE);
	KM(KM_DROP,          SDLVK_q);
	KM(KM_CHAT,          SDLVK_t);
	KM(KM_FOG,           SDLVK_f);
	KM(KM_INVENTORY,     SDLVK_e);
	KM(KM_SNEAK,         SDLVK_LSHIFT);
	KM(KM_SLOT_1,        SDLVK_1);
	KM(KM_SLOT_2,        SDLVK_2);
	KM(KM_SLOT_3,        SDLVK_3);
	KM(KM_SLOT_4,        SDLVK_4);
	KM(KM_SLOT_5,        SDLVK_5);
	KM(KM_SLOT_6,        SDLVK_6);
	KM(KM_SLOT_7,        SDLVK_7);
	KM(KM_SLOT_8,        SDLVK_8);
	KM(KM_SLOT_9,        SDLVK_9);
	KM(KM_TOGGLEGUI,     SDLVK_F1);
	KM(KM_SCREENSHOT,    SDLVK_F2);
	KM(KM_TOGGLEDEBUG,   SDLVK_F3);
	KM(KM_TOGGLEAO,      SDLVK_F4);
	KM(KM_TOGGLE3RD,     SDLVK_F5);
	KM(KM_SLOT_L,        SDLVK_y);
	KM(KM_SLOT_R,        SDLVK_u);
	KM(KM_FLY_UP,        SDLVK_c);
	KM(KM_FLY_DOWN,      SDLVK_x);
	KM(KM_CHAT_CMD,      SDLVK_SLASH);
#endif
#ifdef __ANDROID__
	// -- Original xperia play controls
	//KM(KM_FORWARD,       AKEYCODE_DPAD_UP);
	//KM(KM_LEFT,          AKEYCODE_DPAD_LEFT);
	//KM(KM_BACKWARD,      AKEYCODE_DPAD_DOWN);
	//KM(KM_RIGHT,         AKEYCODE_DPAD_RIGHT);
	//KM(KM_JUMP,          AKEYCODE_DPAD_CENTER);
	//KM(KM_DESTROY,       AKEYCODE_BUTTON_L1);
	//KM(KM_PLACE,         AKEYCODE_BUTTON_R1);
	//KM(KM_MENU_NEXT,     AKEYCODE_DPAD_DOWN);
	//KM(KM_MENU_PREVIOUS, AKEYCODE_DPAD_UP);
	//KM(KM_MENU_OK,       AKEYCODE_DPAD_CENTER);
	//KM(KM_MENU_CANCEL,   AKEYCODE_BACK);
	//custom
	//KM(KM_INVENTORY,     AKEYCODE_BUTTON_Y);
	//KM(KM_SLOT_R,        AKEYCODE_BACK);
	//KM(KM_SLOT_L,        AKEYCODE_BUTTON_X);
	//KM(KM_FLY_UP,        AKEYCODE_BUTTON_R1);
	//KM(KM_FLY_DOWN,      AKEYCODE_BUTTON_L1);

	//use controller input on android for now.
	KM(KM_FORWARD,       AKEYCODE_W);
	KM(KM_LEFT,          AKEYCODE_A);
	KM(KM_BACKWARD,      AKEYCODE_S);
	KM(KM_RIGHT,         AKEYCODE_D);
	KM(KM_JUMP,			 AKEYCODE_BUTTON_A);
	KM(KM_DESTROY,       AKEYCODE_X);
	KM(KM_PLACE,         AKEYCODE_C);
	KM(KM_MENU_NEXT,     AKEYCODE_DPAD_DOWN);
	KM(KM_MENU_PREVIOUS, AKEYCODE_DPAD_UP);
	KM(KM_MENU_OK,       AKEYCODE_ENTER);
	KM(KM_MENU_CANCEL,	 AKEYCODE_BUTTON_START);
	// custom
	KM(KM_SLOT_L,		 AKEYCODE_BUTTON_L1);
	KM(KM_SLOT_R,		 AKEYCODE_BUTTON_R1);
	KM(KM_DROP,          AKEYCODE_Q);
	KM(KM_CHAT,          AKEYCODE_T);
	KM(KM_FOG,           AKEYCODE_F);
	KM(KM_INVENTORY,	 AKEYCODE_BUTTON_Y);
	KM(KM_SNEAK,         AKEYCODE_SHIFT_LEFT);
	KM(KM_SLOT_1,        AKEYCODE_1);
	KM(KM_SLOT_2,        AKEYCODE_2);
	KM(KM_SLOT_3,        AKEYCODE_3);
	KM(KM_SLOT_4,        AKEYCODE_4);
	KM(KM_SLOT_5,        AKEYCODE_5);
	KM(KM_SLOT_6,        AKEYCODE_6);
	KM(KM_SLOT_7,        AKEYCODE_7);
	KM(KM_SLOT_8,        AKEYCODE_8);
	KM(KM_SLOT_9,        AKEYCODE_9);
	KM(KM_TOGGLEGUI,     AKEYCODE_F1);
	KM(KM_SCREENSHOT,    AKEYCODE_F2);
	KM(KM_TOGGLEDEBUG,   AKEYCODE_F3);
	KM(KM_TOGGLEAO,      AKEYCODE_F4);
	KM(KM_TOGGLE3RD,     AKEYCODE_F5);
	KM(KM_FLY_UP,        AKEYCODE_C);
	KM(KM_FLY_DOWN,      AKEYCODE_X);
	KM(KM_CHAT_CMD,      AKEYCODE_SLASH);
#endif
#undef KM
}

Options::Options()
{
	_initDefaultValues();
}

Options::Options(const std::string& folderPath)
{
	m_filePath = folderPath + "/options.txt";
	_initDefaultValues();
	_load();
}

std::string getMessage(const Options::Option& option)
{
	return "Options::getMessage - Not implemented";
}

void Options::_load()
{
	std::vector<std::string> strings = readPropertiesFromFile(m_filePath);

	for (int i = 0; i < strings.size(); i += 2)
	{
		std::string key = strings[i], value = strings[i + 1];

		if (key == "mp_username")
			m_playerName = value;
		else if (key == "ctrl_invertmouse")
			m_bInvertMouse = readBool(value);
		else if (key == "ctrl_autojump")
			m_bAutoJump = readBool(value);
		else if (key == "ctrl_split")
			m_bFancyGraphics = readBool(value);
		else if (key == "gfx_fancygraphics")
			m_bFancyGraphics = readBool(value);
		else if (key == "mp_server_visible_default")
			m_bServerVisibleDefault = readBool(value);
		else if (key == "gfx_smoothlighting")
			Minecraft::useAmbientOcclusion = m_bAmbientOcclusion = readBool(value);
		else if (key == "gfx_viewdistance")
			m_iViewDistance = readInt(value);
		else if (key == "gfx_blockoutlines")
			m_bBlockOutlines = readBool(value);
		else if (key == "gfx_fancygrass")
		{
			m_bFancyGrass = readBool(value);
			if (!(GetPatchManager()->IsGrassSidesTinted()))
			{
				m_bFancyGrass = false;
			}
		}
		else if (key == "gfx_biomecolors")
		{
			m_bBiomeColors = readBool(value);
			if (!GrassColor::isAvailable() && !FoliageColor::isAvailable())
			{
				m_bBiomeColors = false;
			}
		}
	}
}

void Options::save()
{
	savePropertiesToFile(m_filePath, getOptionStrings());
}

std::string Options::getMessage(const Options::Option& option)
{
	return "Options::getMessage - Not implemented";
}

bool Options::readBool(const std::string& str)
{
	std::string trimmed = Util::stringTrim(str);
	if (trimmed == "true" || trimmed == "1")
		return true;
	if (trimmed == "false" || trimmed == "0")
		return false;

	return false;
}

int Options::readInt(const std::string& str)
{
	int f;
	
	if (!sscanf(str.c_str(), "%d", &f))
		f = 0;

	return f;
}

std::string Options::saveBool(bool b)
{
	return b ? "true" : "false";
}

std::string Options::saveInt(int i)
{
	std::stringstream ss;
	ss << i;
	return ss.str();
}

std::vector<std::string> Options::readPropertiesFromFile(const std::string& filePath)
{
	std::vector<std::string> o;

	const char* const path = filePath.c_str();
	LOG_I("Loading options from %s", path);

	std::ifstream ifs(path);
	if (!ifs.is_open())
	{
		LOG_W("%s doesn't exist, resetting to defaults", path);
		return o;
	}

	std::string str;
	while (true)
	{
		if (!std::getline(ifs, str, '\n'))
			break;

		if (str.empty() || str[0] == '#')
			continue;

		std::stringstream ss;
		ss << str;

		std::string key, value;
		if (std::getline(ss, key, ':') && std::getline(ss, value))
		{
			o.push_back(key);
			o.push_back(value);
		}
	}

	return o;
}

void Options::savePropertiesToFile(const std::string& filePath, std::vector<std::string> properties)
{
	assert(properties.size() % 2 == 0);

	std::ofstream os;
	os.open(filePath.c_str());
	if (!os.is_open())
	{
		LOG_E("Failed to read %s", filePath.c_str());
		return;
	}

	os << "#Config file for Minecraft PE.  The # at the start denotes a comment, removing it makes it a command.\n\n";

	for (int i = 0; i < properties.size(); i += 2)
		os << properties[i] << ':' << properties[i + 1] << '\n';
}

std::vector<std::string> Options::getOptionStrings()
{
	std::vector<std::string> vec;

#define SO(optname, value) do { vec.push_back(optname); vec.push_back(value); } while (0)

	SO("mp_username", m_playerName);
	SO("ctrl_invertmouse",          saveBool(m_bInvertMouse));
	SO("ctrl_autojump",             saveBool(m_bAutoJump));
	SO("ctrl_split",                saveBool(m_bSplitControls));
	SO("gfx_fancygraphics",         saveBool(m_bFancyGraphics));
	SO("mp_server_visible_default", saveBool(m_bServerVisibleDefault));
	SO("gfx_smoothlighting",        saveBool(m_bAmbientOcclusion));
	SO("gfx_viewdistance",          saveInt (m_iViewDistance));
	SO("gfx_blockoutlines",         saveBool(m_bBlockOutlines));
	SO("gfx_fancygrass", 			saveBool(m_bFancyGrass));
	SO("gfx_biomecolors",           saveBool(m_bBiomeColors));

	return vec;
}
