
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include "mecha.h"

#include "stringdictionary.h"
#include "http_request.h"
#include "message.h"
#include "processor.h"

#include "http.h"

#include "serialization.h"

int g_pid = -1;
int g_sequence = 1;
pthread_mutex_t g_sequence_mutex = PTHREAD_MUTEX_INITIALIZER;

static int http_request_getPid (void);
static int http_request_getNextSequence (void);

struct s_HttpRequest
{
    dynTstring *token;

    httpTtrans *trans;

    pthread_cond_t cond;
    pthread_mutex_t mutex;

    bool hasStarted;

    bool hasResponse;

    int response;

    Processor *processor;
};

static int
http_request_getPid()
{
    if (g_pid == -1)
    {
        g_pid = getpid();
    }

    return (g_pid);
}

static int
http_request_getNextSequence()
{
    int ret;

    pthread_mutex_lock (&g_sequence_mutex);

    ret = ++g_sequence;

    pthread_mutex_unlock (&g_sequence_mutex);

    return (ret);
}

HttpRequest *
http_request_new (httpTtrans *trans, Processor *proc)
{
    HttpRequest *resp = malloc (sizeof (HttpRequest));

    resp->token = dynFinit();

    dynFappend_print (resp->token, "0x%x-0x%x", http_request_getPid(), http_request_getNextSequence());

    resp->trans = trans;

    pthread_cond_init (&resp->cond, NULL);
    pthread_mutex_init (&resp->mutex, NULL);

    resp->hasResponse = false;
    resp->hasStarted = false;

    resp->response = 0;
    resp->processor = proc;

    httpFset_status (resp->trans, 200, "OK OK");

    return (resp);
}

void
http_request_destroy (void *ptr)
{
    HttpRequest *resp = (HttpRequest *) ptr;

    dynFfree (resp->token);

    pthread_cond_destroy (&resp->cond);
    pthread_mutex_destroy (&resp->mutex);

    free (resp);
}

const char *
http_request_getToken (HttpRequest *resp)
{
    return (dynFgetstr (resp->token));
}

httpTtrans *
http_request_getHttpTrans (HttpRequest *resp)
{
    return (resp->trans);
}

static bool
http_request_pingRemote (HttpRequest *request)
{
    bool ret;
    Message *responseMessage;
    Message *startMessage = message_newRequest ("HttpRequestPing");

    message_setParameter (startMessage, "token",
            dynFgetstr (request->token));

    ret = processor_sendMessage (request->processor, startMessage, &responseMessage);

    if (ret == false)
    {
        logFmsg (CONT_LOG_ERROR, "Could not send message.");
    }
    else 
    {
        if (message_getType (responseMessage) == MessageTypeErrorResponse)
        {
            logFmsg (CONT_LOG_ERROR, "Could not find token %s - %s", dynFgetstr (request->token),
                    message_getParameter (responseMessage, "message"));

            ret = false;
        }
        else
        {
            ret = true;
        }

        message_destroy (responseMessage);
    }

    message_destroy (startMessage);

    return (ret);
}

void
http_request_setResponseHeaders (HttpRequest *request, 
        StringDictionary *responseHeaders)
{
    hshTiterator *iter = stringdictionary_getIterator (responseHeaders);

    while (hshFiterator_next (iter) == 1)
    {
        const char *key = (const char *) hshFiterator_current_key (iter);
        const dynTstring *value = (const dynTstring *) hshFiterator_current_value (iter);

        lstFset_update (request->trans->res_hdrs, key, dynFgetstr (value));
    }

    hshFiterator_free (iter);
}

void
http_request_setStatus (HttpRequest *request, int status, const char *reason)
{
    httpFset_status (request->trans, status, reason);
}

