/*
 * http.c
 *
 *  Created on: 26.3.2014
 *      Author: Stefan
 */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>

#include "http.h"



/* Private typedef -----------------------------------------------------------*/

typedef struct http_file_type_struct
{
    char* extension;
    char* mime;
    bool_t is_text;
} http_file_type_t;

/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static const uint8_t http_newline[] = {0x0d, 0x0a};

static const char* http_methods_str[] =
{
    "GET",
    "HEAD",
    "POST"
};

static const char* http_versions_str[] =
{
    "HTTP/1.1",
    "HTTP/1.0"
};

static const char* http_responses_str[] =
{
    "Continue", "OK", "Moved Permanently", "Found", "Bad Request", 
    "Unauthorized", "Forbidden", "Not Found", "Method Not Allowed",
    "Not Acceptable", "Length Required", "Request Entity Too Large",
    "Request-URI Too Long", "Internal Server Error", "Not Implemented",
    "Bad Gateway", "Service Unavailable", "HTTP Version Not Supported"
};

static const uint32_t http_responses_num[] =
{
    100, 200, 301, 302, 400,
    401, 403, 404, 405, 
    406, 411, 413, 
    414, 500, 501, 
    502, 503, 505
};

static const char* http_headers[] =
{
    "Host",
    "Connection",
    "Content-Length",
    "Content-Type",
    "Transfer-Encoding",
    "Server",
    "Location"
};


static const http_file_type_t http_known_types[] =
{
    {"html", "text/html", TRUE},
    {"json", "application/json", TRUE},
    {"js", "text/javascript", TRUE},
    {"css", "text/css", TRUE},
    {"png", "image/png", FALSE},
    {"gif", "image/gif", FALSE},
    {"svg", "image/svg+xml", TRUE},
    {"jpg", "image/jpeg", FALSE}
};


static const char* http_header_token_dividers = ": ";

/* Public variables ----------------------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
static http_err_t http_conn_get_buffer(
    http_conn_info_t* conn_info
)
{
    if(NULL != conn_info->net_buffer)
    {
        if(netbuf_next(conn_info->net_buffer) >= 0)
        {
            if(ERR_OK != netbuf_data(
                conn_info->net_buffer,
                (void **)&(conn_info->next_char),
                &(conn_info->chars_left))
            )
            {
                return HTTP_ERR_CONNECTION;
            }
        }
        else
        {
            netbuf_delete(conn_info->net_buffer);
            if(ERR_OK != netconn_recv(
                conn_info->connection,
                &(conn_info->net_buffer))
            )
            {
                return HTTP_ERR_CONNECTION;
            }

            if(ERR_OK != netbuf_data(
                conn_info->net_buffer,
                (void **)&(conn_info->next_char),
                &(conn_info->chars_left))
            )
            {
                return HTTP_ERR_CONNECTION;
            }


        }
    }
    else
    {
        if(ERR_OK != netconn_recv(
            conn_info->connection,
            &(conn_info->net_buffer))
        )
        {
            return HTTP_ERR_CONNECTION;
        }

        if(ERR_OK != netbuf_data(
            conn_info->net_buffer,
            (void **)&(conn_info->next_char),
            &(conn_info->chars_left))
        )
        {
            return HTTP_ERR_CONNECTION;
        }
    }

    return HTTP_ERR_OK;
}

static http_err_t http_send_buffer(
    char* buffer,
    uint32_t buffer_length,
    http_conn_info_t* conn_info
)
{
    if(ERR_OK == netconn_write(
        conn_info->connection,
        buffer, buffer_length,
        NETCONN_COPY)
    )
    {
        return HTTP_ERR_OK;
    }
    else
    {
        return HTTP_ERR_CONNECTION;
    }
}

static uint32_t http_get_line(
    char* buffer,
    uint32_t buffer_length,
    http_conn_info_t* conn_info
)
{
    uint32_t length, detect_pos;
    char c;
    detect_pos = 0;
    
    for(length = 0; length < buffer_length; length ++)
    {
        if(conn_info->chars_left == 0)
        {
            if(HTTP_ERR_OK != http_conn_get_buffer(conn_info))
            {
                return 0;
            }
        }

        c = *((conn_info->next_char) ++);
        conn_info->chars_left --;

        if(c == http_newline[detect_pos])
        {			
            detect_pos ++;
            if((sizeof(http_newline) / sizeof(uint8_t)) == detect_pos)
            {
                length ++;
                break;
            }
        }
        else
        {
            detect_pos = 0;
            *(buffer ++) = c;
        }
    }
    length -= detect_pos;

    if(length < buffer_length)
    {
        *buffer = 0;        
    }
    
    return length;
}

static uint32_t http_get_word(
    char* line,
    uint32_t line_length,
    char* word_buffer,
    uint32_t word_buffer_length
)
{
    uint32_t response = word_buffer_length;    

    while(line_length > 0 && word_buffer_length > 0)
    {
        line_length --;
        if(' ' == (*line))
        {            
            break;
        }

        *(word_buffer ++) = *(line ++);
        word_buffer_length --;
    }

    if(word_buffer_length > 0)
    {
        *word_buffer = 0;
    }

    response -= word_buffer_length;
    return response;
}
/* Public functions ----------------------------------------------------------*/
http_err_t http_init_struct(
    http_status_t* http_info,
    struct netconn* stream
)
{

    http_info->connection.connection    = stream;
    http_info->connection.net_buffer    = NULL;
    http_info->connection.next_char     = NULL;
    http_info->connection.chars_left    = 0;

    http_info->buffered_length          = 0;

    return HTTP_ERR_OK;
}

