#include <wx/fileconf.h>
#include <wx/filename.h>
#include <wx/filefn.h>
#include <wx/file.h>
#include <wx/utils.h>
#include "../Headers/ClientConfig.h"
#include "../Headers/Resources/ConfigData.h"

/**
* Gets the singleton instance, constructing (and loading) it on first call.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	ClientConfig& config = ClientConfig::GetInstance();
* @endcode
*
* @return The single ClientConfig instance.
*/
ClientConfig& ClientConfig::GetInstance()
{
	static ClientConfig instance;
	return instance;
}

/**
* Builds the config, seeding every field with its built-in default, then loads any overrides from the INI file.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	//Constructed once by ClientConfig::GetInstance().
* @endcode
*/
ClientConfig::ClientConfig()
{
	myAnimationFps = static_cast<int>(CLIENTCONFIG_DEFAULT_ANIMATION_FPS);
	myCo2FreshMax = CLIENTCONFIG_DEFAULT_CO2_FRESH_MAX;
	myCo2ElevatedMax = CLIENTCONFIG_DEFAULT_CO2_ELEVATED_MAX;
	myTempWarmAbove = CLIENTCONFIG_DEFAULT_TEMP_WARM_ABOVE;
	myTempCoolBelow = CLIENTCONFIG_DEFAULT_TEMP_COOL_BELOW;
	myDuskGlowOpacity = CLIENTCONFIG_DEFAULT_DUSK_GLOW_OPACITY;
	myTemperatureTintOpacity = CLIENTCONFIG_DEFAULT_TEMP_TINT_OPACITY;
	myCloudOpacity = CLIENTCONFIG_DEFAULT_CLOUD_OPACITY;
	myHazeOpacity = CLIENTCONFIG_DEFAULT_HAZE_OPACITY;
	myMistOpacity = CLIENTCONFIG_DEFAULT_MIST_OPACITY;
	myWindOpacity = CLIENTCONFIG_DEFAULT_WIND_OPACITY;

	Load();
}

/**
* Builds the path to the config file (~/rumpi_config.ini), alongside the ~/rumpi_logs and ~/rumpi_images folders.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	wxString path = ConfigFilePath();
* @endcode
*
* @return The full path to the config file.
*/
wxString ClientConfig::ConfigFilePath() const
{
	//Lives alongside the ~/rumpi_logs and ~/rumpi_images folders.
	return wxFileName(wxGetHomeDir(), "rumpi_config.ini").GetFullPath();
}

/**
* Writes the commented default config file to the given path if it doesn't already exist. If writing fails, the
* getters still return the built-in defaults so the app keeps working.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	SeedDefaultFileIfMissing(ConfigFilePath());
* @endcode
*
* @param [in] path The config file path to seed.
*/
void ClientConfig::SeedDefaultFileIfMissing(const wxString& path) const
{
	if (wxFileExists(path))
	{
		return;
	}

	//A commented default so the file is discoverable and easy to hand-edit. If writing fails (e.g. read-only home),
	//the getters still return the built-in defaults, so the app keeps working. The template text lives in
	//Resources/ConfigData.h, embedded like the scene art.
	const wxString content = DEFAULT_CONFIG_FILE;

	wxFile file;
	if (file.Create(path, false, wxS_DEFAULT))
	{
		file.Write(content);
		file.Close();
	}
}

