//
// settings.cpp: Apple2 configuration loading/saving support
//
// by James Hammons
// (C) 2019 Underground Software
//

#include "settings.h"

#include <stdlib.h>
#include <map>
#include <string>
#include <SDL2/SDL.h>
#include "fileio.h"
#include "log.h"
#include "video.h"

// Global variables

Settings settings;

// Private variables

static const char configPath[5][32] = {
	"./apple2.cfg",			// CWD
	"~/apple2.cfg",			// Home directory
	"~/.apple2/apple2.cfg",	// Home under .apple2 directory
	"/etc/apple2.cfg",		// /etc
	"apple2.cfg"			// Somewhere in the path
};

static int8_t configLoc = -1;
static std::map<std::string, std::string> keystore;

// Private function prototypes

static int8_t FindConfig(void);
static void ParseConfigFile(void);
static bool GetValue(const char *, bool);
static int GetValue(const char *, int);
static const char * GetValue(const char *, const char *);
static void SetValue(const char * key, bool value);
static void SetValue(const char * key, int value);
static void SetValue(const char * key, unsigned int value);
static void SetValue(const char * key, const char * value);
static void CheckForTrailingSlash(char * path);
static void UpdateConfigFile(void);


//
// Load Apple2's settings
//
void LoadSettings(void)
{
	ParseConfigFile();

	settings.useJoystick = GetValue("useJoystick", false);
	settings.joyport = GetValue("joyport", 0);
	settings.hardwareTypeNTSC = GetValue("hardwareTypeNTSC", true);
	settings.fullscreen = GetValue("fullscreen", false);
	settings.useOpenGL = GetValue("useOpenGL", true);
	settings.glFilter = GetValue("glFilterType", 0);
	settings.renderType = GetValue("renderType", 0);
	settings.autoStateSaving = GetValue("autoSaveState", true);

	settings.winX = GetValue("windowX", 250);
	settings.winY = GetValue("windowY", 100);

//	strcpy(settings.BIOSPath, sdlemu_getval_string("BIOSROM", "./ROMs/apple2e-enhanced.rom"));
	strcpy(settings.disksPath, GetValue("disks", "./disks/"));
	strcpy(settings.hd[0], GetValue("harddrive1", ""));
	strcpy(settings.hd[1], GetValue("harddrive2", ""));
	strcpy(settings.hd[2], GetValue("harddrive3", ""));
	strcpy(settings.hd[3], GetValue("harddrive4", ""));
	strcpy(settings.hd[4], GetValue("harddrive5", ""));
	strcpy(settings.hd[5], GetValue("harddrive6", ""));
	strcpy(settings.hd[6], GetValue("harddrive7", ""));
	strcpy(settings.autoStatePath, GetValue("autoStateFilename", "./apple2auto.state"));

	settings.cardSlot[0] = GetValue("card1", 6);	// Disk ][
	settings.cardSlot[1] = GetValue("card2", 0);	// Disk ][
	settings.cardSlot[2] = GetValue("card3", 4);	// Mockingboard
	settings.cardSlot[3] = GetValue("card4", 0);	// Mockingboard
	settings.cardSlot[4] = GetValue("card5", 0);	// AHSSCSI

	CheckForTrailingSlash(settings.disksPath);
}


//
// Save Apple2's settings
//
void SaveSettings(void)
{
	SDL_GetWindowPosition(sdlWindow, &settings.winX, &settings.winY);

	SetValue("autoSaveState", settings.autoStateSaving);
	SetValue("autoStateFilename", settings.autoStatePath);
	SetValue("useJoystick", settings.useJoystick);
	SetValue("joyport", settings.joyport);
	SetValue("hardwareTypeNTSC", settings.hardwareTypeNTSC);
	SetValue("fullscreen", settings.fullscreen);
	SetValue("useOpenGL", settings.useOpenGL);
	SetValue("glFilterType", settings.glFilter);
	SetValue("renderType", settings.renderType);
	SetValue("windowX", settings.winX);
	SetValue("windowY", settings.winY);
	SetValue("disks", settings.disksPath);
	SetValue("harddrive1", settings.hd[0]);
	SetValue("harddrive2", settings.hd[1]);
	SetValue("harddrive3", settings.hd[2]);
	SetValue("harddrive4", settings.hd[3]);
	SetValue("harddrive5", settings.hd[4]);
	SetValue("harddrive6", settings.hd[5]);
	SetValue("harddrive7", settings.hd[6]);
	SetValue("card1", settings.cardSlot[0]);
	SetValue("card2", settings.cardSlot[1]);
	SetValue("card3", settings.cardSlot[2]);
	SetValue("card4", settings.cardSlot[3]);
	SetValue("card5", settings.cardSlot[4]);

	UpdateConfigFile();
}


