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

#include <assert.h>

#include <mecha.h>

#include "http_request.h"
#include "http_response.h"
#include "stringdictionary.h"
#include "serialization.h"

struct s_HttpRequest
{
    dynTstring *token;
    dynTstring *remoteIP;

    bool entityRequested;
    unsigned int entityReadOffset;
    dynTstring *entity;

    Processor *processor;

    StringDictionary *requestHeaders;

    StringDictionary *transactionVariables;

    int responseStatus;
    dynTstring *responseReason;

    bool sentResponseHeaders;
    StringDictionary *responseHeaders;
};

static bool http_request_setIfNotNull (dynTstring *dest, const dynTstring *src);
static const char *http_request_getInternalString (dynTstring *string);

HttpRequest *
http_request_new (Message *requestMessage, Processor *processor)
{
    HttpRequest *request = malloc (sizeof (HttpRequest));

    assert (requestMessage != NULL);
    assert (processor != NULL);

    request->token = dynFinit();
    request->remoteIP = dynFinit();

    request->entityRequested = false;
    request->entityReadOffset = 0;
    request->entity = dynFinit();

    request->processor = processor;

    http_request_setIfNotNull (request->token, 
            message_getParameterString (requestMessage, "token"));

    http_request_setIfNotNull (request->remoteIP, 
            message_getParameterString (requestMessage, "remoteIP"));

    {
        dynTstring *value = message_getParameterString (requestMessage, 
                "headers");

        if (value != NULL)
        {
            serialization_deserializeType ("StringDictionary", 
                    &request->requestHeaders, value);
        }
        else
        {
            request->requestHeaders = stringdictionary_new();
        }
    }

    {
        dynTstring *value = message_getParameterString (requestMessage, 
                "variables");

        if (value != NULL)
        {
            serialization_deserializeType ("StringDictionary", 
                    &request->transactionVariables, value);
        }
        else
        {
            request->transactionVariables = stringdictionary_new();
        }
    }

    request->responseHeaders = stringdictionary_new();
    request->sentResponseHeaders = false;

    request->responseStatus = 200;
    request->responseReason = dynFinit();

    return (request);
}

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

    assert (request != NULL);

    dynFfree (request->token);
    dynFfree (request->remoteIP);
    dynFfree (request->entity);

    stringdictionary_destroy (request->requestHeaders);
    stringdictionary_destroy (request->responseHeaders);
    stringdictionary_destroy (request->transactionVariables);

    dynFfree (request->responseReason);

    free (request);
}

void
http_request_setHttpStatus (HttpRequest *request, const int status, const char *description)
{
    assert (request != NULL);

    request->responseStatus = status;

    dynFsappend (request->responseReason, description);
}

const char *
http_request_getToken (const HttpRequest *request)
{
    assert (request != NULL);

    return (dynFgetstr (request->token));
}

const char *
http_request_getUri (const HttpRequest *request)
{
    assert (request != NULL);

    return (http_request_getInternalString (
                stringdictionary_getValue (request->transactionVariables, 
                    "uri")));
}

const char *
http_request_getQuery (const HttpRequest *request)
{
    assert (request != NULL);

    return (http_request_getInternalString (
                stringdictionary_getValue (request->transactionVariables, 
                    "query")));
}

const char *
http_request_getRemoteIP (const HttpRequest *request)
{
    assert (request != NULL);

    return (dynFgetstr (request->remoteIP));
}

const dynTstring *
http_request_getVariableString (const HttpRequest *request, const char *variable)
{
    assert (request != NULL);
    assert (variable != NULL);

    return (stringdictionary_getValue (request->transactionVariables, 
                    variable));
}

const char *
http_request_getVariable (const HttpRequest *request, const char *variable)
{
    assert (request != NULL);
    assert (variable != NULL);

    return (http_request_getInternalString (
                stringdictionary_getValue (request->transactionVariables, 
                    variable)));
}

const char *
http_request_getMethod (const HttpRequest *request)
{
    assert (request != NULL);

    return (http_request_getInternalString (
                stringdictionary_getValue (request->transactionVariables, 
                    "method")));
}

bool
http_request_headersSent (const HttpRequest *request)
{
    assert (request != NULL);

    return (request->sentResponseHeaders);
}

const dynTstring *
http_request_getRequestHeaderString (const HttpRequest *request, const char *key)
{
    assert (request != NULL);

    return (stringdictionary_getValue (request->requestHeaders, key));
}

hshTiterator *
http_request_getRequestHeaderIterator (const HttpRequest *request)
{
    return (stringdictionary_getIterator (request->requestHeaders));
}

const char *
http_request_getRequestHeader (const HttpRequest *request, const char *key)
{
    dynTstring *stringValue;

    assert (request != NULL);
    assert (key != NULL);

    stringValue = stringdictionary_getValue (request->requestHeaders, key);

    if (stringValue == NULL)
    {
        return (NULL);
    }
    else
    {
        return (dynFgetstr (stringValue));
    }
}