/**
* Seeds the config file if missing, then reads every setting from it, expanding a leading ~ in the images folder
* and falling back to the built-in default for any missing key or malformed colour.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	//Called once by the constructor.
* @endcode
*/
void ClientConfig::Load()
{
	long fps = CLIENTCONFIG_DEFAULT_ANIMATION_FPS;
	const wxString path = ConfigFilePath();
	SeedDefaultFileIfMissing(path);

	wxFileConfig config(wxEmptyString, wxEmptyString, path, wxEmptyString, wxCONFIG_USE_LOCAL_FILE);

	myLocation = config.Read("/General/Location", CLIENTCONFIG_DEFAULT_LOCATION_NAME);
	myImagesFolder = config.Read("/Scene/ImagesFolder", CLIENTCONFIG_DEFAULT_IMAGES_FOLDER);
	if (myImagesFolder.StartsWith("~"))
	{
		//Expand a leading ~ to the home directory so the rest of the app gets an absolute path.
		myImagesFolder = wxGetHomeDir() + myImagesFolder.Mid(1);
	}

	config.Read("/Scene/AnimationFps", &fps, CLIENTCONFIG_DEFAULT_ANIMATION_FPS);
	myAnimationFps = static_cast<int>(fps);

	config.Read("/Thresholds/Co2FreshMax", &myCo2FreshMax, CLIENTCONFIG_DEFAULT_CO2_FRESH_MAX);
	config.Read("/Thresholds/Co2ElevatedMax", &myCo2ElevatedMax, CLIENTCONFIG_DEFAULT_CO2_ELEVATED_MAX);
	config.Read("/Thresholds/TempWarmAbove", &myTempWarmAbove, CLIENTCONFIG_DEFAULT_TEMP_WARM_ABOVE);
	config.Read("/Thresholds/TempCoolBelow", &myTempCoolBelow, CLIENTCONFIG_DEFAULT_TEMP_COOL_BELOW);

	config.Read("/Overlays/DuskGlow", &myDuskGlowOpacity, CLIENTCONFIG_DEFAULT_DUSK_GLOW_OPACITY);
	config.Read("/Overlays/TemperatureTint", &myTemperatureTintOpacity, CLIENTCONFIG_DEFAULT_TEMP_TINT_OPACITY);
	config.Read("/Overlays/Clouds", &myCloudOpacity, CLIENTCONFIG_DEFAULT_CLOUD_OPACITY);
	config.Read("/Overlays/Haze", &myHazeOpacity, CLIENTCONFIG_DEFAULT_HAZE_OPACITY);
	config.Read("/Overlays/Mist", &myMistOpacity, CLIENTCONFIG_DEFAULT_MIST_OPACITY);
	config.Read("/Overlays/Wind", &myWindOpacity, CLIENTCONFIG_DEFAULT_WIND_OPACITY);

	//Colours are stored as hex strings. wxColour parses "#RRGGBB"; fall back to the default if a hand-edited
	//value is malformed so a typo can never leave the indicator with an invalid colour.
	myCo2FreshColour = wxColour(config.Read("/Colors/Co2Fresh", CLIENTCONFIG_DEFAULT_CO2_FRESH_COLOUR));
	if (myCo2FreshColour.IsOk() == false)
	{
		myCo2FreshColour = wxColour(CLIENTCONFIG_DEFAULT_CO2_FRESH_COLOUR);
	}

	myCo2ElevatedColour = wxColour(config.Read("/Colors/Co2Elevated", CLIENTCONFIG_DEFAULT_CO2_ELEVATED_COLOUR));
	if (myCo2ElevatedColour.IsOk() == false)
	{
		myCo2ElevatedColour = wxColour(CLIENTCONFIG_DEFAULT_CO2_ELEVATED_COLOUR);
	}

	myCo2HighColour = wxColour(config.Read("/Colors/Co2High", CLIENTCONFIG_DEFAULT_CO2_HIGH_COLOUR));
	if (myCo2HighColour.IsOk() == false)
	{
		myCo2HighColour = wxColour(CLIENTCONFIG_DEFAULT_CO2_HIGH_COLOUR);
	}
}

/**
* Gets the configured location name.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	wxString location = config.GetLocation();
* @endcode
*
* @return The location name.
*/
const wxString& ClientConfig::GetLocation() const
{
	return myLocation;
}

/**
* Gets the configured images folder (with a leading ~ already expanded to the home directory).
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	wxString folder = config.GetImagesFolder();
* @endcode
*
* @return The images folder path.
*/
const wxString& ClientConfig::GetImagesFolder() const
{
	return myImagesFolder;
}