static int8_t FindConfig()
{
	for(uint8_t i=0; i<5; i++)
	{
		FILE * f = fopen(configPath[i], "r");

		if (f != NULL)
		{
			fclose(f);
			return i;
		}
	}

	return -1;
}


//
// Read & parse the configuration file into our keystore
//
static void ParseConfigFile(void)
{
	configLoc = FindConfig();

	if (configLoc == -1)
	{
		WriteLog("Settings: Couldn't find configuration file. Using defaults...\n");
		return;
	}

	char * buf = (char *)ReadFile(configPath[configLoc]);
	std::string s(buf);

	const std::string delim = "\n\r";
	std::string::size_type start = s.find_first_not_of(delim, 0);
	std::string::size_type end   = s.find_first_of(delim, start);

	while ((start != std::string::npos) || (end != std::string::npos))
	{
		std::string sub = s.substr(start, end - start);

		if ((sub[0] != '#') && (sub[0] != '['))
		{
			std::string::size_type kStart = sub.find_first_not_of(" ", 0);
			std::string::size_type kEnd   = sub.find_first_of(" ", kStart);
			std::string::size_type vStart = sub.find_first_of(" =\t\n\r", 0);
			std::string::size_type vEnd   = sub.find_first_not_of(" =\t\n\r", vStart);

			if ((kStart != std::string::npos) && (kEnd != std::string::npos)
				&& (vStart != std::string::npos) && (vEnd != std::string::npos))
			{
				keystore[sub.substr(kStart, kEnd - kStart)] = sub.substr(vEnd);
			}
		}

		start = s.find_first_not_of(delim, end);
		end   = s.find_first_of(delim, start);
	}

	free(buf);
}


static bool GetValue(const char * key, bool def)
{
	if (keystore.find(key) == keystore.end())
		return def;

	return (atoi(keystore[key].c_str()) == 0 ? false : true);
}


static int GetValue(const char * key, int def)
{
	if (keystore.find(key) == keystore.end())
		return def;

	return atoi(keystore[key].c_str());
}


static const char * GetValue(const char * key, const char * def)
{
	if (keystore.find(key) == keystore.end())
		return def;

	return keystore[key].c_str();
}


static void SetValue(const char * key, bool value)
{
	keystore[key] = (value ? "1" : "0");
}


static void SetValue(const char * key, int value)
{
	char buf[64];

	sprintf(buf, "%i", value);
	keystore[key] = buf;
}


static void SetValue(const char * key, unsigned int value)
{
	char buf[64];

	sprintf(buf, "%u", value);
	keystore[key] = buf;
}


static void SetValue(const char * key, const char * value)
{
	keystore[key] = value;
}


//
// Check path for a trailing slash, and append if not present
//
static void CheckForTrailingSlash(char * path)
{
	uint32_t len = strlen(path);

	// If the length is greater than zero, and the last char in the string is
	// not a forward slash, and there's room for one more character, then add a
	// trailing slash.
	if ((len > 0) && (path[len - 1] != '/') && (len < MAX_PATH))
		strcat(path, "/");
}


//
// Update the values in the config file (if one exists) with updated values in
// the keystore.
//
static void UpdateConfigFile(void)
{
	// Sanity check
	if (configLoc == -1)
	{
		WriteLog("Settings: Creating default config...\n");
		configLoc = 0;
	}

	char * buf = (char *)ReadFile(configPath[configLoc]);

	FILE * f = fopen(configPath[configLoc], "w");

	if (f == NULL)
	{
		WriteLog("Settings: Could not open config file for writing!\n");
		free(buf);
		return;
	}

	std::string s(buf);

	const std::string delim = "\n\r";
	std::string::size_type start = 0;
	std::string::size_type end   = s.find_first_of(delim, start);

	while (end != std::string::npos)
	{
		if (end > start)
		{
			std::string sub = s.substr(start, end - start);

			if ((sub[0] != '#') && (sub[0] != '['))
			{
				std::string::size_type kStart = sub.find_first_not_of(" ", 0);
				std::string::size_type kEnd   = sub.find_first_of(" ", kStart);

				if ((kStart != std::string::npos)
					&& (kEnd != std::string::npos))
				{
					std::string key = sub.substr(kStart, kEnd - kStart);

					if (keystore.find(key) != keystore.end())
					{
						sub = key + " = " + keystore[key];
						keystore.erase(key);
					}
				}
			}

			fprintf(f, "%s\n", sub.c_str());
		}
		else
			fprintf(f, "\n");

		start = end + 1;
		end   = s.find_first_of(delim, start);
	}

	std::map<std::string, std::string>::iterator i;

	for(i=keystore.begin(); i!=keystore.end(); i++)
		fprintf(f, "%s = %s\n", i->first.c_str(), i->second.c_str());

	fclose(f);
	free(buf);
}

