#include <internal/sighandl.h>

#ifdef __linux__

#include <test.h>

#include <sys/wait.h>
#include <sys/mman.h>

namespace tvision
{

struct SignalHandlerTest : public ::testing::Test
{
    struct Counters
    {
        size_t handlerInvocations;
        size_t callbackEnterInvocations;
        size_t callbackExitInvocations;
        size_t wrapperInvocations;
    };

    // Counters stored in shared memory.
    static Counters *counters;

    SignalHandlerTest() noexcept
    {
        counters = (Counters *) mmap(nullptr, sizeof(Counters), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
        EXPECT_NE(counters, nullptr)
            << "Test bug: 'mmap' failed.";
        *counters = {};
    }

    ~SignalHandlerTest()
    {
        int ret = munmap(counters, sizeof(Counters));
        EXPECT_NE(ret, -1)
            << "Test bug: 'munmap' failed.";

        SignalHandler::disable();
        for (int signo : SignalHandler::handledSignals)
            signal(signo, SIG_DFL);
    }
};

SignalHandlerTest::Counters *SignalHandlerTest::counters;

static bool signalKillsSubprocess(int signo)
{
    auto pid = fork();
    if (pid == 0)
    {
        raise(signo);
        _Exit(0);
    }
    else if (pid > 0)
    {
        int status;
        while (waitpid(pid, &status, 0) != pid);
        if (WIFSIGNALED(status))
            return WTERMSIG(status) == signo;
        return false;
    }
    return false;
}

static void signalHandlerThatDoesNotKillTheProcess(int)
{
    ++SignalHandlerTest::counters->handlerInvocations;
}

static void extendedSignalHandlerThatDoesNotKillTheProcess(int, siginfo_t *, void *)
{
    ++SignalHandlerTest::counters->handlerInvocations;
}

static void signalCallbackThatTemporarilyDisablesSignalHandler(bool enter) noexcept
{
    // Disable and re-enable SignalHandler because this is also what is done in
    // practice by Platform::signalCallback.
    if (enter)
    {
        ++SignalHandlerTest::counters->callbackEnterInvocations;
        SignalHandler::disable();
    }
    else
    {
        ++SignalHandlerTest::counters->callbackExitInvocations;
        SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    }
}

static void signalCallbackThatAborts(bool enter) noexcept
{
    if (enter)
    {
        ++SignalHandlerTest::counters->callbackEnterInvocations;
        abort();
    }
    else
        ++SignalHandlerTest::counters->callbackExitInvocations;
}

static void signalCallbackThatSegfaults(bool enter) noexcept
{
    if (enter)
    {
        ++SignalHandlerTest::counters->callbackEnterInvocations;
        *(volatile int *) nullptr = 0;
    }
    else
        ++SignalHandlerTest::counters->callbackExitInvocations;
}

static void installSignalHandler(int signo, void (*handler)(int)) noexcept
{
    auto ret = signal(signo, handler);
    EXPECT_NE(ret, SIG_ERR)
        << "Test bug: failed to install signal handler.";
}

static void installExtendedSignalHandler(int signo, void (*handler)(int, siginfo_t *, void *)) noexcept
{
    struct sigaction sa {};
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handler;
    int ret = sigaction(signo, &sa, nullptr);
    EXPECT_NE(ret, -1)
        << "Test bug: failed to install extended signal handler.";
}

static struct sigaction wrappedSa;

static void signalWrapper(int signo, siginfo_t *info, void *context)
{
    ++SignalHandlerTest::counters->wrapperInvocations;
    if (wrappedSa.sa_flags & SA_SIGINFO)
        wrappedSa.sa_sigaction(signo, info, context);
    else
        wrappedSa.sa_handler(signo);
}

static void installSignalWrapper(int signo)
{
    struct sigaction sa {};
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = &signalWrapper;
    int ret = sigaction(signo, &sa, &wrappedSa);
    EXPECT_NE(ret, -1)
        << "Test bug: failed to install signal wrapper.";
}

TEST_F(SignalHandlerTest, ShouldOnlyInvokeCallbackOnEnterWhenSignalKillsTheProcess)
{
    SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    ASSERT_TRUE(signalKillsSubprocess(SIGINT));
    ASSERT_EQ(counters->callbackEnterInvocations, 1);
    ASSERT_EQ(counters->callbackExitInvocations, 0);
}

TEST_F(SignalHandlerTest, ShouldInvokeCallbackOnEnterAndExitWhenSignalIsIgnored)
{
    installSignalHandler(SIGINT, SIG_IGN);
    SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    for (int i = 1; i <= 2; ++i)
    {
        ASSERT_NE(raise(SIGINT), -1);
        ASSERT_EQ(counters->callbackEnterInvocations, i);
        ASSERT_EQ(counters->callbackExitInvocations, i);
    }
}

TEST_F(SignalHandlerTest, ShouldFallBackToDefaultHandlerWhenCallbackAborts)
{
    installSignalHandler(SIGABRT, signalHandlerThatDoesNotKillTheProcess);
    SignalHandler::enable(signalCallbackThatAborts);
    ASSERT_TRUE(signalKillsSubprocess(SIGABRT));
    ASSERT_EQ(counters->handlerInvocations, 0);
    ASSERT_EQ(counters->callbackEnterInvocations, 1);
    ASSERT_EQ(counters->callbackExitInvocations, 0);
}

TEST_F(SignalHandlerTest, ShouldFallBackToDefaultHandlerWhenCallbackSegfaults)
{
    installSignalHandler(SIGSEGV, signalHandlerThatDoesNotKillTheProcess);
    SignalHandler::enable(signalCallbackThatSegfaults);
    ASSERT_TRUE(signalKillsSubprocess(SIGSEGV));
    ASSERT_EQ(counters->handlerInvocations, 0);
    ASSERT_EQ(counters->callbackEnterInvocations, 1);
    ASSERT_EQ(counters->callbackExitInvocations, 0);
}

TEST_F(SignalHandlerTest, ShouldInvokeAPreviouslyInstalledHandler)
{
    installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess);
    SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    for (int i = 1; i <= 2; ++i)
    {
        ASSERT_NE(raise(SIGINT), -1);
        ASSERT_EQ(counters->handlerInvocations, i);
        ASSERT_EQ(counters->callbackEnterInvocations, i);
        ASSERT_EQ(counters->callbackExitInvocations, i);
    }
}

TEST_F(SignalHandlerTest, ShouldInvokeAPreviouslyInstalledExtendedHandler)
{
    installExtendedSignalHandler(SIGINT, extendedSignalHandlerThatDoesNotKillTheProcess);
    SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    for (int i = 1; i <= 2; ++i)
    {
        ASSERT_NE(raise(SIGINT), -1);
        ASSERT_EQ(counters->handlerInvocations, i);
        ASSERT_EQ(counters->callbackEnterInvocations, i);
        ASSERT_EQ(counters->callbackExitInvocations, i);
    }
}

TEST_F(SignalHandlerTest, ShouldPreserveACustomHandlerInstalledBetween_enable_And_disable_)
{
    SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess);
    SignalHandler::disable();
    for (int i = 1; i <= 2; ++i)
    {
        ASSERT_NE(raise(SIGINT), -1);
        ASSERT_EQ(counters->handlerInvocations, i);
        ASSERT_EQ(counters->callbackEnterInvocations, 0);
        ASSERT_EQ(counters->callbackExitInvocations, 0);
    }
}

