/**
 * \file   httpd.c
 * \author Štefan Mišík
 * \date   April, 2014
 * \brief  Web server application
 *
 * \details
 *
 */

/* Includes ------------------------------------------------------------------*/


#include <string.h>

#include "httpd.h"
#include "doc.h"

/* Private define ------------------------------------------------------------*/
/**
 * \brief Name of HTTPD process
 */
#define HTTPD_PNAME     "httpd"

/**
 * \brief Stack size of HTTPD Process
 */
#define HTTPD_PSTACK    (configMINIMAL_STACK_SIZE)

/**
 * \brief Priority of HTTPD process
 */
#define HTTPD_PPRIORITY (tskIDLE_PRIORITY + 3)

/**
 * \brief Number of HTTP server threads
 */
#define HTTPD_THREADS   6  /* MEMP_NUM_TCP_PCB */

/**
 * \brief HTTPD Server thread's stack size
 */
#define HTTPD_SRV_PSTACK    (configMINIMAL_STACK_SIZE * 6)



/* Private typedef -----------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/**
 * \brief HTTPD Task handle
 */
static TaskHandle_t httpd_handle = NULL;
/**
 * \brief Server threads handles
 */
static TaskHandle_t httpd_servers[HTTPD_THREADS];

/**
 * \brief Httpd data structure for each thread
 */
static httpd_srv_data_t httpd_srv_data[HTTPD_THREADS];

/**
 * \brief HTTPD connection queue
 */
static QueueHandle_t httpd_conn_queue = NULL;



/* Private functions ---------------------------------------------------------*/

static void httpd_server(
    void* arg
)
{
    struct netconn* conn = NULL;
    httpd_srv_data_t* my_data = &(httpd_srv_data[((uint32_t)arg)]);
    http_request_t http_request;
    char* tok;
    http_err_t response;

    (void)xTaskNewlibAllocateReent();




    while(1)
    {
        if(pdTRUE ==
            xQueueReceive(httpd_conn_queue, &conn, portMAX_DELAY)
        )
        {
            /* NULL means thread should be deleted */
            if(NULL == conn)
            {
                break;
            }

            my_data->connection_close = TRUE;

            netconn_set_recvtimeout(conn, 10000);

            http_init_struct(&(my_data->http_status), conn);

            do
            {

                /* Serve here */
                http_request.document_name = my_data->url;
                http_request.document_name_length = HTTPD_URL_BUFFER_LENGTH;
                response = http_decode_request(&(my_data->http_status), &http_request);



                if(HTTP_ERR_OK == response)
                {
                    if(http_request.version == HTTP_VER_1_0)
                    {
                        my_data->connection_close = TRUE;
                    }

                    my_data->current_connection = conn;
                    tok = strtok(my_data->url, "/");
                    if(NULL != tok)
                    {
                        if(0 == strcmp(tok, DOC_REST_PREFIX))
                        {
                            tok = strtok(NULL, "/");
                            if(FALSE == doc_send_rest(my_data, tok))
                            {
                                doc_send_error_page(my_data, 404);
                            }
                        }
                        else if(FALSE == doc_send_file(my_data, tok))
                        {
                            doc_send_error_page(my_data, 404);
                        }
                    }
                    else
                    {
                        doc_redirect(my_data, "/index.html");
                    }
                }
                else
                {
                    my_data->connection_close = TRUE;
                }

            } while (FALSE == my_data->connection_close);

            http_fini_struct(&(my_data->http_status));

            /* Close The connection */
            (void)netconn_close(conn);
            (void)netconn_delete(conn);
        }
    }

    /* Delete This server thread */
    httpd_servers[((uint32_t)arg)] = NULL;
    vTaskDelete(NULL);
}

static void httpd_main(
    void* arg
)
{
    struct netconn *conn, *newconn;
    err_t err, accept_err;

    (void)arg;


    /* Create a new connection identifier. */
    conn = netconn_new(NETCONN_TCP);

    if(NULL != conn)
    {
        /* Bind connection to well port number 80. */
        err = netconn_bind(conn, NULL, 80);

        if (ERR_OK == err)
        {
            /* Tell connection to go into listening mode. */
            netconn_listen(conn);

            while(1)
            {
                /* Grab new connection. */
                accept_err = netconn_accept(conn, &newconn);

                if(ERR_OK == accept_err)
                {
                    if(pdTRUE !=
                        xQueueSend(
                            httpd_conn_queue,
                            &newconn,
                            portMAX_DELAY)
                    )
                    {
                        (void)netconn_close(newconn);
                        (void)netconn_delete(newconn);
                    }

                }
            }
        }
    }
}

/* Public variables ----------------------------------------------------------*/
/* Public functions ----------------------------------------------------------*/
bool_t httpd_start(
    void
)
{
    bool_t response = FALSE;

    /* Start only if HTTPD is not already running*/
    if(NULL == httpd_handle)
    {

        /* Create Server queue*/
        httpd_conn_queue = xQueueCreate(HTTPD_THREADS, sizeof(struct netconn*));
        if(NULL != httpd_conn_queue)
        {
            unsigned int thread_id;

            /* Create server threads */
            for(thread_id = 0; thread_id < HTTPD_THREADS; thread_id ++)
            {
                char name[configMAX_TASK_NAME_LEN];

                /* Create name */
                sprintf(name, "http_srv_%u", thread_id);

                if(pdPASS !=
                    xTaskCreate(
                        httpd_server,
                        (const char * const)name,
                        HTTPD_SRV_PSTACK,
                        (void *)thread_id,
                        HTTPD_PPRIORITY,
                        &(httpd_servers[thread_id])
                    )
                )
                {
                    break;
                }
            }

            /* If all threads were created successfully */
            if(HTTPD_THREADS == thread_id)
            {
                /* Try to create main httpd process */
                if(pdPASS ==
                    xTaskCreate(
                        httpd_main,
                        (const char * const)HTTPD_PNAME,
                        HTTPD_PSTACK,
                        NULL,
                        HTTPD_PPRIORITY,
                        &httpd_handle
                    )
                )
                {
                    response = TRUE;
                }
                else
                {
                    /* TODO: Handle Errors (Could not create main process) */
                }
            }
            else
            {
                /* TODO: Handle Errors (Not all server threads were created) */
            }
        }
        else
        {
            /* TODO: Handle Errors (Could not create queue) */
        }
    }

    return response;
}




