
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

#include "processor.h"
#include "mobility.h"
#include "mobilitytable.h"

#include "http_request.h"
#include "http_request_list.h"

/* #include "capi.h" */
#include "http.h"

#include "http_message_handler.h"

struct s_Mobility
{
    char *executable;
    key_t ipcKey;
    Processor *processor;

    HttpRequestList *requestList;
    int pid;

    pthread_rwlock_t pid_lock;
};

static pthread_once_t g_setupChildWatcher_once_control = PTHREAD_ONCE_INIT;

static void mobility_startChildProcess (Mobility *mob);

Mobility *
mobility_new (const char *executable)
{
    Mobility *mob;

    mob = malloc (sizeof (Mobility));

    mob->requestList = http_request_list_new();
    mob->executable = strdup (executable);
    mob->pid = -1;
    mob->ipcKey = rand();
    pthread_rwlock_init (&mob->pid_lock, NULL);

    /* start up the processor as a server */
    mob->processor = processor_new (mob->ipcKey, false);

    processor_addRequestHandler (mob->processor, &command_HttpWrite, mob);
    processor_addRequestHandler (mob->processor, &command_HttpFinish, mob);
    processor_addRequestHandler (mob->processor, &command_HttpReadEntityBody, mob);

    processor_finishedInit (mob->processor);

    mobility_startChildProcess (mob);

    return (mob);
}

void
mobility_destroy (void *ptr)
{
    Mobility *mob = (Mobility *) ptr;

    http_request_list_destroy (mob->requestList);

    if (mob->pid != -1)
    {
        kill (mob->pid, SIGHUP);
    }

    free (mob->executable);

    free (mob);
}

int
mobility_getPid (Mobility *mob)
{
    return (mob->pid);
}

static const char *
mobility_childExitReason (int si_code)
{
    switch (si_code)
    {
        case CLD_EXITED:
            return ("exited");
            break;
        case CLD_KILLED:
            return ("was killed");
            break;
        case CLD_DUMPED:
            return ("terminated abnormally");
            break;
        default:
            return ("exited for an unknown reason");
            break;
    }
}

static void
mobility_sigChildHandler (int signum MECHA_UNUSED, siginfo_t *siginfo, void *data MECHA_UNUSED)
{
    Mobility *mob = mobilitytable_getMobilityEntryByPid (
            mobilitytable_getTable(), siginfo->si_pid);

    waitpid (siginfo->si_pid, NULL, WNOHANG);

    if (mob == NULL)
    {
        logFmsg (CONT_LOG_WARN, "Recieved termination of unknown child process: %d",
                siginfo->si_pid);

        return;
    }

    logFmsg (CONT_LOG_DEBUG, "Causality process (%d) %s.", siginfo->si_pid,
            mobility_childExitReason (siginfo->si_code));

    mob->pid = -1;
}

static void
mobility_setupSigChildWatcher (void)
{
    struct sigaction action;

    action.sa_handler = NULL;
    action.sa_sigaction = mobility_sigChildHandler;
    action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
#ifdef LINUX
    action.sa_restorer = NULL;
#endif

    sigemptyset (&action.sa_mask);

    if (sigaction (SIGCHLD, &action, NULL) != 0)
    {
        int e = errno;

        logFmsg (CONT_LOG_ERROR, "Could not install signal handler for SIGCHLD: %s", 
                strerror (e));
    }
}

static void
mobility_startChildProcess (Mobility *mob)
{
    int pid;
    char commandLine[4096];
    const char *argv[4];

    pthread_once (&g_setupChildWatcher_once_control, mobility_setupSigChildWatcher);

    pthread_rwlock_wrlock (&mob->pid_lock);

    if (mob->pid != -1)
    {
        /* we already got started by another thread */

        pthread_rwlock_unlock (&mob->pid_lock);

        return;
    }

    snprintf (commandLine, sizeof (commandLine), "%s %u", mob->executable, mob->ipcKey);

    argv[0] = "/bin/sh";
    argv[1] = "-c";
    argv[2] = commandLine;
    argv[3] = NULL;

    if ((pid = fork()) == 0)
    {
        /* child */

        execv (argv[0], (char *const *) argv);

        /* if we are still here.. we failed. */

        perror ("execv");

        exit (127);
    }
    else
    {
        /* parent */

        mob->pid = pid;

        logFmsg (CONT_LOG_WARN, "%s executing with pid %d", mob->executable, mob->pid);

        pthread_rwlock_unlock (&mob->pid_lock);
    }
}

static void
mobility_checkChildProcessStatus (Mobility *mob)
{
    bool startChild = false;

    pthread_rwlock_rdlock (&mob->pid_lock);

    if (mob->pid == -1)
    {
        startChild = true;
    }

    pthread_rwlock_unlock (&mob->pid_lock);

    if (startChild == true)
    {
        mobility_startChildProcess (mob);
    }
}

HttpRequest *
mobility_getRequest (Mobility *mobility, const char *token)
{
    return (http_request_list_getRequest (mobility->requestList, token));
}

dynTstring 
*mobility_getRequestDiag (Mobility *mobility)
{
    return (http_request_list_getDiag (mobility->requestList));
}

int
mobility_sendRequest (Mobility *mob, httpTtrans *trans)
{
    HttpRequest *req = http_request_new (trans, mob->processor);
    int response;

    /* spawn a new client process if needed */
    mobility_checkChildProcessStatus (mob);

    http_request_list_addRequest (mob->requestList, req);

    if (http_request_sendRequestStart (req) == false)
    {
        logFmsg (CONT_LOG_WARN, "Could not process request.");
        response = STATUS_PROCEED;
    }
    else
    {
        response = http_request_getResponse (req);
    }

    /* this should free the request object */
    http_request_list_removeRequest (mob->requestList, 
            http_request_getToken (req));

    return (response);
}

