#include "async_signals_handler.h"

#include <util/system/platform.h>

#if !defined(_win_)

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>

#include <unistd.h>

#if defined(_linux_)
#include <dlfcn.h>
#endif

#include <library/cpp/deprecated/atomic/atomic.h>
#include <util/system/defaults.h>
#include <util/system/event.h>
#include <util/system/rwlock.h>
#include <util/system/spinlock.h>
#include <util/system/thread.h>
#include <util/system/yassert.h>
#include <util/generic/hash.h>

namespace {
    volatile int SIGNAL_PIPE_WRITE_FD = 0; // will be initialized in ctor

    void WriteAllOrDie(const int fd, const void* buf, size_t bufsize) {
        size_t totalBytesWritten = 0;

        while (totalBytesWritten != bufsize) {
            const ssize_t result = write(fd, (const char*)buf + totalBytesWritten, bufsize - totalBytesWritten);

            Y_ABORT_UNLESS(result >= 0 || (result == -1 && errno == EINTR), "write failed: %s (errno = %d)", strerror(errno), errno);
            totalBytesWritten += static_cast<size_t>(result);
        }
    }

    void PipeWriterSignalHandler(int, siginfo_t* info, void*) {
        const ui8 signum = static_cast<ui8>(info->si_signo);

        WriteAllOrDie(SIGNAL_PIPE_WRITE_FD, &signum, 1);
    }

    // Handler for the "asynchronous" unix signals (those which can occur
    // at arbitrary point of execution and have no need to be reacted on instantly
    // and/or to preserve execution context at the point of interrupt).
    //
    // Async signals -- SIGHUP, SIGUSR1 (used to cause configuration files reread for example)
    // Sync signals -- fatal errors like SIGSEGV, SIGBUS...
    class TAsyncSignalsHandler {
    private:
        TThread Thread;
        int SignalPipeReadFd;
        typedef THolder<TEventHandler> TEventHandlerPtr;
        THashMap<int, TEventHandlerPtr> Handlers;
        TRWMutex HandlersLock;

        TAtomic ShouldDie;
        TSystemEvent DieEvent;

        static void* ThreadFunc(void* data) {
            reinterpret_cast<TAsyncSignalsHandler*>(data)->RealThreadFunc();

            return nullptr;
        }

        inline void RealThreadFunc() {
            for (;;) {
                ui8 signum;
                const ssize_t bytesRead = read(SignalPipeReadFd, &signum, 1);

                Y_ABORT_UNLESS(bytesRead >= 0 || (bytesRead == -1 && errno == EINTR), "read failed: %s (errno = %d)", strerror(errno), errno);

                if (AtomicAdd(ShouldDie, 0) != 0) {
                    DieEvent.Signal();

                    break;
                }

                if (bytesRead == 0) {
                    break;
                } else if (bytesRead == -1) {
                    continue;
                }

                {
                    TReadGuard dnd(HandlersLock);

                    const TEventHandlerPtr* handler = Handlers.FindPtr(signum);
                    Y_ABORT_UNLESS(handler && handler->Get(), "Async signal handler is not set, it's a bug!");
                    handler->Get()->Handle(signum);
                }
            }
        }

    public:
        TAsyncSignalsHandler()
            : Thread(TThread::TParams(ThreadFunc, this).SetName("sighandler"))
            , SignalPipeReadFd(0)
            , ShouldDie(0)
        {
            int filedes[2] = {-1};

#ifdef _linux_
            int result;

            {
                using pipe2_t = decltype(pipe2);
                pipe2_t* pipe2Ptr = (pipe2_t*)dlsym(RTLD_DEFAULT, "pipe2");

#if defined(_musl_)
                if (!pipe2Ptr) {
                    pipe2Ptr = pipe2;
                }
#endif

                if (pipe2Ptr) {
                    result = pipe2Ptr(filedes, O_CLOEXEC);
                } else {
                    result = -1;
                    errno = ENOSYS;
                }
            }

            if (result != 0 && errno == ENOSYS) { // linux older than 2.6.27 returns "not implemented"
#endif
                Y_ABORT_UNLESS(pipe(filedes) == 0, "pipe failed: %s (errno = %d)", strerror(errno), errno);

                SignalPipeReadFd = filedes[0];
                SIGNAL_PIPE_WRITE_FD = filedes[1];

                Y_ABORT_UNLESS(fcntl(SignalPipeReadFd, F_SETFD, FD_CLOEXEC) == 0, "fcntl failed: %s (errno = %d)", strerror(errno), errno);
                Y_ABORT_UNLESS(fcntl(SIGNAL_PIPE_WRITE_FD, F_SETFD, FD_CLOEXEC) == 0, "fcntl failed: %s (errno = %d)", strerror(errno), errno);
#ifdef _linux_
            } else {
                Y_ABORT_UNLESS(result == 0, "pipe2 failed: %s (errno = %d)", strerror(errno), errno);
                SignalPipeReadFd = filedes[0];
                SIGNAL_PIPE_WRITE_FD = filedes[1];
            }
#endif

            Thread.Start();
            Thread.Detach();
        }