http_err_t http_fini_struct(
    http_status_t* http_info
)
{
    if(NULL != http_info->connection.net_buffer)
    {
        netbuf_delete(http_info->connection.net_buffer);
        http_info->connection.net_buffer = NULL;
    }
    return HTTP_ERR_OK;
}

http_err_t http_decode_request(
	http_status_t* http_info,
    http_request_t* request_info
)
{
    char buffer[12];
    uint32_t position, tmp;
    
    
    /* Initialize structure members */    
    request_info->method    = HTTP_METHOD_UNKNOWN;
    request_info->version   = HTTP_VER_UNKNOWN;

    /* If file name buffer is not provided */
    if(NULL == request_info->document_name)
    {
        /* Use line buffer as file name buffer */
        request_info->document_name = http_info->buffer;
        request_info->document_name_length = HTTP_BUFFER_LENGTH;
    }

    /* Read line */
    http_info->buffered_length = http_get_line(
        http_info->buffer, 
        HTTP_BUFFER_LENGTH,
        &(http_info->connection));
        
    position = 0;
    /* Read Method */
    tmp = http_get_word(
        &((http_info->buffer)[position]),
        (http_info->buffered_length) - position,
        buffer,
        sizeof(buffer) / sizeof(uint8_t));    
    /* If there was enough space in buffer */
    if(tmp < (sizeof(buffer) / sizeof(uint8_t)))
    {
        /* Advance position */
        position += tmp + 1;
    }
    else
    {
        request_info->document_name_length = 0;

        /* Return error code */
        return HTTP_ERR_METHOD_NOT_ALLOWED;
    }

    /* Compare with known methods names */
    for(tmp = 0; tmp < (sizeof(http_methods_str) / sizeof(uint8_t*)); tmp ++)
    {
        if(0 == strcmp((char*)buffer, (char*)(http_methods_str[tmp])))
        {
            request_info->method = (http_method_t)tmp;
            break;
        }
    }
    /* If there was no match */
    if(HTTP_METHOD_UNKNOWN == request_info->method)
    {
        request_info->document_name_length = 0;

        /* Return error code */
        return HTTP_ERR_METHOD_NOT_ALLOWED;
    }


    /* Read requested file name */
    tmp = http_get_word(
        &((http_info->buffer)[position]),
        (http_info->buffered_length) - position,
        request_info->document_name,
        request_info->document_name_length);
    if(tmp < request_info->document_name_length)
    {
        request_info->document_name_length = tmp;
        position += tmp + 1;
    }
    else
    {
        request_info->document_name_length = 0;

        /* Return error code */
        return HTTP_ERR_BUFFER_TOO_SMALL;
    }


    /* Read version */
    tmp = http_get_word(
        &((http_info->buffer)[position]),
        (http_info->buffered_length) - position,
        buffer,
        sizeof(buffer) / sizeof(uint8_t));    
    /* If there was enough space in buffer */
    if(tmp >= (sizeof(buffer) / sizeof(uint8_t)))
    {
        /* Return error code */
        return HTTP_ERR_UNKNOWN_VERSION;
    }

    /* Compare with known version names */
    for(tmp = 0; tmp < (sizeof(http_versions_str) / sizeof(uint8_t*)); tmp ++)
    {
        if(0 == strcmp((char*)buffer, (char*)(http_versions_str[tmp])))
        {
            request_info->version = (http_version_t)tmp;
            break;
        }
    }
    /* If there was no match */
    if(HTTP_VER_UNKNOWN == request_info->version)
    {        
        /* Return error code */
        return HTTP_ERR_UNKNOWN_VERSION;
    }

    /* Return success indication */
    return HTTP_ERR_OK;
}

