#include "PreFork.h"
#include <unistd.h>
#include <cstdlib>
#include <signal.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/types.h>

namespace panda { namespace unievent { namespace http { namespace manager {

struct Shmem {
    struct Shdata {
        std::atomic<uint32_t> active_requests;
        std::atomic<uint32_t> activity_time;
        std::atomic<uint8_t>  load_average;
        std::atomic<uint32_t> total_requests;
        std::atomic<uint32_t> recent_requests;
    };

    void* mapped_mem = nullptr;

    Shdata& shmem () {
        return *(reinterpret_cast<Shdata*>(mapped_mem));
    }

    void unmap_mem () {
        if (!mapped_mem) return;
        auto res = munmap(mapped_mem, sizeof(Shdata));
        if (res) panda_log_critical("could not unmap memory");
        mapped_mem = nullptr;
    }

    ~Shmem () { unmap_mem(); }
};

struct PreForkWorker : Worker, Shmem {
    using Worker::Worker;
    pid_t pid;

    PreForkWorker () {
        mapped_mem = mmap(nullptr, sizeof(Shmem::Shdata), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
        if (mapped_mem == MAP_FAILED) throw exception("could not map shared memory");
        shmem().active_requests = 0;
        shmem().activity_time   = 0;
        shmem().load_average    = 0;
        shmem().total_requests  = 0;
    }

    void fetch_state () override {
        active_requests = shmem().active_requests;
        load_average    = (float)shmem().load_average / 100;
        activity_time   = (time_t)shmem().activity_time;
        total_requests  = shmem().total_requests;
        recent_requests = shmem().recent_requests;
        shmem().recent_requests -= recent_requests;
    }

    void terminate () override {
        panda_log_info("terminating worker pid=" << pid);
        send_signal(SIGINT);
    }

    void kill () override {
        panda_log_info("killing worker pid=" << pid);
        send_signal(SIGKILL);
    }

    void send_signal (int signum) {
        auto res = ::kill(pid, signum);
        if (res == -1) panda_log_critical("could not send signal " << signum << " to worker pid=" << pid);
    }
};

struct PreForkChild : Child, Shmem {
    pid_t    master_pid;
    SignalSP term_signal;

    void init (ServerParams p) override {
        master_pid = getppid();

        Child::init(p);

        term_signal = Signal::create(SIGINT, [this](auto...) { terminate(); }, loop);
        term_signal->weak(true);
    }

    void run () override {
        Child::run();
        panda_log_info("worker terminated");
        server->stop(); // normally it should already be stopped
        std::exit(0);
    }

    void send_active_requests (uint32_t areqs) override {
        shmem().active_requests = areqs;
    }

    void send_activity (time_t now, float la, uint32_t total_requests, uint32_t recent_requests) override {
        if (kill(master_pid, 0) != 0) {
            panda_log_info("master process died, terminating...");
            server->stop();
            std::exit(0);
        }
        shmem().load_average     = la * 100;
        shmem().activity_time    = (uint32_t)now;
        shmem().total_requests   = total_requests;
        shmem().recent_requests += recent_requests;
    }
};

using ChildPtr = std::unique_ptr<Child>;

struct RunChildInOuterScope {
    ChildPtr child;
};

void PreFork::run () {
    sigchld = Signal::create(SIGCHLD, [this](auto...){ handle_sigchld(); }, loop);

    ChildPtr child;
    try {
        Mpm::run();
    }
    catch (RunChildInOuterScope& e) {
        child = std::move(e.child);
    }

    if (child) {
        child->run();
        std::abort(); // unreachable
    }
}

void PreFork::handle_sigchld () {
    pid_t pid;
    int wstatus;
    while ((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) {
        panda_log_info("worker pid=" << pid << " terminated");
        for (auto& row : workers) {
            auto worker = static_cast<PreForkWorker*>(row.second.get());
            if (worker->pid == pid) {
                worker_terminated(worker);
                break;
            }
        }
    }
}

WorkerPtr PreFork::create_worker () {
    auto worker = std::make_unique<PreForkWorker>();

    auto pid = fork();
    if (pid == -1) throw exception("could not fork worker");

    if (pid) {
        worker->pid = pid;
        return WorkerPtr(worker.release());
    }

    // release shared memory for other workers as we forked and cloned all of them
    for (auto& row : workers) {
        auto worker = static_cast<PreForkWorker*>(row.second.get());
        worker->unmap_mem();
    }

    // release manager's resources
    check_timer.reset();
    check_termination_timer.reset();
    sigchld.reset();

    auto child = std::make_unique<PreForkChild>();
    child->mapped_mem = worker->mapped_mem;
    worker->mapped_mem = nullptr;
    child->init({worker_loop, config, server_factory, spawn_event, request_event});

    // we can't run child here because it would be a recursive loop run call.
    // we need to bail out of loop execution and run child from there
    throw RunChildInOuterScope{std::move(child)};
}

void PreFork::stop () {
    Mpm::stop();
}

void PreFork::stopped () {
    Mpm::stopped();
    sigchld.reset();
}

}}}}