#include "certdd.hpp"
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>


typedef struct {
    pid_t pid;
    unsigned spawn_count;
    time_t spawn_count_since;
} child_t;
static child_t* childs = NULL;
static int childnum = 0;


static void signal_broadcast(int sig) {
    for (int i=0; i< childnum; ++i) {
        if (childs[i].pid > 0) {
            (void)kill(childs[i].pid, sig);
        }
    }
}


static pid_t spawn(int i, int argc, char** argv) {
    pid_t pid = fork();
    if (pid == -1) {
        STDERR("cannot fork: %d - %s", errno, strerror(errno));
    } else if (pid != 0) {
        STDERR(" -> %d", pid);
    } else {
        ++argv;
        --argc;
        static const char* certd = "./certd";
        argv[0] = (char*)certd;
        char suff[64];
        (void)sprintf(suff, ".%d", i);
        strcat(argv[argc-1], suff);
        (void)execvp(argv[0], argv);
        exit(1);
    }
    return pid;
}


int main(int argc, char** argv) {
    if (argc < 3) {
        STDERR("usage: %s num certd-args...", argv[0]);
        STDERR("       the last argument gets appended a .#");
        return 1;
    }

    childnum = atoi(argv[1]);
    if (childnum <= 0) {
        STDERR("invalid number of processes to spawn: %d", childnum);
        return 1;
    }
    childs = (child_t*)calloc(childnum, sizeof(child_t));

    static struct sigaction sa = {};
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sa, NULL);
    sa.sa_handler = &signal_broadcast;
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGHUP, &sa, NULL);
    sigaction(SIGUSR1, &sa, NULL);

    STDERR("spawning %d processes...", childnum);
    for (int i=0; i<childnum; ++i) {
        childs[i].pid = spawn(i, argc, argv);
        childs[i].spawn_count = 1;
        childs[i].spawn_count_since = time(NULL);
    }

    while (true) {
        int status;
        pid_t pid = wait(&status);
        if (pid == -1) {
            if (errno == ECHILD) {
                STDERR("no children to wait for");
                break;
            }
            continue; // ignore other errors, i.e. EINTR
        }

        unsigned left = 0;
        for (int i=0; i<childnum; ++i) {
            if (childs[i].pid == pid) {
                if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
                    STDERR("%d exited successfully", pid);
                    childs[i].pid = 0;
                } else {
                    time_t now = time(NULL);
                    if (childs[i].spawn_count_since < now-10) {
                        childs[i].spawn_count_since = now;
                        childs[i].spawn_count = 0;
                    }
                    if (childs[i].spawn_count >= 3) {
                        STDERR("%d exited w/ %d - NOT respawning", pid, status);
                        childs[i].pid = 0;
                    } else {
                        STDERR("%d exited w/ %d - respawning", pid, status);
                        childs[i].spawn_count++;
                        childs[i].pid = spawn(i, argc, argv);
                    }
                }
            }
            if (childs[i].pid > 0) {
                left++;
            }
        }
        if (!left) {
            STDERR("no children left");
            break;
        }
    }

    return 0;
}