/* SPDX-License-Identifier: MIT License

Copyright © 2021 Thomas “illwieckz” Debesse

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. */

/* transformpath: transform file path based on keywords.

This is not an environment variable parser, this only supports
a set of keywords using common environment name syntax when it
exists to make the strings easier to read.

Supported substitution keywords,
Windows:

- %HOMEPATH%
- %USERPROFILE%
- %ProgramFiles%
- %ProgramFiles(x86)%
- %ProgramW6432%
- %APPDATA%
- [CSIDL_MYDOCUMENTS]

Supported substitution keywords,
Linux, FreeBSD, macOS:

- ~
- ${HOME}

Supported substitution keywords,
Linux, FreeBSD, other XDG systems:

- ${XDG_CONFIG_HOME}
- ${XDG_DATA_HOME}

Examples,
game engine directories:

- Windows: %ProgramFiles%\Unvanquished
- Linux: ${XDG_DATA_HOME}/unvanquished/base
- macOS: ${HOME}/Games/Unvanquished

Examples,
game home directories:

- Windows: [CSIDL_MYDOCUMENTS]\My Games\Unvanquished
- Linux: ${XDG_DATA_HOME}/unvanquished
- macOS: ${HOME}/Application Data/Unvanquished

*/

#include "globaldefs.h"
#include "stringio.h"

#include <cstring>
#include <string>

#if GDEF_OS_WINDOWS
#include <windows.h>
#include <iostream>
#include <shlobj.h>
#pragma comment(lib, "shell32.lib")
#endif // !GDEF_OS_WINDOWS

#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS

static std::string getEnvVar( const char* name )
{
	const char *env = getenv( name );
	env = env ? env : "";
	std::string path( env );
	return path;
}

#if GDEF_OS_WINDOWS
static std::string getUserProfilePath();
#endif // !GDEF_OS_WINDOWS

