/*
// Full copyright information is available in the file ../doc/CREDITS
*/

#define SIG_C

#include "defs.h"

#include <signal.h>
#include <sys/types.h>
#ifdef __UNIX__
#include <sys/wait.h>
#endif
#include "execute.h"            /* task() */
#include "sig.h"

void catch_SIGFPE(int sig);
#ifdef __UNIX__
void catch_SIGCHLD(int sig);
void catch_SIGPIPE(int sig);
#endif

void uninit_sig(void)
{
    signal(SIGFPE, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    signal(SIGINT, SIG_DFL);
    signal(SIGTERM, SIG_DFL);
#ifndef __MSVC__
    signal(SIGUSR1, SIG_DFL);
    signal(SIGUSR2, SIG_DFL);
#endif
#ifdef __UNIX__
    signal(SIGQUIT, SIG_DFL);
    signal(SIGHUP, SIG_DFL);
    signal(SIGCHLD, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
#endif
}

void init_sig(void)
{
    caught_fpe = 0;
    signal(SIGFPE, catch_SIGFPE);
    signal(SIGILL, catch_signal);
    signal(SIGINT, catch_signal);
    signal(SIGTERM, catch_signal);
#ifndef __MSVC__
    signal(SIGUSR1, catch_signal);
    signal(SIGUSR2, catch_signal);
#endif
#ifdef __UNIX__
    signal(SIGQUIT, catch_signal);
    signal(SIGHUP, catch_signal);
    signal(SIGPIPE, catch_SIGPIPE);
    signal(SIGCHLD, catch_SIGCHLD);
#endif
}

#ifdef __UNIX__
void catch_SIGPIPE(int sig)
{
    signal(SIGPIPE, catch_SIGPIPE);
}

void catch_SIGCHLD(int sig)
{
    waitpid(-1, NULL, WNOHANG);
    signal(SIGCHLD, catch_SIGCHLD);
}
#endif

void dump_core_and_exit(void)
{
    uninit_sig();
    abort();
}

void catch_SIGFPE(int sig)
{
    caught_fpe++;
    signal(SIGFPE, catch_SIGFPE);
}

char *sig_name(int sig)
{
    switch (sig) {
    case SIGILL:
        return "ILL";
    case SIGSEGV:
        return "SEGV";
    case SIGINT:
        return "INT";
#ifdef __UNIX__
    case SIGQUIT:
        return "QUIT";
    case SIGHUP:
        return "HUP";
#endif
    case SIGTERM:
        return "TERM";
#ifndef __MSVC__
    case SIGUSR1:
        return "USR1";
    case SIGUSR2:
        return "USR2";
#endif
    default:
        return "Unknown";
    }
    return NULL;
}

/* void catch_signal(int sig, int code, struct sigcontext *scp) { */
void catch_signal(int sig)
{
    char *sptr;
    cStr *sigstr;
    cData arg1;
    Bool do_shutdown = NO;

    signal(sig, catch_signal);

    sptr = sig_name(sig);
    sigstr = string_from_chars(sptr, strlen(sptr));

    write_err("Caught signal %d: %S", sig, sigstr);

    string_discard(sigstr);


    /* figure out what to do */
    switch (sig) {
#ifdef __UNIX__
    case SIGHUP:
        atomic = NO;
        handle_connection_output();
        flush_files();
#endif
#ifndef __MSVC__
    case SIGUSR2:
        /* let the db do what it wants from here */
        break;
    case SIGUSR1:
        {
            cData *d;
            cList *l;

            /* First cancel all preempted and suspended tasks */
            l = vm_list();
            for (d = list_first(l); d; d = list_next(l, d)) {
                /* boggle */
                if (d->type != INTEGER)
                    continue;
                vm_cancel(d->u.val);
            }
            list_discard(l);

            /* now cancel the current task if it is valid */
            if (vm_lookup(task_id) != NULL) {
                vm_cancel(task_id);
            }

            /* jump back to the main loop */
            longjmp(main_jmp, 1);
            break;
        }
#endif
    case SIGILL:
        /* lets panic and hopefully shutdown without frobbing the db */
        panic(sig_name(sig));
        break;
    case SIGTERM:
        if (running) {
            write_err("*** Attempting normal shutdown ***");
            running = NO;

            /* jump back to the main loop, ignore any current tasks;
             *drip*, *drip*, leaky */
            longjmp(main_jmp, 1);
        } else {
            panic(sig_name(sig));
        }
        break;
    default:
        do_shutdown = YES;
        break;
    }

    /* only pass onto the db if we are 'executing' */
    if (!running)
        return;

    /* send a message to the system object */
    arg1.type = SYMBOL;
    arg1.u.symbol = ident_get(sptr);
    vm_task(SYSTEM_OBJNUM, signal_id, 1, &arg1);

    if (do_shutdown)
        running = NO;
}
