/*****************************************************************************
 * Copyright (c) 2014-2026 OpenRCT2 developers
 *
 * For a complete list of all authors, please refer to contributors.md
 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 is licensed under the GNU General Public License version 3.
 *****************************************************************************/

#include "../Context.h"
#include "../OpenRCT2.h"
#include "../PlatformEnvironment.h"
#include "../Version.h"
#include "../config/Config.h"
#include "../core/Console.hpp"
#include "../core/File.h"
#include "../core/Guard.hpp"
#include "../core/Memory.hpp"
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../localisation/Language.h"
#include "../network/Network.h"
#include "../object/ObjectRepository.h"
#include "../park/ParkFile.h"
#include "../platform/Crash.h"
#include "../platform/Platform.h"
#include "../scripting/ScriptEngine.h"
#include "CommandLine.hpp"

#include <ctime>
#include <iterator>
#include <string>

#ifdef USE_BREAKPAD
    #define IMPLIES_SILENT_BREAKPAD ", implies --silent-breakpad"
#else
    #define IMPLIES_SILENT_BREAKPAD
#endif // USE_BREAKPAD

namespace OpenRCT2
{
#ifndef DISABLE_NETWORK
    Network::Mode gNetworkStart = Network::Mode::none;
    std::string gNetworkStartHost;
    int32_t gNetworkStartPort = Network::kDefaultPort;
    std::string gNetworkStartAddress;

    static uint32_t _port = 0;
    static char* _address = nullptr;
#endif

    static bool _help = false;
    static bool _version = false;
    static bool _noInstall = false;
    static bool _all = false;
    static bool _about = false;
    static bool _verbose = false;
    static bool _headless = false;
    static bool _silentReplays = false;
    static u8string _password = {};
    static u8string _userDataPath = {};
    static u8string _openrct2DataPath = {};
    static u8string _rct1DataPath = {};
    static u8string _rct2DataPath = {};
    static bool _silentBreakpad = false;

    // clang-format off
    static constexpr CommandLineOptionDefinition kStandardOptions[]
    {
        { CMDLINE_TYPE_SWITCH,  &_help,             'h', "help",               "show this help message and exit"                            },
        { CMDLINE_TYPE_SWITCH,  &_version,          'v', "version",            "show version information and exit"                          },
        { CMDLINE_TYPE_SWITCH,  &_noInstall,        'n', "no-install",         "do not install scenario if passed"                          },
        { CMDLINE_TYPE_SWITCH,  &_all,              'a', "all",                "show help for all commands"                                 },
        { CMDLINE_TYPE_SWITCH,  &_about,            kNAC, "about",              "show information about " OPENRCT2_NAME                      },
        { CMDLINE_TYPE_SWITCH,  &_verbose,          kNAC, "verbose",            "log verbose messages"                                       },
        { CMDLINE_TYPE_SWITCH,  &_headless,         kNAC, "headless",           "run " OPENRCT2_NAME " headless" IMPLIES_SILENT_BREAKPAD     },
        { CMDLINE_TYPE_SWITCH,  &_silentReplays,    kNAC, "silent-replays",     "use unobtrusive replays"                                    },
    #ifndef DISABLE_NETWORK
        { CMDLINE_TYPE_INTEGER, &_port,             kNAC, "port",               "port to use for hosting or joining a server"                },
        { CMDLINE_TYPE_STRING,  &_address,          kNAC, "address",            "address to listen on when hosting a server"                 },
    #endif
        { CMDLINE_TYPE_STRING,  &_password,         kNAC, "password",           "password needed to join the server"                         },
        { CMDLINE_TYPE_STRING,  &_userDataPath,     kNAC, "user-data-path",     "path to the user data directory (containing config.ini)"    },
        { CMDLINE_TYPE_STRING,  &_openrct2DataPath, kNAC, "openrct2-data-path", "path to the OpenRCT2 data directory (containing languages)" },
        { CMDLINE_TYPE_STRING,  &_rct1DataPath,     kNAC, "rct1-data-path",     "path to the RollerCoaster Tycoon 1 data directory (containing data/csg1.dat)" },
        { CMDLINE_TYPE_STRING,  &_rct2DataPath,     kNAC, "rct2-data-path",     "path to the RollerCoaster Tycoon 2 data directory (containing data/g1.dat)" },
    #ifdef USE_BREAKPAD
        { CMDLINE_TYPE_SWITCH,  &_silentBreakpad,  kNAC, "silent-breakpad",   "make breakpad crash reporting silent"                       },
    #endif // USE_BREAKPAD
        kOptionTableEnd
    };