static std::string getUserName()
{
#if GDEF_OS_WINDOWS
	std::string path = getEnvVar( "USERNAME" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%USERNAME% not found.\n";

	return "";
#endif // !GDEF_OS_WINDOWS

#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
	std::string path = getEnvVar( "USERNAME" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "${USERNAME} not found, guessing…\n";

	path = getEnvVar( "LOGNAME" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "${LOGNAME} not found, guessing…\n";

	path = getEnvVar( "USER" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "${USER} not found.\n";

	return "";
#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
}

static std::string getHomePath()
{
#if GDEF_OS_WINDOWS
	std::string path = getEnvVar( "HOMEPATH" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%HOMEPATH% not found, guessing…\n";

	std::string path1 = getUserProfilePath();

	if ( ! path1.empty() )
	{
		return path1;
	}

	globalErrorStream() << "%HOMEPATH% not found.\n";

	return "";
#endif // !GDEF_OS_WINDOWS

#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
	// Get the path environment variable.
	std::string path = getEnvVar( "HOME" );

	// Look up path directory in password database.
	if( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "${HOME} not found, guessing…\n";

	static char	buf[ 4096 ];
	struct passwd pw, *pwp;

	if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 )
	{
		return std::string( pw.pw_dir );
	}

	globalErrorStream() << "${HOME} not found, guessing…\n";

	std::string path1( "/home/" );

	std::string path2 = getUserName();

	if ( ! path2.empty() )
	{
		return path1 + path2;
	}

	globalErrorStream() << "${HOME} not found…\n";

	return "";
#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
}

#if GDEF_OS_WINDOWS
static std::string getSystemDrive()
{
	std::string path = getEnvVar( "SYSTEMDRIVE" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%SYSTEMDRIVE% not found, guessing…\n";

	return "C:";
}

static std::string getUserProfilePath()
{
	std::string path = getEnvVar( "USERPROFILE" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%USERPROFILE% not found, guessing…\n";

	std::string path1 = getSystemDrive();
	std::string path2 = getUserName();

	if ( ! path2.empty() )
	{
		return path1 + "\\Users\\" + path2;
	}

	globalErrorStream() << "%USERPROFILE% not found.\n";

	return "";
}

static std::string getProgramFilesPath()
{
	std::string path = getEnvVar( "ProgramFiles" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%ProgramFiles% not found, guessing…\n";

	std::string path1 = getSystemDrive();
	return path1 + "\\Program Files";
}

static std::string getProgramFilesX86Path()
{
	std::string path = getEnvVar( "ProgramFiles(x86)" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%ProgramFiles(x86)% not found, guessing…\n";

	return getProgramFilesPath();
}

static std::string getProgramW6432Path()
{
	std::string path = getEnvVar( "ProgramFilesW6432" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%ProgramW6432% not found, guessing…\n";

	return getProgramFilesPath();
}

static std::string getAppDataPath()
{
	std::string path = getEnvVar( "APPDATA" );

	if ( ! path.empty() )
	{
		return path;
	}

	globalErrorStream() << "%APPDATA% not found, guessing…\n";

	std::string path1 = getUserProfilePath();

	if ( ! path1.empty() )
	{
		return path1 + "\\AppData\\Roaming";
	}

	globalErrorStream() << "%APPDATA% not found.\n";

	return std::string( "" );
}

/* TODO: see also qFOLDERID_SavedGames in mainframe.cpp,
HomePaths_Realise and other things like that,
they look to be game paths, not NetRadiant paths. */

static std::string getMyDocumentsPath()
{
	CHAR path[ MAX_PATH ];
	HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);

	if ( result == S_OK )
	{
		return std::string( path );
	}

	globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found, guessing…\n";

	std::string path1 = getHomePath();

	if ( ! path1.empty() )
	{
		path1 += "\\Documents";
		
		return path1;
	}

	globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found.\n";

	return "";
}
#endif // !GDEF_OS_WINDOWS

#if GDEF_OS_XDG
static std::string getXdgConfigHomePath()
{
	/* FIXME: we may want to rely on g_get_user_config_dir()
	provided by GLib. */

	std::string path = getEnvVar( "XDG_CONFIG_HOME" );

	if ( ! path.empty() )
	{
		return path;
	}

	// This is not an error.
	// globalErrorStream() << "${XDG_CONFIG_HOME} not found, guessing…\n";

	std::string path1 = getHomePath();

	if ( ! path1.empty() )
	{
		return path1 + "/.config";
	}

	globalErrorStream() << "${XDG_CONFIG_HOME} not found.\n";

	return "";
}

static std::string getXdgDataHomePath()
{
	std::string path = getEnvVar( "XDG_DATA_HOME" );

	if ( ! path.empty() )
	{
		return path;
	}

	// This is not an error.
	// globalErrorStream() << "${XDG_DATA_HOME} not found, guessing…\n";

	std::string path1 = getHomePath();

	if ( ! path1.empty() )
	{
		return path1 + "/.local/share";
	}

	globalErrorStream() << "${XDG_DATA_HOME} not found.\n";

	return "";
}
#endif // GDEF_OS_XDG

struct pathTransformer_t
{
	std::string pattern;
	std::string ( *function )();
};

static const pathTransformer_t pathTransformers[] =
{
#if GDEF_OS_WINDOWS
	{ "%HOMEPATH%", getHomePath },
	{ "%USERPROFILE%", getUserProfilePath },
	{ "%ProgramFiles%", getProgramFilesPath },
	{ "%ProgramFiles(x86)%", getProgramFilesX86Path },
	{ "%ProgramW6432%", getProgramW6432Path },
	{ "%APPDATA%", getAppDataPath },
	{ "[CSIDL_MYDOCUMENTS]", getMyDocumentsPath },
#endif // GDEF_OS_WINDOWS

#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
	{ "~", getHomePath },
	{ "${HOME}", getHomePath },
#endif // GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS

#if GDEF_OS_XDG
	{ "${XDG_CONFIG_HOME}", getXdgConfigHomePath },
	{ "${XDG_DATA_HOME}", getXdgDataHomePath },
#endif // GDEF_OS_XDG
};

/* If no transformation succeeds, the path will be returned untransformed. */
std::string transformPath( std::string transformedPath )
{
	for ( const pathTransformer_t &pathTransformer : pathTransformers )
	{
		if ( transformedPath.find( pathTransformer.pattern, 0 ) == 0 )
		{
			globalOutputStream() << "Path Transforming: '" << transformedPath.c_str() << "'\n";

			std::string path = pathTransformer.function();

			if ( ! path.empty() )
			{
				transformedPath.replace( 0, pathTransformer.pattern.length(), path );

				globalOutputStream() << "Path Transformed: '" << transformedPath.c_str() << "'\n";

				return transformedPath;
			}

			break;
		}
	}

	globalErrorStream() << "Path not transformed: '" << transformedPath.c_str() << "'\n";

	return transformedPath;
}