http_header_t http_get_header(
    http_status_t* http_info,
    const char** value
)
{
    char* buffer;
    uint32_t tmp;

    /* Read line */
    http_info->buffered_length = http_get_line(
        http_info->buffer,
        HTTP_BUFFER_LENGTH,
        &(http_info->connection));

    if(http_info->buffered_length >= HTTP_BUFFER_LENGTH)
    {
        return HTTP_HEADER_TOO_LONG;
    }
    else if (http_info->buffered_length == 0)
    {
        return HTTP_HEADER_EMPTY;
    }
    else
    {
        /* Get header's name */
        buffer = strtok(http_info->buffer, http_header_token_dividers);

        /* Compare to known names */
        for(tmp = 0; tmp < (sizeof(http_headers)/sizeof(char*)); tmp ++)
        {
            if(0 == stricmp(buffer, http_headers[tmp]))
            {
                break;
            }
        }

        /* Locate position of header's value */
        *value = strtok(NULL, http_header_token_dividers);

        if(tmp < (sizeof(http_headers)/sizeof(char*)))
        {
            return (http_header_t)tmp;
        }

    }
    return HTTP_HEADER_UNKNOWN;
}

http_err_t http_generate_response(
    http_status_t* http_info,
    const http_response_t* response_info
)
{    
    uint32_t tmp;
    const char* msg;

    /* Prepare Message */
    if(NULL == response_info->message)
    {
        for(tmp = 0; 
            tmp < (sizeof(http_responses_num) / sizeof(uint32_t));
            tmp ++
        )
        {
            if(response_info->response_code == http_responses_num[tmp])
            {
                break;
            }
        }
        if(tmp < (sizeof(http_responses_num) / sizeof(uint32_t)))
        {
            msg = (http_responses_str[tmp]);
        }
        else
        {
            /* Return error code */
            return HTTP_ERR_UNKNOWN_RESPONSE_NUMBER;
        }
    }
    else
    {
        msg = response_info->message;
    }

    /* Build response */
    http_info->buffered_length = snprintf(
        (char*)(http_info->buffer), 
        HTTP_BUFFER_LENGTH,
        "%s %u %s",
        http_versions_str[response_info->version],
        (unsigned int)response_info->response_code,
        msg);

    if(http_info->buffered_length < (HTTP_BUFFER_LENGTH - 2))
    {
        http_info->buffer[http_info->buffered_length ++] = http_newline[0];
        http_info->buffer[http_info->buffered_length ++] = http_newline[1];
        
        return http_send_buffer(http_info->buffer, http_info->buffered_length,
                &(http_info->connection));
    }
    else
    {
        return HTTP_ERR_BUFFER_TOO_SMALL;
    }
}

http_err_t http_set_header(
    http_status_t* http_info,
    http_header_t header_type,
    const char* value
)
{
    if(header_type < 0 || header_type >= (sizeof(http_headers)/sizeof(char*)))
    {
        return HTTP_ERR_UNKNOWN_HEADER;
    }

    /* Build response */
    http_info->buffered_length = snprintf(
        (char*)(http_info->buffer),
        HTTP_BUFFER_LENGTH,
        "%s: %s",
        http_headers[header_type],
        value);

    if(http_info->buffered_length < (HTTP_BUFFER_LENGTH - 2))
    {
        http_info->buffer[http_info->buffered_length ++] = http_newline[0];
        http_info->buffer[http_info->buffered_length ++] = http_newline[1];

        return http_send_buffer(http_info->buffer, http_info->buffered_length,
                &(http_info->connection));
    }
    else
    {
        return HTTP_ERR_BUFFER_TOO_SMALL;
    }
}

