//  OpenVPN 3 Linux client -- Next generation OpenVPN client
//
//  SPDX-License-Identifier: AGPL-3.0-only
//
//  Copyright (C) 2017 - 2023  OpenVPN Inc <sales@openvpn.net>
//  Copyright (C) 2017 - 2023  David Sommerseth <davids@openvpn.net>
//  Copyright (C) 2019 - 2023  Lev Stipakov <lev@openvpn.net>
//

#define SHUTDOWN_NOTIF_PROCESS_NAME "openvpn3-service-configmgr"
#include "dbus/core.hpp"
#include "dbus/path.hpp"
#include "configmgr.hpp"
#include "log/logwriter.hpp"
#include "log/logwriters/implementations.hpp"
#include "log/dbus-log.hpp"
#include "log/proxy-log.hpp"
#include "common/cmdargparser.hpp"
#include "common/utils.hpp"

using namespace openvpn;


static int config_manager(ParsedArgs::Ptr args)
{
    std::cout << get_version(args->GetArgv0()) << std::endl;

    GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);

    // Enable automatic shutdown if the config manager is
    // idling for 1 minute or more.  By idling, it means
    // no configuration files is stored in memory.
    unsigned int idle_wait_min = 3;
    if (args->Present("idle-exit"))
    {
        idle_wait_min = std::atoi(args->GetValue("idle-exit", 0).c_str());
    }

    // Open a log destination, if requested
    std::ofstream logfs;
    std::ostream *logfile = nullptr;
    LogWriter::Ptr logwr = nullptr;
    ColourEngine::Ptr colourengine = nullptr;

    if (args->Present("log-file"))
    {
        std::string fname = args->GetValue("log-file", 0);

        if ("stdout:" != fname)
        {
            logfs.open(fname.c_str(), std::ios_base::app);
            logfile = &logfs;
        }
        else
        {
            logfile = &std::cout;
        }

        if (args->Present("colour"))
        {
            colourengine.reset(new ANSIColours());
            logwr.reset(new ColourStreamWriter(*logfile,
                                               colourengine.get()));
        }
        else
        {
            logwr.reset(new StreamLogWriter(*logfile));
        }
    }
    bool signal_broadcast = args->Present("signal-broadcast");
    DBus dbus(G_BUS_TYPE_SYSTEM);
    dbus.Connect();

    ConfigManagerDBus cfgmgr(dbus.GetConnection(), logwr.get(), signal_broadcast);

    LogServiceProxy::Ptr logsrvprx = nullptr;
    if (!signal_broadcast)
    {
        logsrvprx = LogServiceProxy::AttachInterface(dbus.GetConnection(),
                                                     OpenVPN3DBus_interf_configuration);
    }
    unsigned int log_level = 3;
    if (args->Present("log-level"))
    {
        log_level = std::atoi(args->GetValue("log-level", 0).c_str());
    }
    cfgmgr.SetLogLevel(log_level);

    if (args->Present("state-dir"))
    {
        cfgmgr.SetStateDirectory(args->GetValue("state-dir", 0));
        umask(077);
    }

    IdleCheck::Ptr idle_exit;
    if (idle_wait_min > 0)
    {
        idle_exit.reset(new IdleCheck(main_loop,
                                      std::chrono::minutes(idle_wait_min)));
        cfgmgr.EnableIdleCheck(idle_exit);
    }
    else
    {
        // If we don't use the IdleChecker, handle these signals
        // in via the stop_handler instead
        g_unix_signal_add(SIGINT, stop_handler, main_loop);
        g_unix_signal_add(SIGTERM, stop_handler, main_loop);
    }
    cfgmgr.Setup();

    if (idle_wait_min > 0)
    {
        idle_exit->Enable();
    }
    g_main_loop_run(main_loop);
    g_main_loop_unref(main_loop);

    if (logsrvprx)
    {
        logsrvprx->Detach(OpenVPN3DBus_interf_configuration);
    }

    if (idle_wait_min > 0)
    {
        idle_exit->Disable();
        idle_exit->Join();
    }

    return 0;
}



int main(int argc, char **argv)
{
    SingleCommand argparser(argv[0], "OpenVPN 3 Configuration Manager", config_manager);
    argparser.AddVersionOption();
    argparser.AddOption("log-level",
                        "LOG-LEVEL",
                        true,
                        "Log verbosity level (valid values 0-6, default 3)");
    argparser.AddOption("log-file",
                        "FILE",
                        true,
                        "Write log data to FILE.  Use 'stdout:' for console logging.");
    argparser.AddOption("colour",
                        0,
                        "Make the log lines colourful");
    argparser.AddOption("signal-broadcast",
                        0,
                        "Broadcast all D-Bus signals instead of targeted unicast");
    argparser.AddOption("idle-exit",
                        "MINUTES",
                        true,
                        "How long to wait before exiting if being idle. "
                        "0 disables it (Default: 3 minutes)");
    argparser.AddOption("state-dir",
                        0,
                        "DIRECTORY",
                        true,
                        "Directory where to save persistent data");

    try
    {
        // This program does not require root privileges,
        // so if used - drop those privileges
        drop_root();

        return argparser.RunCommand(simple_basename(argv[0]), argc, argv);
    }
    catch (const LogServiceProxyException &excp)
    {
        std::cout << "** ERROR ** " << excp.what() << std::endl;
        std::cout << "            " << excp.debug_details() << std::endl;
        return 2;
    }
    catch (const CommandArgBaseException &excp)
    {
        std::cout << excp.what() << std::endl;
        return 2;
    }
}