bool
http_request_write (HttpRequest *request, dynTstring *string)
{
    if (request->hasStarted == false)
    {
        httpFstart_response (request->trans);

        request->hasStarted = true;
    }

    /* If we didn't have any data to send but want to send our headers */
    if (dynFgetlen (string) == 0)
    {
        return (true);
    }

    if (httpFwrite (request->trans, dynFgetstr (string), dynFgetlen (string)) == -1)
    {
        http_request_setResponse (request, STATUS_ERROR);
        
        return (false);
    }
    else
    {
        return (true);
    }
}

int
http_request_getResponse (HttpRequest *resp)
{
    int response;

    pthread_mutex_lock (&resp->mutex);

    while (resp->hasResponse == false)
    {
        struct timespec waitTime = { 0, 0 };

        waitTime.tv_sec = time(0) + 15;

        if (pthread_cond_timedwait (&resp->cond, &resp->mutex, &waitTime) == ETIMEDOUT)
        {
            if (http_request_pingRemote (resp) == false)
            {
                resp->response = STATUS_ERROR;
                resp->hasResponse = true;

                logFmsg (CONT_LOG_DEBUG, "request no longer active: %s", dynFgetstr (resp->token));
            }
            else
            {
                logFmsg (CONT_LOG_DEBUG, "request still active: %s", dynFgetstr (resp->token));
            }
        }
    }

    response = resp->response;

    pthread_mutex_unlock (&resp->mutex);

    return (response);
}

void
http_request_setResponse (HttpRequest *resp, int response)
{
    pthread_mutex_lock (&resp->mutex);

    resp->hasResponse = true;
    resp->response = response;

    pthread_mutex_unlock (&resp->mutex);

    pthread_cond_signal (&resp->cond);
}

static void
http_request_serializeListSet (Message *message, const char *entryName, lstTset *list)
{
    int i;
    StringDictionary *returnList = stringdictionary_new();

    for (i = 0; i < lstFset_size (list); i++)
    {
        dynTstring *value_str;
        const char *key;
        const char *value;
        int value_len;

        key = lstFset_get_key_index (list, i);
        value = lstFset_get_index (list, i);

        if (value != NULL)
        {
            value_len = strlen (value);

            value_str = dynFinit();
            dynFappend (value_str, value, value_len);

            /*
               dynFappend (value_str, lstFset_get_index (list, i),
               lstFset_get_index (list, i));
               */

            stringdictionary_setValue (returnList, key, value_str);
        }
    }

    {
        dynTstring *ser = serialization_serializeType ("StringDictionary", &returnList);

        message_setParameterBinary (message, entryName, dynFgetstr (ser), dynFgetlen (ser));

        dynFfree (ser);
    }

    stringdictionary_destroy (returnList);
}

static Message *
http_request_createRequestStartMessage (HttpRequest *resp)
{
    char ipBuf[20];

    Message *startMessage = message_newRequest ("HttpRequestStart");

    message_setParameter (startMessage, "token",
            dynFgetstr (resp->token));

    utlFip_to_str (resp->trans->cli_ipv4_addr, ipBuf, sizeof (ipBuf));

    message_setParameter (startMessage, "remoteIP", ipBuf);

    /* serialize all transaction variables */
    http_request_serializeListSet (startMessage, "variables", 
            resp->trans->vars);

    /* serialize all request headers */
    http_request_serializeListSet (startMessage, "headers", 
            resp->trans->req_hdrs);

    return (startMessage);
}

bool
http_request_sendRequestStart (HttpRequest *resp)
{
    Message *message;
    Message *responseMessage;
    bool ret;

    message = http_request_createRequestStartMessage (resp);

    ret = processor_sendMessage (resp->processor, message, &responseMessage);

    if (ret == false)
    {
        logFmsg (CONT_LOG_ERROR, "Could not send message.");
    }
    else 
    {
        if (message_getType (responseMessage) == MessageTypeErrorResponse)
        {
            logFmsg (CONT_LOG_ERROR, "Error was returned.");

            message_print (responseMessage);

            ret = false;
        }
        else
        {
            ret = true;
        }

        message_destroy (responseMessage);
    }

    message_destroy (message);

    return (ret);
}