    static exitcode_t HandleNoCommand(CommandLineArgEnumerator * enumerator);
    static exitcode_t HandleCommandEdit(CommandLineArgEnumerator * enumerator);
    static exitcode_t HandleCommandIntro(CommandLineArgEnumerator * enumerator);
    #ifndef DISABLE_NETWORK
    static exitcode_t HandleCommandHost(CommandLineArgEnumerator * enumerator);
    static exitcode_t HandleCommandJoin(CommandLineArgEnumerator * enumerator);
    #endif
    static exitcode_t HandleCommandSetRCT2(CommandLineArgEnumerator * enumerator);
    static exitcode_t HandleCommandScanObjects(CommandLineArgEnumerator * enumerator);

    #if defined(_WIN32)

    static bool _removeShell = false;

    static constexpr CommandLineOptionDefinition RegisterShellOptions[]
    {
        { CMDLINE_TYPE_SWITCH, &_removeShell, 'd', "remove", "remove shell integration" },
    };

    static exitcode_t HandleCommandRegisterShell(CommandLineArgEnumerator * enumerator);

    #endif

    static void PrintAbout();
    static void PrintVersion();
    static void PrintLaunchInformation();

    const CommandLineCommand CommandLine::kRootCommands[]
    {
        // Main commands
    #ifndef DISABLE_HTTP
        DefineCommand("",         "<uri>",                  kStandardOptions, HandleNoCommand     ),
        DefineCommand("edit",     "<uri>",                  kStandardOptions, HandleCommandEdit   ),
    #else
        DefineCommand("",         "<path>",                 kStandardOptions, HandleNoCommand     ),
        DefineCommand("edit",     "<path>",                 kStandardOptions, HandleCommandEdit   ),
    #endif
        DefineCommand("intro",    "",                       kStandardOptions, HandleCommandIntro  ),
    #ifndef DISABLE_NETWORK
        DefineCommand("host",     "<uri>",                  kStandardOptions, HandleCommandHost   ),
        DefineCommand("join",     "<hostname>",             kStandardOptions, HandleCommandJoin   ),
    #endif
        DefineCommand("set-rct2", "<path>",                 kStandardOptions, HandleCommandSetRCT2),
        DefineCommand("scan-objects", "<path>",             kStandardOptions, HandleCommandScanObjects),
        DefineCommand("handle-uri", "openrct2://.../",      kStandardOptions, HandleCommandUri),
        DefineCommand("trigger-steam-download", "",         kStandardOptions, HandleCommandTriggerSteamDownload),

    #if defined(_WIN32)
        DefineCommand("register-shell", "", RegisterShellOptions, HandleCommandRegisterShell),
    #endif

        // Sub-commands
        DefineSubCommand("convert",         kConvertCommands          ),
        DefineSubCommand("screenshot",      kScreenshotCommands       ),
        DefineSubCommand("sprite",          Sprite::kSpriteCommands   ),
        DefineSubCommand("simulate",        kSimulateCommands         ),
        DefineSubCommand("parkinfo",        kParkInfoCommands         ),
        kCommandTableEnd
    };

    const CommandLineExample CommandLine::kRootExamples[]
    {
        { "./my_park.sv6",                                "open a saved park"                      },
        { "./SnowyPark.sc6",                              "install and open a scenario"            },
        { "./ShuttleLoop.td6",                            "install a track"                        },
    #ifndef DISABLE_HTTP
        { "https://example.org/files/ExamplePark.sv6", "download and open a saved park"         },
    #endif
    #ifndef DISABLE_NETWORK
        { "host ./my_park.sv6 --port 11753 --headless",   "run a headless server for a saved park" },
    #endif
        kExampleTableEnd
    };
    // clang-format on

