#include "init.h"

#include "tile/base/chrono.h"
#include "tile/base/config.h"
#include "tile/base/internal/background_task_host.h"
#include "tile/base/internal/time_keeper.h"
#include "tile/base/internal/utility.h"
#include "tile/base/logging.h"
#include "tile/base/logging/console_sink.h"
#include "tile/base/option.h"
#include "tile/base/thread/latch.h"
#include "tile/init/on_init.h"
#include "tile/init/override_flag.h"
#include "tile/net/internal/http_engine.h"

#include "gflags/gflags.h"
// #include "glog/logging.h"
// #include "glog/raw_logging.h"

#include <condition_variable>
#include <functional>
#include <mutex>
#include <sys/signal.h>

DEFINE_bool(tile_abort_on_double_quit_signal,
            true,
            "If set, flare aborts the whole program when a second SIGQUIT (or "
            "SIGINT) is received. This helps when the program cannot exit "
            "cleanly on the first signal.");

namespace tile {
namespace {

std::atomic<bool> g_quit_siganl{false};
std::atomic<bool> g_tile_running{false};

void
QuitSignalHandler(int sig)
{
    auto old = g_quit_siganl.exchange(true, std::memory_order_relaxed);
    if (old && FLAGS_tile_abort_on_double_quit_signal) {
        // TODO: Add TILE_RAW_LOG
        // RAW_LOG(FATAL, "Double quit signal received. Crashing the program");
    }
}

void
InstallQuitSignalHandler()
{
    static std::once_flag flag;
    std::call_once(flag, [&] {
        TILE_PCHECK(signal(SIGINT, QuitSignalHandler) != SIG_ERR);
        TILE_PCHECK(signal(SIGQUIT, QuitSignalHandler) != SIG_ERR);
        TILE_PCHECK(signal(SIGTERM, QuitSignalHandler) != SIG_ERR);
    });
}
}// namespace

int
Start(int argc, char **argv, std::function<int(int, char **)> cb, bool single_thread, bool enable_crash_catch)
{
    {
        bool expected = false;
        TILE_CHECK(g_tile_running.compare_exchange_strong(expected, true), "tile already running.");
    }

    if (enable_crash_catch) {
        // TODO: Add InstallFailureSignalHandler
        // google::InstallFailureSignalHandler();
    }

    // Init gflags
    gflags::SetVersionString(TILE_VERSION);
    gflags::ParseCommandLineFlags(&argc, &argv, true);
    detail::ApplyFlagOverrider();

    AddLogSink(ConsoleSink::Create());

    // TODO: Add Init log level
    // google::InitGoogleLogging(argv[0]);

    TILE_LOG_INFO("Tile started. version: {}", TILE_VERSION);

    TILE_PCHECK(signal(SIGPIPE, SIG_IGN) != SIG_ERR);

    // Init BasicRuntime
    InitializeBasicRuntime();

    // Run all initializers
    detail::RunAllInitializers();

    int rc = 0;

    Latch latch(1);
    auto runner = [&] {
        option::InitializeOptions();
        rc = cb(argc, argv);
        internal::HttpEngine::Stop();
        internal::HttpEngine::Join();
        option::ShutdownOptions();

        latch.CountDown();
    };

    if (single_thread) {
        runner();
    } else {
        std::thread worker(runner);
        latch.Wait();
        worker.join();
    }

    detail::RunAllFinalizers();
    TerminateBasicRuntime();
    gflags::ShutDownCommandLineFlags();
    TILE_LOG_INFO("Exited");

    return rc;
}

void
InitializeBasicRuntime()
{
    detail::chrono::CoarseClockInitializer::Instance()->Start();
    internal::BackgroundTaskHost::Instance()->Start();
    internal::TimeKeeper::Instance()->Start();
}

void
TerminateBasicRuntime()
{
    internal::TimeKeeper::Instance()->Stop();
    internal::TimeKeeper::Instance()->Join();
    internal::BackgroundTaskHost::Instance()->Stop();
    internal::BackgroundTaskHost::Instance()->Join();
    detail::chrono::CoarseClockInitializer::Instance()->Stop();
    detail::chrono::CoarseClockInitializer::Instance()->Join();
}

std::mutex init_lock;
std::condition_variable init_cv;
std::thread *lib_thread;

void
Init(int argc, char **argv, std::function<int(int, char **)> cb)
{
    std::unique_lock<std::mutex> _(init_lock);
    TILE_CHECK(g_tile_running.load() == false, "tile already running.");
    assert(lib_thread && "ReInitialize!!!");
    const char *my_argv[1] = {"unknown"};

    if (!argv) {
        argc = 1;
        argv = (char **) my_argv;
    }

    if (!cb) {
        cb = [](int argc, char **) {
            std::unique_lock<std::mutex> _(init_lock);
            init_cv.wait(_);
            return 0;
        };
    }

    lib_thread = new std::thread([=] { ::tile::Start(argc, argv, cb); });
}

void
Uninit()
{
    std::thread *work_thread = nullptr;
    {
        std::unique_lock<std::mutex> _(init_lock);
        assert(!lib_thread && "Please Initialize it!!!");
        init_cv.notify_all();
        std::swap(work_thread, lib_thread);
    }
    work_thread->join();
}

}// namespace tile
