/******************************************************************************!
 * \file httpServer.c
 * \author Sebastien Beaugrand
 * \sa http://beaugrand.chez.com/
 * \copyright CeCILL 2.1 Free Software license
 ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#include <search.h>
#include <linux/tcp.h>
#include "httpServer.h"
#include "resources.h"
#include "common.h"

/******************************************************************************!
 * \fn httpInit
 ******************************************************************************/
void httpInit()
{
    hcreate(27);  // 21 + 25 %
}

/******************************************************************************!
 * \fn httpAddResource
 ******************************************************************************/
void httpAddResource(const char* name, void* func)
{
    ENTRY e;

    e.key = (char*) name;
    e.data = func;
    if (hsearch(e, ENTER) == 0) {
        ERROR("hsearch");
    }
}

/******************************************************************************!
 * \fn httpSendResource
 ******************************************************************************/
unsigned int
httpSendResource(int sockClient, const char* resourceName,
                 struct Buffer* buffer, enum tFormat format)
{
    ENTRY e;
    ENTRY* ep;
    unsigned int (* func)(int sockClient,
                          struct Buffer* buffer, enum tFormat format);

    e.key = (char*) resourceName;
    if ((ep = hsearch(e, FIND)) == NULL) {
        if (send(sockClient, " ", 1, 0) != (ssize_t) 1) {
            ERROR("send");
            return 0;
        }
        return 1;
    }
    func = ep->data;
    (*func)(sockClient, buffer, format);

    return 1;
}

/******************************************************************************!
 * \fn httpGetResourceName
 ******************************************************************************/
#define RESOURCE_NAME_SIZE 16
const char* httpGetResourceName(const char* buffer)
{
    static char resourceName[RESOURCE_NAME_SIZE];
    const char* c;
    int i;

    if (strncmp(buffer, "GET /", 5) == 0) {
        c = buffer + 5;
    } else if (strncmp(buffer, "PUT /", 5) == 0) {
        c = buffer + 5;
    } else {
        return NULL;
    }
    i = 0;
    while (i < RESOURCE_NAME_SIZE - 1 &&
           *c != ' ' &&
           *c != '/' &&
           *c != '?' &&
           *c != '\0') {
        resourceName[i] = *c;
        ++c;
        ++i;
    }
    if (i == 0 && *c == ' ') {
        DEBUG("resource=root");
        return RESOURCE_NAME_ROOT;
    }
    if (i == 0 || i == RESOURCE_NAME_SIZE - 1) {
        return NULL;
    }
    resourceName[i] = '\0';

    DEBUG("resource=%s", resourceName);
    return resourceName;
}

/******************************************************************************!
 * \fn httpGetResourceParam
 ******************************************************************************/
#define RESOURCE_PARAM_SIZE 16
const char* httpGetResourceParam(const char* buffer, const char* param)
{
    static char value[RESOURCE_PARAM_SIZE];
    const char* c;
    const char* d;
    const size_t l = strlen(param);
    unsigned int i;

    c = buffer + 5;
    while (*c != '?') {
        if (*c == '\n' || *c == '\0') {
            return NULL;
        }
        ++c;
    }
    ++c;
    d = c;
    while (*d != '=') {
        if (*c == '\n' || *c == '\0') {
            return NULL;
        }
        ++d;
    }
    while (d - c != (ssize_t) l && strncmp(c, param, l) != 0) {
        c = d + 1;
        while (*c != '&' && *c != ' ') {
            if (*c == '\n' || *c == '\0') {
                return NULL;
            }
            ++c;
        }
        ++c;
        d = c;
        while (*d != '=') {
            if (*c == '\n' || *c == '\0') {
                return NULL;
            }
            ++d;
        }
    }
    c = d + 1;
    i = 0;
    while (*c != '&' && *c != ' ') {
        if (*c == '\n' || *c == '\0' || i >= RESOURCE_PARAM_SIZE - 1) {
            return NULL;
        }
        value[i] = *c;
        ++c;
        ++i;
    }
    value[i] = '\0';
    DEBUG("%s=%s", param, value);
    return value;
}