    exitcode_t CommandLine::HandleCommandDefault()
    {
        exitcode_t result = EXITCODE_CONTINUE;

        if (_about)
        {
            PrintAbout();
            result = EXITCODE_OK;
        }
        else
        {
            if (_verbose)
            {
                _log_levels[EnumValue(DiagnosticLevel::Verbose)] = true;
                PrintLaunchInformation();
            }

            if (_version)
            {
                if (!_verbose)
                {
                    PrintVersion();
                }
                result = EXITCODE_OK;
            }
        }

        if (_help || _all)
        {
            PrintHelp(_all);
            result = EXITCODE_OK;
        }

        gOpenRCT2Headless = _headless;
        gOpenRCT2NoGraphics = _headless;
        gOpenRCT2SilentBreakpad = _silentBreakpad || _headless;

        if (!_userDataPath.empty())
        {
            gCustomUserDataPath = Path::GetAbsolute(_userDataPath);
        }

        if (!_openrct2DataPath.empty())
        {
            gCustomOpenRCT2DataPath = Path::GetAbsolute(_openrct2DataPath);
        }

        if (!_rct1DataPath.empty())
        {
            gCustomRCT1DataPath = Path::GetAbsolute(_rct1DataPath);
        }

        if (!_rct2DataPath.empty())
        {
            gCustomRCT2DataPath = Path::GetAbsolute(_rct2DataPath);
        }

        if (!_password.empty())
        {
            gCustomPassword = _password;
        }

        if (_silentReplays)
        {
            gSilentReplays = _silentReplays;
        }

        return result;
    }

    exitcode_t HandleNoCommand(CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        const char* parkUri;
        if (enumerator->TryPopString(&parkUri) && parkUri[0] != '-')
        {
            String::set(gOpenRCT2StartupActionPath, sizeof(gOpenRCT2StartupActionPath), parkUri);
            gOpenRCT2StartupAction = StartupAction::Open;
        }

        return EXITCODE_CONTINUE;
    }

    exitcode_t HandleCommandEdit(CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        const char* parkUri;
        if (!enumerator->TryPopString(&parkUri))
        {
            Console::Error::WriteLine("Expected path or URL to a saved park.");
            return EXITCODE_FAIL;
        }
        String::set(gOpenRCT2StartupActionPath, sizeof(gOpenRCT2StartupActionPath), parkUri);

        gOpenRCT2StartupAction = StartupAction::Edit;
        return EXITCODE_CONTINUE;
    }

    exitcode_t HandleCommandIntro([[maybe_unused]] CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        gOpenRCT2StartupAction = StartupAction::Intro;
        return EXITCODE_CONTINUE;
    }

#ifndef DISABLE_NETWORK

    exitcode_t HandleCommandHost(CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        const char* parkUri;
        if (!enumerator->TryPopString(&parkUri))
        {
            Console::Error::WriteLine("Expected path or URL to a scenario or saved park.");
            return EXITCODE_FAIL;
        }

        gOpenRCT2StartupAction = StartupAction::Open;
        String::set(gOpenRCT2StartupActionPath, sizeof(gOpenRCT2StartupActionPath), parkUri);

        gNetworkStart = Network::Mode::server;
        gNetworkStartPort = _port;
        gNetworkStartAddress = String::toStd(_address);

        return EXITCODE_CONTINUE;
    }

    exitcode_t HandleCommandJoin(CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        const char* hostname;
        if (!enumerator->TryPopString(&hostname))
        {
            Console::Error::WriteLine("Expected a hostname or IP address to the server to connect to.");
            return EXITCODE_FAIL;
        }

        gNetworkStart = Network::Mode::client;
        gNetworkStartPort = _port;
        gNetworkStartHost = hostname;
        return EXITCODE_CONTINUE;
    }

#endif // DISABLE_NETWORK

    static exitcode_t HandleCommandSetRCT2(CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        // Get the path that was passed
        const utf8* rawPath;
        if (!enumerator->TryPopString(&rawPath))
        {
            Console::Error::WriteLine("Expected a path.");
            return EXITCODE_FAIL;
        }

        const auto path = Path::GetAbsolute(rawPath);

        // Check if path exists
        Console::WriteLine("Checking path...");
        if (!Path::DirectoryExists(path))
        {
            Console::Error::WriteLine("The path '%s' does not exist", path.c_str());
            return EXITCODE_FAIL;
        }

        // Check if g1.dat exists (naive but good check)
        Console::WriteLine("Checking g1.dat...");

        auto pathG1Check = Path::Combine(path, u8"Data", u8"g1.dat");
        if (!File::Exists(pathG1Check))
        {
            Console::Error::WriteLine("RCT2 path not valid.");
            Console::Error::WriteLine("Unable to find %s.", pathG1Check.c_str());
            return EXITCODE_FAIL;
        }

        // Update RCT2 path in config
        auto env = CreatePlatformEnvironment();
        auto configPath = env->GetFilePath(PathId::config);
        Config::SetDefaults();
        Config::OpenFromPath(configPath);
        Config::Get().general.rct2Path = path;
        if (Config::SaveToPath(configPath))
        {
            Console::WriteFormat("Updating RCT2 path to '%s'.", path.c_str());
            Console::WriteLine();
            Console::WriteLine("Updated config.ini");
            return EXITCODE_OK;
        }

        Console::Error::WriteLine("Unable to update config.ini");
        return EXITCODE_FAIL;
    }

