#pragma once

#include "controller.h"

#include <saas/library/daemon_base/config/options.h>

#include <saas/util/system/signal_handler.h>
#include <saas/util/logging/messages.h>

#include <util/generic/noncopyable.h>
#include <util/generic/ptr.h>
#include <util/generic/singleton.h>
#include <util/generic/string.h>
#include <util/generic/yexception.h>
#include <util/stream/output.h>
#include <util/system/daemon.h>
#include <util/system/guard.h>
#include <util/system/mutex.h>

void CreatePidFile(const TString& pidFile);

template <class TServer, class Controller=TController, class ServerDescriptor = TServerDescriptor<TServer> >
class TDaemon : TNonCopyable {
private:
    typedef Controller TControllerClass;
    typedef ServerDescriptor TServerDescriptorClass;
    typedef TDaemon<TServer, TControllerClass> TSelf;
    Y_DECLARE_SINGLETON_FRIEND()

private:
    THolder<TControllerClass> Server;
    TAutoPtr<NUtil::TSigHandlerThread> SigHandlerThread;
    bool Stopped = false;
    static TMutex Mutex;

private:
    void StopServer(int signal) {
        TGuard<TMutex> guard(Mutex);
        if (!Stopped) {
            Stopped = true;
            INFO_LOG << "Signal caught, stopping server" << Endl;
            Server->Stop(((signal == SIGINT) || (signal == SIGTERM)) ? Max<ui32>() : 0);
        } else {
            INFO_LOG << "Server stop already is in progress, ignoring signal" << Endl;
        }
    }

private:
    static void StopServerStatic(int signal) {
        Singleton<TDaemon<TServer, TControllerClass> >()->StopServer(signal);
    }

    static void LogDisconnection(int /*signal*/) {
        WARNING_LOG << "SIGPIPE handled. Client disconnected." << Endl;
    }

    static void ReopenLog(int) {
        DEBUG_LOG << "start loggers reopen...";
        Singleton<TDaemon<TServer, TControllerClass> >()->Server->GetConfig()->GetDaemonConfig().ReopenLog();
        TLogBackend::ReopenAllBackends(false);
        TMessageReopenLogs messReopen;
        SendGlobalMessage(messReopen);
        DEBUG_LOG << "loggers reopen finished";
    }

public:
    class IAdditionOptionsParser {
    public:
        virtual TString GetUsage() const {
            return TString();
        };
        virtual TString GetAdditionOptionsString() = 0;
        virtual bool ParseOption(int optlet, const TString& arg, TConfigPatcher& preprocessor) = 0;
        virtual void OnBeforeParsing(TConfigPatcher& /*preprocessor*/) {}
        virtual ~IAdditionOptionsParser() {}
    };

    int Run(int argc, char* argv[]) {
        InitGlobalLog2Console(TLOG_DEBUG);
        try {
            TDaemonOptions options;
            options.Parse(argc, argv);
            TString configText = options.RunPreprocessor();
            TDaemonConfig daemonConfig(configText.data(), true);
            options.GetPreprocessor().SetStrict();
            INFO_LOG << "Daemon config parsed" << Endl;
            if (!options.GetValidateOnly() && daemonConfig.StartAsDaemon()) {
                NDaemonMaker::MakeMeDaemon(NDaemonMaker::closeStdIoOnly, NDaemonMaker::openNone, NDaemonMaker::chdirNone);
                daemonConfig.ReopenLog();
                CreatePidFile(daemonConfig.GetPidFileName());
            }
            TServerConfigConstructorParams configParams(options, configText);
            INFO_LOG << "Original config: " << Endl << configText << Endl << Endl;
            if (options.GetValidateOnly()) {
                return EXIT_SUCCESS;
            }
            TServerDescriptorClass sd;
            Server = MakeHolder<TControllerClass>(configParams, sd);
            // start signal handling on a dedicated thread
            {
                TVector<NUtil::TSigHandler> sigHandlers;
                sigHandlers.push_back(NUtil::TSigHandler(SIGINT,  StopServerStatic));
                sigHandlers.push_back(NUtil::TSigHandler(SIGTERM, StopServerStatic));
                sigHandlers.push_back(NUtil::TSigHandler(SIGUSR1, StopServerStatic));
                sigHandlers.push_back(NUtil::TSigHandler(SIGHUP, ReopenLog));
                sigHandlers.push_back(NUtil::TSigHandler(SIGPIPE, LogDisconnection));
                SigHandlerThread.Reset(new NUtil::TSigHandlerThread(sigHandlers));
            }
            SigHandlerThread->DelegateSignals();
            INFO_LOG << "Starting server" << Endl;
            Server->Run();
            Server->WaitStopped();
            // Stop signal handling
            SigHandlerThread->Stop();
            SigHandlerThread.Reset(nullptr);
            TGuard<TMutex> guard(Mutex);
            Server.Destroy();
            NMessenger::TGlobalMediator::CheckList();
            NMessenger::TGlobalMediator::CheckEmpty();
            INFO_LOG << "Server stopped" << Endl;
            return EXIT_SUCCESS;
        } catch (const yexception& exc) {
            FATAL_LOG << "Daemon running failed: " << exc.what() << Endl;
            return EXIT_FAILURE;
        }
    }
};

template <class TServer, class Controller, class ServerDescriptor>
TMutex TDaemon<TServer, Controller, ServerDescriptor>::Mutex;
