/********************************************************************************
 * Copyright (c) 2025-2026 ZF Friedrichshafen AG
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License Version 2.0 which is available at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0   
 *
 * Contributors: 
 *   Erik Verhoeven - initial API and implementation
 ********************************************************************************/

#include <support/sdv_core.h>
#include <atomic>
#include "../../global/cmdlnparser/cmdlnparser.cpp"
#include "../../global/exec_dir_helper.h"
#include <support/app_control.h>
#include "../error_msg.h"

#ifdef _WIN32
/**
 * @brief Console application control handler (CTRL+C, close, shutdown, etc.).
 * @param[in] dwCtrlType The control type triggered by the system.
 * @return Returns TRUE when the signal was handled.
 */
static BOOL WINAPI ControlHandler(DWORD dwCtrlType)
{
    switch (dwCtrlType)
    {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:
    case CTRL_CLOSE_EVENT:
    case CTRL_LOGOFF_EVENT:
    case CTRL_SHUTDOWN_EVENT:
        return TRUE;
        break;
    default:
        return FALSE;
    }
}
#elif defined __unix__
#include <signal.h>

 /**
 * @brief Signal handler to catch signal events.
 * @param[in] iSigNum The signal type that was triggered.
 */
static void SignalHandler(int /*iSigNum*/)
{
    // Do nothing...
}
#endif

