
#include <stdio.h>

#include <stdlib.h>
#include <unistd.h>
#include <signal.h>


#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include "threadpool.h"
#include "message_processor.h"

#include "message.h"
#include "processor.h"
#include "serialization.h"
#include "sysvipc.h"
#include "responseholder.h"
#include "responseholderlist.h"
#include "command_handlers.h"

struct s_Processor
{
    SysVIPC *ipc;

    int last_sequence;

    ResponseHolderList *responses;
    CommandHandlers *commandHandlers;

    pthread_t incoming_handler_thread;

    bool init_sync;
    pthread_cond_t init_sync_cond;
    pthread_mutex_t init_sync_mutex;
};

static void *processor_incomingHandler (void *arg);

Processor *
processor_new (const key_t ipcKey, bool isClient)
{
    Processor *proc;

    proc = malloc (sizeof (Processor));

    if (isClient == true)
    {
        proc->ipc = sysvipc_new (ipcKey, IPCClient);
    }
    else
    {
        proc->ipc = sysvipc_new (ipcKey, IPCServer);
    }

    if (proc->ipc == NULL)
    {
        free (proc);

        return (NULL);
    }

    proc->last_sequence = 0;
    proc->responses = responseholderlist_new();
    proc->commandHandlers = command_handlers_new();

    proc->init_sync = false;

    pthread_cond_init (&proc->init_sync_cond, NULL);
    pthread_mutex_init (&proc->init_sync_mutex, NULL);

    pthread_create (&proc->incoming_handler_thread, NULL, processor_incomingHandler, proc);

    return (proc);
}

void
processor_destroy (void *ptr)
{
    Processor *proc = (Processor *) ptr;

    pthread_cancel (proc->incoming_handler_thread);
    pthread_join (proc->incoming_handler_thread, NULL);

    sysvipc_destroy (proc->ipc);

    pthread_cond_destroy (&proc->init_sync_cond);
    pthread_mutex_destroy (&proc->init_sync_mutex);

    command_handlers_destroy (proc->commandHandlers);

    responseholderlist_destroy (proc->responses);

    free (proc);
}

static int
processor_getNextSequence (Processor *proc)
{
    int next_seq = ++proc->last_sequence;

    return (next_seq);
}

void
processor_finishedInit (Processor *processor)
{
    pthread_mutex_lock (&processor->init_sync_mutex);

    processor->init_sync = true;

    pthread_cond_broadcast (&processor->init_sync_cond);
    pthread_mutex_unlock (&processor->init_sync_mutex);
}

bool
processor_sendMessageResponse (Processor *proc, Message *msg)
{
    dynTstring *msgString;
    bool ret;

    if (message_getType (msg) != MessageTypeResponse && 
            message_getType (msg) != MessageTypeErrorResponse)
    {
        return (false);
    }

    msgString = serialization_serializeType ("Message", &msg);

    ret = sysvipc_sendData (proc->ipc, msgString);

    dynFfree (msgString);

    return (ret);
}


bool
processor_sendMessage (Processor *proc, Message *msg, Message **receiveMessage)
{
    dynTstring *msgString;
    bool ret;
    int seq;

    if (message_getType (msg) != MessageTypeRequest)
    {
        return (false);
    }

    seq = processor_getNextSequence (proc);

    responseholderlist_addResponseHolder (proc->responses, seq);

    message_setSequence (msg, seq);

    msgString = serialization_serializeType ("Message", &msg);

    ret = sysvipc_sendData (proc->ipc, msgString);

    dynFfree (msgString);

    if (ret == false)
    {
        /* we're leaking ResponseHolders here */

        abort();

        return (false);
    }
    else
    {
        *receiveMessage = responseholderlist_getResponse (proc->responses, seq);

        if (*receiveMessage == NULL)
        {
            return (false);
        }
        else
        {
            return (true);
        }
    }
}

void
processor_addRequestHandler (Processor *processor, const CommandHandler *handler, void *context)
{
    command_handlers_addHandler (processor->commandHandlers, handler, context);
}

static void *
processor_incomingHandler (void *arg)
{
    Processor *proc = (Processor *) arg;

    {
        /* 
         * we'll wait for 5 seconds until someone calls the signal
         */
        struct timespec waitTime = { 0, 0 };

        waitTime.tv_sec = time (0) + 5;

        pthread_mutex_lock (&proc->init_sync_mutex);

        if (proc->init_sync == false)
        {
            pthread_cond_timedwait (&proc->init_sync_cond, 
                    &proc->init_sync_mutex, &waitTime);
        }

        pthread_mutex_unlock (&proc->init_sync_mutex);
    }

    while (true)
    {
        dynTstring *msgString;
        MessageProcessor *msgProc;

        msgString = sysvipc_getData (proc->ipc);

        if (msgString == NULL)
        {
            logFmsg (CONT_LOG_ERROR, "IPC communication failed. Exiting handler.");

            return (NULL);
        }

        /*
         * there is obviously refactoring that needs to happen when we need to
         * pass this much data in.
         */
        msgProc = message_processor_new (msgString, proc->responses, 
                proc->commandHandlers, proc);

        threadpool_queueUserWorkItem (
                threadpool_getInstance(), message_processor_process, msgProc);
    }

    return (NULL);
}