http_err_t http_data_start(
    http_status_t* http_info,
    http_transfer_encoding_t encoding
)
{
    http_info->transfer_encoding = encoding;

    http_info->buffered_length = 0;
    http_info->buffer[http_info->buffered_length ++] = http_newline[0];
    http_info->buffer[http_info->buffered_length ++] = http_newline[1];

    return http_send_buffer(http_info->buffer, http_info->buffered_length,
            &(http_info->connection));

}

http_err_t http_data_write(
    http_status_t* http_info,
    char* data,
    uint32_t length
)
{
    http_err_t response;
    if(HTTP_TRANSFER_ENCODING_CHUNKED == http_info->transfer_encoding)
    {
        http_info->buffered_length = snprintf(
            http_info->buffer,
            HTTP_BUFFER_LENGTH,
            "%x",
            (unsigned int)length);

        http_info->buffer[http_info->buffered_length ++] = http_newline[0];
        http_info->buffer[http_info->buffered_length ++] = http_newline[1];

        response = http_send_buffer(http_info->buffer,
            http_info->buffered_length,
            &(http_info->connection));

        if(HTTP_ERR_OK != response)
        {
            return response;
        }
    }

    if(length > 0)
    {
        response = http_send_buffer(data, length, &(http_info->connection));
    }

    if(HTTP_TRANSFER_ENCODING_CHUNKED == http_info->transfer_encoding)
    {
        http_info->buffered_length = 0;
        http_info->buffer[http_info->buffered_length ++] = http_newline[0];
        http_info->buffer[http_info->buffered_length ++] = http_newline[1];

        response = http_send_buffer(http_info->buffer,
            http_info->buffered_length,
            &(http_info->connection));

        if(HTTP_ERR_OK != response)
        {
            return response;
        }
    }

    return response;
}

http_err_t http_get_resp_msg(
    uint32_t code,
    const char** msg
)
{
    uint32_t tmp;

    for(tmp = 0;
        tmp < (sizeof(http_responses_num) / sizeof(uint32_t));
        tmp ++
    )
    {
        if(code == http_responses_num[tmp])
        {
            break;
        }
    }

    if(tmp < (sizeof(http_responses_num) / sizeof(uint32_t)))
    {
        *msg = (http_responses_str[tmp]);
        return HTTP_ERR_OK;
    }
    else
    {
        /* Return error code */
        return HTTP_ERR_UNKNOWN_RESPONSE_NUMBER;
    }

}

http_err_t http_build_mmime(
    char* buffer,
    uint32_t length,
    const char* filename,
    const char* charset
)
{
    char* ext;
    int32_t i, tmp;

    /* Copy filename into buffer */
    strncpy(buffer, filename, length);

    /* Get extension */
    ext = strrchr(filename, '.');

    if(NULL == ext)
    {
        return HTTP_ERR_UNKNOWN_EXTENSION;
    }
    ext ++;

    /* Try to recognize the extension */
    for(i = 0; i < (sizeof(http_known_types)/sizeof(http_file_type_t)); i ++)
    {
        if(0 == strcmp(ext, http_known_types[i].extension))
        {
            break;
        }
    }

    if(i == (sizeof(http_known_types)/sizeof(http_file_type_t)))
    {
        return HTTP_ERR_UNKNOWN_EXTENSION;
    }

    if(NULL != charset && TRUE == http_known_types[i].is_text)
    {
        tmp = snprintf(buffer, length, "%s; charset=%s",
            http_known_types[i].mime, charset);
    }
    else
    {
        tmp = snprintf(buffer, length, "%s",
            http_known_types[i].mime);
    }

    if(tmp > 0 && tmp < length)
    {
        return HTTP_ERR_OK;
    }
    else
    {
        return HTTP_ERR_BUFFER_TOO_SMALL;
    }
}
