/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "PlatformFolders.hpp"
#include <iostream>

#ifdef _WIN32
#include <winerror.h>
// For SHGetFolderPathW and various CSIDL "magic numbers"
#include <shlobj.h>
#include <utf8.h>
#include <memory>

namespace OpenVulkano
{
	namespace
	{
		std::string GetKnownWindowsFolder(REFKNOWNFOLDERID folderId, std::string name)
		{
			LPWSTR wszPath = NULL;
			HRESULT hr;
			hr = SHGetKnownFolderPath(folderId, KF_FLAG_CREATE, NULL, &wszPath);
			std::unique_ptr<wchar_t, decltype(&CoTaskMemFree)> scopeBoundMemory(wszPath, &CoTaskMemFree);

			if (!SUCCEEDED(hr))
			{
				throw std::runtime_error("Failed to find " + name + "folder.");
			}
			std::wstring winPath(wszPath);
			std::string str;
			utf8::utf16to8(winPath.begin(), winPath.end(), std::back_inserter(str));
			return str;
		}
	}
}
#endif

namespace OpenVulkano
{
	namespace
	{
		std::filesystem::path GetHome()
		{
			auto home = std::getenv("HOME");
			if (home == nullptr) throw std::runtime_error("HOME environment variable not set!");
			return home;
		}

		[[nodiscard]] std::filesystem::path GetXDGFolderDefault(const char* envName, std::string_view defaultRelativePath)
		{
#ifndef __APPLE__
			const char* envValue = std::getenv(envName);
			if (envValue)
			{
				if (envValue[0] == '/') return envValue;
				std::cerr << "Ignoring environment '" << envName << "' (value: '" << envValue << "') because it's relative! XDG specifies it should be absolute path. Falling back to default path.";
			}
#endif
			return GetHome() / defaultRelativePath;
		}
	}

	PlatformFolders::PlatformFolders()
	{
#ifdef __APPLE__
		dataHome   = GetHome() / "Documents/";
		configHome = dataHome;
		stateDir   = dataHome;
		dataCache  = GetHome() / "Library/Caches/";
		tempDir    = dataCache;
#elif defined(_WIN32)
		dataHome   = GetKnownWindowsFolder(FOLDERID_RoamingAppData, "RoamingAppData");
		configHome = GetKnownWindowsFolder(FOLDERID_RoamingAppData, "RoamingAppData");
		stateDir   = GetKnownWindowsFolder(FOLDERID_LocalAppData, "LocalAppData");
		dataCache  = GetKnownWindowsFolder(FOLDERID_LocalAppData, "LocalAppData");
		desktop    = GetKnownWindowsFolder(FOLDERID_Desktop,    "Desktop");
		documents  = GetKnownWindowsFolder(FOLDERID_Documents,  "Documents");
		downloads  = GetKnownWindowsFolder(FOLDERID_Downloads,  "Downloads");
		pictures   = GetKnownWindowsFolder(FOLDERID_Pictures,   "Pictures");
		publicDir  = GetKnownWindowsFolder(FOLDERID_Public,     "Public");
		music      = GetKnownWindowsFolder(FOLDERID_Music,      "Music");
		video      = GetKnownWindowsFolder(FOLDERID_Videos,     "Videos");
		savedGames = GetKnownWindowsFolder(FOLDERID_SavedGames, "Saved Games");
#else
		dataHome   = GetXDGFolderDefault("XDG_DATA_HOME", ".local/share");
		configHome = GetXDGFolderDefault("XDG_CONFIG_HOME", ".config");
		stateDir   = GetXDGFolderDefault("XDG_STATE_HOME", ".local/state");
		dataCache  = GetXDGFolderDefault("XDG_CACHE_HOME", ".cache");
#endif
#ifndef __APPLE__
		tempDir = std::filesystem::temp_directory_path();
#endif

		// Use XDG resolution on Linux and MacOS (will skip the env variable check on MacOS and just use the fallbacks, since they align with apple)
#ifndef _WIN32
		desktop = GetXDGFolderDefault("XDG_DESKTOP_DIR", "Desktop");
		documents = GetXDGFolderDefault("XDG_DOCUMENTS_DIR", "Documents");
		downloads = GetXDGFolderDefault("XDG_DOWNLOAD_DIR", "Downloads");
		pictures = GetXDGFolderDefault("XDG_PICTURES_DIR", "Pictures");
		publicDir = GetXDGFolderDefault("XDG_PUBLICSHARE_DIR", "Public");
		music = GetXDGFolderDefault("XDG_MUSIC_DIR", "Music");
#ifdef __APPLE__
		video = GetXDGFolderDefault("XDG_VIDEOS_DIR", "Movies");
#else
		video = GetXDGFolderDefault("XDG_VIDEOS_DIR", "Videos");
#endif
		savedGames = documents / "Saved Games";
#endif
	}

	PlatformFolders& PlatformFolders::GetInstance()
	{
		static PlatformFolders instance;
		return instance;
	}
}