void
http_request_setResponseHeader (const HttpRequest *request, const char *key, const char *value)
{
    dynTstring *value_string = dynFinit();

    assert (request != NULL);
    assert (key != NULL);
    assert (value != NULL);

    dynFsappend (value_string, value);

    stringdictionary_setValue (request->responseHeaders, key, value_string);
}

void
http_request_setResponseHeaderData (const HttpRequest *request, const char *key, const char *value, int valueLen)
{
    dynTstring *value_string = dynFinit();

    assert (request != NULL);
    assert (key != NULL);
    assert (value != NULL);

    dynFappend (value_string, value, valueLen);

    stringdictionary_setValue (request->responseHeaders, key, value_string);
}

static void
http_request_populateInitial (HttpRequest *request, Message *requestMessage)
{
    dynTstring *serializedResponseHeaders;
    dynTstring *status = dynFinit();

    assert (request != NULL);
    assert (requestMessage != NULL);

    /* insert responseHeaders */
    serializedResponseHeaders = serialization_serializeType (
            "StringDictionary", &request->responseHeaders);

    message_setParameterString (requestMessage, "responseHeaders",
            serializedResponseHeaders);

    dynFfree (serializedResponseHeaders);

    /* insert status */
    dynFappend_print (status, "%d", request->responseStatus);

    message_setParameterString (requestMessage, "status", status);

    dynFfree (status);
}

bool
http_request_writeData (HttpRequest *request, const char *data, const int dataLen)
{
    Message *requestMessage = message_newRequest ("HttpWrite");
    bool ret;

    assert (request != NULL);
    assert (data != NULL);

    message_setParameterString (requestMessage, "token", request->token);
    message_setParameterBinary (requestMessage, "data", data, dataLen);

    if (request->sentResponseHeaders == false)
    {
        http_request_populateInitial (request, requestMessage);

        request->sentResponseHeaders = true;
    }

    ret = http_response_sendSimpleMessage (request->processor,
            requestMessage);

    message_destroy (requestMessage);

    return (ret);
}

bool
http_request_writeString (HttpRequest *request, const char *data)
{
    assert (request != NULL);
    assert (data != NULL);

    return (http_request_writeData (request, data, strlen (data)));
}

bool
http_request_finishStatus (const HttpRequest *request, int status)
{
    bool ret;

    assert (request != NULL);

    ret = http_response_sendFinishMessage (request->processor, 
            dynFgetstr (request->token), status);

    return (ret);
}

bool
http_request_finish (const HttpRequest *request)
{
    bool ret;

    assert (request != NULL);

    ret = http_response_sendFinishMessage (request->processor, 
            dynFgetstr (request->token), STATUS_EXIT);

    return (ret);
}

static bool
http_request_loadEntity (HttpRequest *instance)
{
    assert (instance != NULL);

    if (instance->entityRequested == false)
    {
        if (http_response_sendReadEntityBody (instance->processor, 
                    dynFgetstr (instance->token), instance->entity) == false)
        {
            /* logFmsg (CONT_LOG_ERROR, "causality-client: Could not send entity body request."); */

            return (false);
        }

        instance->entityRequested = true;
    }

    return (true);
}

const dynTstring *
http_request_getEntireEntity (HttpRequest *instance)
{
    assert (instance != NULL);

    if (http_request_loadEntity (instance) == false)
    {
        return (NULL);
    }

    return (instance->entity);
}

int
http_request_getEntity (HttpRequest *request, char *outBuf, const int bufLen)
{
    int size;

    assert (request != NULL);
    assert (outBuf != NULL);

    if (http_request_loadEntity (request) == false)
    {
        return (-1);
    }

    size = dynFgetlen (request->entity) - request->entityReadOffset;

    if (size > bufLen)
    {
        size = bufLen;
    }

    if (size > 0)
    {
        memcpy (outBuf, dynFgetstr (request->entity) + request->entityReadOffset, size);

        request->entityReadOffset += size;
    }

    return (size);
}

bool
http_request_isEntireEntityLoaded (const HttpRequest *request)
{
    assert (request != NULL);

    if (request->entityRequested == true && 
            request->entityReadOffset == dynFgetlen (request->entity))
    {
        return (true);
    }
    else
    {
        return (false);
    }
}

static const char *
http_request_getInternalString (dynTstring *string)
{
    if (string == NULL)
    {
        return ("");
    }
    else
    {
        return (dynFgetstr (string));
    }
}

static bool
http_request_setIfNotNull (dynTstring *dest, const dynTstring *src)
{
    assert (dest != NULL);

    if (src == NULL)
    {
        return (false);
    }

    dynFappend_string (dest, src);

    return (true);
}