/**
* @brief Main function of the executable.
* @param[in] iArgc Amount of arguments provided.
* @param[in] rgszArgv Array of C-string pointers. The first entry is the name or path to this executable.
* @return Returns the executable's exit code.
*/
#if defined(_WIN32) && defined(_UNICODE)
extern "C" int wmain(int iArgc, const wchar_t* rgszArgv[])
#else
extern "C" int main(int iArgc, const char* rgszArgv[])
#endif
{
    // Workaround for GCC to make certain that POSIX thread library is loaded before the components are loaded.
    // REASON: If the first call to a thread is done in a dynamic library, the application is already classified as single
    // threaded and a termination is initiated.
    // See: https://stackoverflow.com/questions/51209268/using-stdthread-in-a-library-loaded-with-dlopen-leads-to-a-sigsev
    // NOTE EVE 27.05.2025: in release builds, starting and ending the thread right after each other causes incorrect behavior and
    // leads in some cases to create a deadlock in the join-function. The solution is to add delays in the thread processing.
    std::atomic_bool bThreadStarted = false;
    std::thread thread = std::thread([&]()
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            bThreadStarted = true;
        });
    while (!bThreadStarted) {std::this_thread::sleep_for(std::chrono::milliseconds(100));}
    if (thread.joinable())
        thread.join();

    CCommandLine cmdln(static_cast<uint32_t>(CCommandLine::EParseFlags::no_assignment_character));
    bool bHelp = false;
    bool bError = false;
    bool bSilent = false;
    bool bNoBanner = false;
    bool bVerbose = false;
    bool bVersion = false;
    bool bStandalone = false;
    bool bServer = false;
    std::filesystem::path pathConfig;
    uint32_t uiInstanceID = 1000;   // Default instance is 1000
    std::filesystem::path pathInstallDir;
    std::string ssArgError;
    try
    {
        auto& rArgHelpDef = cmdln.DefineOption("?", bHelp, "Show help");
        rArgHelpDef.AddSubOptionName("help");
        auto& rArgSilentDef = cmdln.DefineOption("s", bSilent, "Do not show any information on STDOUT. Not compatible with 'verbose'.");
        rArgSilentDef.AddSubOptionName("silent");
        auto& rArgVerboseDef = cmdln.DefineOption("v", bVerbose, "Provide verbose information. Not compatible with 'silent'.");
        rArgVerboseDef.AddSubOptionName("verbose");
        cmdln.DefineSubOption("no_banner", bNoBanner, "Do not show banner information. Ignored when silent is activated.");
        cmdln.DefineSubOption("version", bVersion, "Show version information.");
        cmdln.DefineSubOption("instance", uiInstanceID, "The instance ID of the SDV instance (default ID is 1000).");
        cmdln.DefineOption("local", bStandalone, "Start the local version (default - no IPC available).");
        cmdln.DefineOption("server", bServer, "Start the server version (not compatible with the local version).");
        cmdln.DefineSubOption("install_dir", pathInstallDir, "Installation directory (absolute or relative to this executable).");
        cmdln.DefineDefaultArgument(pathConfig, "Configuration file to start running (compulsory; applicable for local version only).");
        cmdln.Parse(static_cast<size_t>(iArgc), rgszArgv);
    } catch (const SArgumentParseException& rsExcept)
    {
        ssArgError = rsExcept.what();
        bHelp = true;
        bError = true;
    }

    if (!bSilent && !bNoBanner)
    {
        std::cout << "SDV core application" << std::endl;
        std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl;
        std::cout << "Author: Erik Verhoeven" << std::endl << std::endl;
    }

    if (!ssArgError.empty() && !bSilent)
        std::cerr << "ERROR: " << ssArgError << std::endl;

    if (bServer && bStandalone)
    {
        std::cerr << "ERROR: The commandline options -server and -local cannot be supplied at the same time." << std::endl;
        bHelp = true;
        bError = true;
    }
    bStandalone = !bServer;

    if (!pathConfig.empty() && bServer)
    {
        std::cerr << "ERROR: Cannot supply a configuration when running as server." << std::endl;
        bHelp = true;
        bError = true;
    }
    else if (pathConfig.empty() && bStandalone)
    {
        std::cerr << "ERROR: Needing a configuration when running as local application." << std::endl;
        bHelp = true;
        bError = true;
    }

    if (bHelp)
    {
        if (!bSilent)
        {
            if (bError)
                std::cout << std::endl;
            cmdln.PrintHelp(std::cout, "The core application is used to start the service framework for use by applications. The "
                "safe version using component isolation and installation manifests should be started with the -safe option and is "
                "for use in the vehicle. For local simulation the stand-alone version can be used.\n");
        }
        return bError ? CMDLN_ARG_ERR : NO_ERROR;
    }
    if (bError) return CMDLN_ARG_ERR;

    if (bVersion || bVerbose)
        std::cout << "Version: " << (SDVFrameworkBuildVersion / 100) << "." << (SDVFrameworkBuildVersion % 100) << " build " <<
        SDVFrameworkSubbuildVersion << " interface " << SDVFrameworkInterfaceVersion << std::endl;
    if (!bSilent)
    {
        std::cout << "Running as: " << (bServer ? "server" : "local") << std::endl;
        std::cout << "Instance ID: " << uiInstanceID << std::endl;
        if (bStandalone)
            std::cout << "Config: " << pathConfig.generic_u8string() << std::endl;
    }
    if (bVerbose)
        std::cout << "Installation directory: " << pathInstallDir.generic_u8string() << std::endl;

    // Create the startup config
    std::stringstream sstreamConfig;
    sstreamConfig << "[Application]" << std::endl;
    sstreamConfig << "Mode = \"" << (bServer ? "Main" : "Standalone") << "\"" << std::endl;
    sstreamConfig << "Instance = " << uiInstanceID << std::endl;
    if (!pathConfig.empty())
        sstreamConfig << "Config = \"" << pathConfig.generic_u8string() << "\"" << std::endl;
    if (!pathInstallDir.empty())
    {
        if (pathInstallDir.is_relative())
            sstreamConfig << "InstallDir = \"" << pathInstallDir.generic_u8string() << "\"" << std::endl;
        else
            sstreamConfig << "InstallDir = \"" << pathInstallDir.lexically_relative(GetExecDirectory()).generic_u8string() <<
            "\"" << std::endl;
    }

    // Add console information
    if (bVerbose || bSilent)
    {
        sstreamConfig << "[Console]" << std::endl;
        sstreamConfig << "Report = ";
        if (bSilent) sstreamConfig << "\"Silent\"";
        else
            sstreamConfig << "\"Verbose\"";
        sstreamConfig << std::endl;
    }

    if (bVerbose)
        std::cout << "Starting up..." << std::endl;

    // Start the application control
    sdv::app::CAppControl appcontrol;
    if (!appcontrol.Startup(sstreamConfig.str()))
    {
        if (!bSilent)
            std::cerr << "ERROR: " << APP_CONTROL_STARTUP_ERROR_MSG << std::endl;
        return APP_CONTROL_STARTUP_ERROR;
    }

#ifdef _WIN32
    // Register the console control handler
    if (!SetConsoleCtrlHandler(&ControlHandler, TRUE))
    {
        if (!bSilent)
            std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << " - failed registering control handler." << std::endl;
        return APP_CONTROL_START_LOOP_FAILED;
    }
#elif defined __unix__
    // Install our signal handler
    struct sigaction sSigAction
    {};
    sSigAction.sa_handler = SignalHandler;
    sSigAction.sa_flags   = SA_RESTART;
    if (sigaction(SIGINT, &sSigAction, NULL) == -1)
    {
        if (!bSilent)
            std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << " - failed registering control handler." << std::endl;
        return APP_CONTROL_START_LOOP_FAILED;
    }
#endif

    // Start the running loop
    if (!appcontrol.RunLoop())
    {
        if (!bSilent)
            std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << std::endl;
        return APP_CONTROL_START_LOOP_FAILED;
    }

#ifdef _WIN32
    SetConsoleCtrlHandler(&ControlHandler, FALSE);
#endif

    if (bVerbose)
    {
        std::cout << "Shutdown request received..." << std::endl;
        std::cout << "Shutting down..." << std::endl;
    }

    // Shutdwown
    appcontrol.Shutdown();

    return NO_ERROR;
}