/**
* Gets the scene animation frame rate.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	int fps = config.GetAnimationFps();
* @endcode
*
* @return The animation frame rate.
*/
int ClientConfig::GetAnimationFps() const
{
	return myAnimationFps;
}

/**
* Gets the upper CO2 bound for the "fresh" band.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double max = config.GetCo2FreshMax();
* @endcode
*
* @return The "fresh" CO2 upper bound.
*/
double ClientConfig::GetCo2FreshMax() const
{
	return myCo2FreshMax;
}

/**
* Gets the upper CO2 bound for the "elevated" band.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double max = config.GetCo2ElevatedMax();
* @endcode
*
* @return The "elevated" CO2 upper bound.
*/
double ClientConfig::GetCo2ElevatedMax() const
{
	return myCo2ElevatedMax;
}

/**
* Gets the temperature above which it reads "warm".
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double temp = config.GetTempWarmAbove();
* @endcode
*
* @return The "warm" temperature threshold.
*/
double ClientConfig::GetTempWarmAbove() const
{
	return myTempWarmAbove;
}

/**
* Gets the temperature below which it reads "cool".
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double temp = config.GetTempCoolBelow();
* @endcode
*
* @return The "cool" temperature threshold.
*/
double ClientConfig::GetTempCoolBelow() const
{
	return myTempCoolBelow;
}

/**
* Gets the maximum dusk-glow overlay opacity.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double opacity = config.GetDuskGlowOpacity();
* @endcode
*
* @return The dusk-glow overlay opacity (0.0 - 1.0).
*/
double ClientConfig::GetDuskGlowOpacity() const
{
	return myDuskGlowOpacity;
}

/**
* Gets the maximum temperature-tint overlay opacity.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double opacity = config.GetTemperatureTintOpacity();
* @endcode
*
* @return The temperature-tint overlay opacity (0.0 - 1.0).
*/
double ClientConfig::GetTemperatureTintOpacity() const
{
	return myTemperatureTintOpacity;
}

/**
* Gets the maximum cloud overlay opacity.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double opacity = config.GetCloudOpacity();
* @endcode
*
* @return The cloud overlay opacity (0.0 - 1.0).
*/
double ClientConfig::GetCloudOpacity() const
{
	return myCloudOpacity;
}

/**
* Gets the maximum haze overlay opacity.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double opacity = config.GetHazeOpacity();
* @endcode
*
* @return The haze overlay opacity (0.0 - 1.0).
*/
double ClientConfig::GetHazeOpacity() const
{
	return myHazeOpacity;
}

/**
* Gets the maximum mist overlay opacity.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double opacity = config.GetMistOpacity();
* @endcode
*
* @return The mist overlay opacity (0.0 - 1.0).
*/
double ClientConfig::GetMistOpacity() const
{
	return myMistOpacity;
}

/**
* Gets the maximum wind overlay opacity.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	double opacity = config.GetWindOpacity();
* @endcode
*
* @return The wind overlay opacity (0.0 - 1.0).
*/
double ClientConfig::GetWindOpacity() const
{
	return myWindOpacity;
}

/**
* Gets the indicator colour for the "fresh" CO2 band.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	wxColour colour = config.GetCo2FreshColour();
* @endcode
*
* @return The "fresh" CO2 indicator colour.
*/
wxColour ClientConfig::GetCo2FreshColour() const
{
	return myCo2FreshColour;
}

/**
* Gets the indicator colour for the "elevated" CO2 band.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	wxColour colour = config.GetCo2ElevatedColour();
* @endcode
*
* @return The "elevated" CO2 indicator colour.
*/
wxColour ClientConfig::GetCo2ElevatedColour() const
{
	return myCo2ElevatedColour;
}

/**
* Gets the indicator colour for the "high" CO2 band.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	wxColour colour = config.GetCo2HighColour();
* @endcode
*
* @return The "high" CO2 indicator colour.
*/
wxColour ClientConfig::GetCo2HighColour() const
{
	return myCo2HighColour;
}