TEST_F(SignalHandlerTest, ShouldPreserveAPreviouslyInstalledHandlerInstalledAfter_enable_)
{
    installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess);
    SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    installSignalWrapper(SIGINT);
    for (int i = 1; i <= 2; ++i)
    {
        ASSERT_NE(raise(SIGINT), -1);
        ASSERT_EQ(counters->handlerInvocations, i);
        ASSERT_EQ(counters->callbackEnterInvocations, i);
        ASSERT_EQ(counters->callbackExitInvocations, i);
        ASSERT_EQ(counters->wrapperInvocations, i);
    }
}

TEST_F(SignalHandlerTest, ShouldNotInvokeNeitherAPreviouslyInstalledHandlerNorTheCallbackIfAWrapperIsInstalledBetween_enable_And_disable_)
{
    installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess);
    SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler);
    installSignalWrapper(SIGINT);
    SignalHandler::disable();
    ASSERT_TRUE(signalKillsSubprocess(SIGINT));
    ASSERT_EQ(counters->handlerInvocations, 0);
    ASSERT_EQ(counters->callbackEnterInvocations, 0);
    ASSERT_EQ(counters->callbackExitInvocations, 0);
    ASSERT_EQ(counters->wrapperInvocations, 1);
}

} // namespace tvision

#endif // __linux__
