namespace IBFUtility {

	unsigned char CharToLower(unsigned char ch) {
		return (unsigned char)(std::tolower(ch));
	}

	std::string StringToLower(std::string toConvert) {
		std::string lower = toConvert;
		std::transform(toConvert.begin(), toConvert.end(), lower.begin(), CharToLower);
		return lower;
	}

}

namespace IBFSettings {

	// general
	std::int32_t iSleepTime = 50;
	// boostpack
	bool bEnabledB = true;
	float fLowValueB = 25.0F;
	// spaceship
	bool bEnabledS = true;
	float fLowValueS = 1.0F;

	bool ConfigBool(mINI::INIStructure ini, std::string section, std::string key, bool fallback) {
		bool result = fallback;
		std::string raw = ini.get(section).get(key);
		std::string lower = IBFUtility::StringToLower(raw);
		if (lower.compare("true") == 0 || lower.compare("1") == 0) result = true;
		else if (lower.compare("false") == 0 || lower.compare("0") == 0) result = false;
		else logs::info("Failed to read [{}]{} ini value", section, key);
		logs::info("Bool value [{}]{} is {}", section, key, result);
		return result;
	}

	std::int32_t ConfigInt(mINI::INIStructure ini, std::string section, std::string key, std::int32_t fallback) {
		std::int32_t result = fallback;
		std::string raw = ini.get(section).get(key);
		try { result = std::stol(raw); } catch (...) { logs::info("Failed to read [{}]{} ini value", section, key); }
		logs::info("Int value [{}]{} is {}", section, key, result);
		return result;
	}

	float ConfigFloat(mINI::INIStructure ini, std::string section, std::string key, float fallback) {
		float result = fallback;
		std::string raw = ini.get(section).get(key);
		try { result = std::stof(raw); } catch (...) { logs::info("Failed to read [{}]{} ini value", section, key); }
		logs::info("Float value [{}]{} is {}", section, key, result);
		return result;
	}

	void LoadSettings() {
		mINI::INIFile file("Data\\SFSE\\Plugins\\InfiniteBoostFuel.ini");
		mINI::INIStructure ini;
		if (file.read(ini)) {
			// general
			iSleepTime = ConfigInt(ini, "General", "iSleepTime", 50);
			// boostpack
			bEnabledB = ConfigBool(ini, "Boostpack", "bEnabled", true);
			fLowValueB = ConfigFloat(ini, "Boostpack", "fLowValue", 25.0F);
			// spaceship
			bEnabledS = ConfigBool(ini, "Spaceship", "bEnabled", true);
			fLowValueS = ConfigFloat(ini, "Spaceship", "fLowValue", 1.0F);
		} else {
			logs::info("Config read error, all settings set to default");
		}
	}

}

namespace IBFGetSet {

	float GetBoostpackStateFull(RE::PlayerCharacter* player, RE::ActorValueInfo* fuel) {
		if (IBFSettings::bEnabledB && player && fuel) {
			float value = player->GetActorValue(*fuel);
			float base = player->GetBaseActorValue(*fuel);
			if (value > 0.0F && value < base && value < IBFSettings::fLowValueB) return base;
			return 0.0F;
		}
		return 0.0F;
	}

	void SetBoostpackStateFull(RE::PlayerCharacter* player, RE::ActorValueInfo* fuel, float base) {
		if (base > 0.0F) player->RestoreActorValue(*fuel, base);
	}

	float GetSpaceshipStateFull(RE::TESObjectREFR* spaceship, RE::ActorValueInfo* fuel) {
		if (IBFSettings::bEnabledS && spaceship && fuel) {
			float value = spaceship->GetActorValue(*fuel);
			float base = spaceship->GetBaseActorValue(*fuel);
			if (value > 0.0F && value < base && value < IBFSettings::fLowValueS) return base;
			return 0.0F;
		}
		return 0.0F;
	}

	void SetSpaceshipStateFull(RE::TESObjectREFR* spaceship, RE::ActorValueInfo* fuel, float base) {
		if (base > 0.0F) spaceship->RestoreActorValue(*fuel, base);
	}

}

namespace IBFProcess {

	static DWORD BoostpackThread(void* unused) {
		(void)unused;
		std::uint32_t BFuelID = 0x024021;
		std::uint32_t SFuelID = 0x001885;
		while (RE::TESForm::LookupByID(BFuelID) == nullptr || RE::TESForm::LookupByID(SFuelID) == nullptr) Sleep(IBFSettings::iSleepTime);
		logs::info("Records {} and {} found, data loaded", BFuelID, SFuelID);
		
		// fill pointers
		RE::PlayerCharacter* Player = RE::PlayerCharacter::GetSingleton();
		RE::ActorValueInfo* BFuel = static_cast<RE::ActorValueInfo*>(RE::TESForm::LookupByID(BFuelID));
		RE::ActorValueInfo* SFuel = static_cast<RE::ActorValueInfo*>(RE::TESForm::LookupByID(SFuelID));

		if (Player == nullptr || BFuel == nullptr || SFuel == nullptr) {
			logs::info("Critical pointer(s) not found, Player = {}, BFuel = {}, SFuel = {}, perform a return", Player != nullptr, BFuel != nullptr, SFuel != nullptr);
			return 1;
		} else {
			logs::info("Critical pointers found, Player = {}, BFuel = {}, SFuel = {}", (std::uint64_t)Player, (std::uint64_t)BFuel, (std::uint64_t)SFuel);
		}

		// do the job
		while (true) {
			// process spaceship
			auto Spaceship = Player->GetSpaceship(true);
			float SBase = IBFGetSet::GetSpaceshipStateFull(Spaceship, SFuel);
			IBFGetSet::SetSpaceshipStateFull(Spaceship, SFuel, SBase);
			
			// process boostpack
			float BBase = IBFGetSet::GetBoostpackStateFull(Player, BFuel);
			IBFGetSet::SetBoostpackStateFull(Player, BFuel, BBase);
			
			// cooldown
			Sleep(IBFSettings::iSleepTime);
		}
		return 0;
	}

	void MessageCallback(SFSE::MessagingInterface::Message* a_msg) noexcept {
		switch (a_msg->type) {
			case SFSE::MessagingInterface::kPostLoad:
			{
				CreateThread(NULL, 4096, BoostpackThread, NULL, 0, NULL);
				break;
			}
			default: break;
		}
	}

}

SFSEPluginLoad(const SFSE::LoadInterface* a_sfse)
{
	SFSE::Init(a_sfse);
	const auto pluginInfo = SFSE::PluginVersionData::GetSingleton();
	std::string gameVersion = a_sfse->RuntimeVersion().string(".");
	logs::info("{} version {} is loading into Starfield {}", std::string(pluginInfo->pluginName), REL::Version::unpack(pluginInfo->pluginVersion).string("."), gameVersion);
	SFSE::AllocTrampoline(256);

	// read settings
	IBFSettings::LoadSettings();

	// register stuff
	const auto SFSEMessagingInterface = SFSE::GetMessagingInterface();
	if (SFSEMessagingInterface && SFSEMessagingInterface->RegisterListener(IBFProcess::MessageCallback)) {
		logs::info("Message listener registered");
		return true;
	} else {
		logs::info("Message listener not registered");
	}
	return false;
}