/******************************************************************************!
 * \fn httpRunServer
 ******************************************************************************/
void httpRunServer(struct Buffer* buffer)
{
    static int sockServer;
    static int sockClient;
    static struct sockaddr_in addrServer;
    static struct sockaddr_in addrClient;
    static unsigned int addrSize = sizeof(addrClient);
    static enum {
        STATE0_INIT,
        STATE1_WAIT_CONN,
        STATE2_WAIT_DATA
    } serverState = STATE0_INIT;
    int flags;
    const char* resourceName;
    const char* formatName;
    static char recvBuff[TCP_MSS_DEFAULT + 1];
    FILE* file;

    switch (serverState) {
    case STATE0_INIT:
        // socket
        if ((sockServer = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
            ERROR("socket");
            return;
        }
        // sockaddr_in
        memset(&addrServer, 0, sizeof(addrServer));
        addrServer.sin_family = AF_INET;
        addrServer.sin_addr.s_addr = htonl(INADDR_ANY);
        addrServer.sin_port = htons(8080);
        // bind
        if (bind(sockServer, (struct sockaddr*) &addrServer,
                 sizeof(addrServer)) < 0) {
            ERROR("bind");
            return;
        }
        // listen
        if (listen(sockServer, 1) < 0) {
            ERROR("listen");
            return;
        }
        // nonblock
        flags = fcntl(sockServer, F_GETFL);
        fcntl(sockServer, F_SETFL, flags | O_NONBLOCK);
        // next state
        serverState = STATE1_WAIT_CONN;
        __attribute__ ((fallthrough));

    case STATE1_WAIT_CONN:
        // accept
        if ((sockClient =
                 accept(sockServer,
                        (struct sockaddr*) &addrClient,
                        &addrSize)) < 0) {
            if (errno != EWOULDBLOCK) {
                ERROR("accept");
            }
            return;
        }
        if (strcmp(inet_ntoa(addrClient.sin_addr), "127.0.0.1") != 0) {
            ERROR("unauthorized ip %s", inet_ntoa(addrClient.sin_addr));
            close(sockClient);
            return;
        }
        // next state
        serverState = STATE2_WAIT_DATA;
        __attribute__ ((fallthrough));

    case STATE2_WAIT_DATA:
        // recv
        *recvBuff = '\0';
        if (recv(sockClient, recvBuff, TCP_MSS_DEFAULT, MSG_DONTWAIT) == -1) {
            if (errno == EWOULDBLOCK) {
            } else if (errno == ECONNRESET) {  // nmap
                serverState = STATE1_WAIT_CONN;
            } else {
                ERROR("recv");
            }
            return;
        }
        // send
        if ((resourceName = httpGetResourceName(recvBuff)) != NULL) {
            formatName = httpGetResourceParam(recvBuff, "format");
            file = bufferInit(buffer);
            fprintf(file, "%s", recvBuff);
            if (formatName != NULL && strcmp(formatName, "html") == 0) {
                if (httpSendResource(sockClient, resourceName, buffer, HTML)) {
                    return;
                }
            } else {
                if (httpSendResource(sockClient, resourceName, buffer, RAW)) {
                    return;
                }
            }
        }
        // close
        close(sockClient);
        serverState = STATE1_WAIT_CONN;
        return;

    default:
        ERROR("server state");
    }
}

/******************************************************************************!
 * \fn httpGetHttpOk
 ******************************************************************************/
const char* httpGetHttpOk()
{
    static const char* r =
        "HTTP/1.0 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "Connection: Keep-Alive\r\n"
        "Content-Length: ";
    return r;
}

/******************************************************************************!
 * \fn httpGetHttpOkSize
 ******************************************************************************/
size_t httpGetHttpOkSize()
{
    static size_t r = 0;
    if (r == 0) {
        r = strlen(httpGetHttpOk()) + (HTTP_LENGTH_SIZE - 1) +
            4 + 1;  // \r\n\r\n + \n
    }
    return r;
}

/******************************************************************************!
 * \fn httpQuit
 ******************************************************************************/
void httpQuit()
{
    hdestroy();
}