    static exitcode_t HandleCommandScanObjects([[maybe_unused]] CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        gOpenRCT2Headless = true;
        gOpenRCT2NoGraphics = true;

        auto context = CreateContext();
        auto& env = context->GetPlatformEnvironment();
        auto objectRepository = CreateObjectRepository(env);
        objectRepository->Construct(Config::Get().general.language);
        return EXITCODE_OK;
    }

#if defined(_WIN32)
    static exitcode_t HandleCommandRegisterShell([[maybe_unused]] CommandLineArgEnumerator* enumerator)
    {
        exitcode_t result = CommandLine::HandleCommandDefault();
        if (result != EXITCODE_CONTINUE)
        {
            return result;
        }

        if (!_removeShell)
        {
            Platform::SetUpFileAssociations();
        }
        else
        {
            Platform::RemoveFileAssociations();
        }
        return EXITCODE_OK;
    }
#endif // defined(_WIN32)

    static void PrintAbout()
    {
        PrintVersion();
        Console::WriteLine();
        Console::WriteLine("OpenRCT2 is an amusement park simulation game based upon the popular game");
        Console::WriteLine("RollerCoaster Tycoon 2, written by Chris Sawyer. It attempts to mimic the ");
        Console::WriteLine("original game as closely as possible while extending it with new features.");
        Console::WriteLine("OpenRCT2 is licensed under the GNU General Public License version 3.0, but");
        Console::WriteLine("includes some 3rd party software under different licenses. See the file");
        Console::WriteLine("\"licence.txt\" shipped with the game for details.");
        Console::WriteLine();
        Console::WriteLine("Website:        https://openrct2.io");
        Console::WriteLine("GitHub:         https://github.com/OpenRCT2/OpenRCT2");
        Console::WriteLine("Contributors:   https://github.com/OpenRCT2/OpenRCT2/blob/develop/contributors.md");
        Console::WriteLine("Privacy Policy: https://github.com/OpenRCT2/OpenRCT2/blob/develop/PRIVACY.md");
        Console::WriteLine();
    }

    static void PrintVersion()
    {
        u8string versionInfo = gVersionInfoFull;
        Console::WriteLine(versionInfo.c_str());
        Console::WriteFormat("%s (%s)", OPENRCT2_PLATFORM, OPENRCT2_ARCHITECTURE);
        Console::WriteLine();
        Console::WriteFormat("Network version: %s", Network::GetVersion().c_str());
        Console::WriteLine();
#ifdef ENABLE_SCRIPTING
        Console::WriteFormat("Plugin API version: %d", Scripting::kPluginApiVersion);
        Console::WriteLine();
#else
        Console::WriteFormat("Plugin API not enabled in this build");
        Console::WriteLine();
#endif
        Console::WriteFormat("Current park file version: %d", kParkFileCurrentVersion);
        Console::WriteLine();
        Console::WriteFormat("Minimum park file version: %d", kParkFileMinVersion);
        Console::WriteLine();
#ifdef USE_BREAKPAD
        Console::WriteFormat("With breakpad support enabled");
        Console::WriteLine();
#else
        Console::WriteFormat("Breakpad support disabled");
        Console::WriteLine();
#endif
    }

    static void PrintLaunchInformation()
    {
        char buffer[256];
        time_t timer;
        struct tm* tmInfo;

        // Print name and version information
        u8string versionInfo = gVersionInfoFull;
        Console::WriteLine(versionInfo.c_str());
        Console::WriteFormat("%s (%s)", OPENRCT2_PLATFORM, OPENRCT2_ARCHITECTURE);
        Console::WriteLine();
        Console::WriteLine();

        // Print current time
        time(&timer);
        tmInfo = localtime(&timer);
        strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", tmInfo);
        Console::WriteFormat("VERBOSE: time is %s", buffer);
        Console::WriteLine();

        // TODO Print other potential information (e.g. user, hardware)
    }

    exitcode_t CommandLine::HandleCommandTriggerSteamDownload([[maybe_unused]] CommandLineArgEnumerator* enumerator)
    {
        if (!Platform::triggerSteamDownload())
        {
            return EXITCODE_FAIL;
        }

        return EXITCODE_OK;
    }
} // namespace OpenRCT2