        ~TAsyncSignalsHandler() {
            AtomicSwap(&ShouldDie, TAtomic(1));
            ui8 fakeSignal = 0;
            WriteAllOrDie(SIGNAL_PIPE_WRITE_FD, &fakeSignal, 1);

            DieEvent.WaitT(TDuration::Seconds(15));

            /* may cause VERIFY failure in signal handler, propably we should leave it to process clean procedure
        close(SIGNAL_PIPE_WRITE_FD);
        close(SignalPipeReadFd);
*/
        }

        bool DoInstall(int signum, THolder<TEventHandler> handler) {
            TWriteGuard dnd(HandlersLock);
            TEventHandlerPtr& ev = Handlers[signum];
            const bool ret = !ev;

            ev = std::move(handler);

            return ret;
        }

        void Install(int signum, THolder<TEventHandler> handler) {
            if (DoInstall(signum, std::move(handler))) {
                struct sigaction a;

                memset(&a, 0, sizeof(a));
                a.sa_sigaction = PipeWriterSignalHandler;
                a.sa_flags = SA_SIGINFO | SA_RESTART;

                Y_ABORT_UNLESS(!sigaction(signum, &a, nullptr), "sigaction failed: %s (errno = %d)", strerror(errno), errno);
            }
        }
    };

    // This pointer is never deleted - yeah, it's intended memory leak.
    // It is necessary to prevent problems when user's signal handler calls exit function
    // which destroys all global variables including this one.
    // It such situation we have 2 options:
    //  - wait for auxiliary thread to die - which will cause dead lock
    //  - destruct variable, ignoring thread - which will cause data corruption.
    TAsyncSignalsHandler* SIGNALS_HANDLER = nullptr;
}

void SetAsyncSignalHandler(int signum, THolder<TEventHandler> handler) {
    static TAdaptiveLock lock;

    if (Y_UNLIKELY(SIGNALS_HANDLER == nullptr)) {
        TGuard dnd(lock);

        if (SIGNALS_HANDLER == nullptr) {
            // NEVERS GETS DESTROYED
            SIGNALS_HANDLER = new TAsyncSignalsHandler();
        }
    }

    SIGNALS_HANDLER->Install(signum, std::move(handler));
}

#else //_win_

void SetAsyncSignalHandler(int, THolder<TEventHandler>) {
    // TODO: it's really easy to port using _pipe, _read and _write, but it must be tested properly.
}

#endif

namespace {
    template <typename TFunc>
    class TFunctionEventHandler: public TEventHandler {
        TFunc Func;

    public:
        TFunctionEventHandler(TFunc func) {
            if (func)
                Func = func;
        }

        int Handle(int signum) override {
            if (Func) {
                Func(signum);
            }

            return 0;
        }
    };
}

void SetAsyncSignalHandler(int signum, void (*handler)(int)) {
    SetAsyncSignalHandler(signum, MakeHolder<TFunctionEventHandler<void (*)(int)>>(handler));
}

void SetAsyncSignalFunction(int signum, std::function<void(int)> func) {
    typedef std::function<void(int)> TFunc;
    SetAsyncSignalHandler(signum, MakeHolder<TFunctionEventHandler<TFunc>>(func));
}