#include "client.h"
#include "socket.h"
#include "engine.h"
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>

// TODO
#include <stdio.h>
#define ERROR printf("error at %s:%d\n", __FILE__, __LINE__);

#define CLIENT_MAX_CONNS 256

typedef enum {
    CLIENT_CONNECTION_FREE,
    CLIENT_CONNECTION_INIT,
    CLIENT_CONNECTION_WAIT,
    CLIENT_CONNECTION_DONE,
} ClientConnectionState;

typedef struct {
    ClientConnectionState state;
    uint16_t        gen;
    Socket          socket;
    HTTP_Engine     engine;
    bool            trace;
} ClientConnection;

struct HTTP_Client {
    SocketGroup group;
    int num_conns;
    ClientConnection conns[CLIENT_MAX_CONNS];

    int ready_head;
    int ready_count;
    int ready[CLIENT_MAX_CONNS];
};

// Rename the memory function
static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
    (void)data;
    switch (tag) {
        case HTTP_MEMFUNC_MALLOC:
            return malloc(len);
        case HTTP_MEMFUNC_FREE:
            free(ptr);
            return NULL;
    }
    return NULL;
}

void http_global_init(void)
{
    socket_global_init();
}

void http_global_free(void)
{
    socket_global_free();
}

HTTP_Client *http_client_init(void)
{
    HTTP_Client *client = malloc(sizeof(HTTP_Client));
    if (client == NULL)
        return NULL;

    if (socket_group_init(&client->group) < 0) {
        free(client);
        return NULL;
    }

    for (int i = 0; i < CLIENT_MAX_CONNS; i++) {
        client->conns[i].state = CLIENT_CONNECTION_FREE;
        client->conns[i].gen  = 1;
    }

    client->num_conns = 0;
    client->ready_head = 0;
    client->ready_count = 0;

    return client;
}

void http_client_free(HTTP_Client *client)
{
    for (int i = 0, j = 0; j < client->num_conns; i++) {

        if (client->conns[i].state == CLIENT_CONNECTION_FREE)
            continue;
        j++;

        // TODO
    }

    socket_group_free(&client->group);
    free(client);
}

int http_client_request(HTTP_Client *client, HTTP_RequestHandle *handle)
{
    if (client->num_conns == CLIENT_MAX_CONNS)
        return -1;

    int i = 0;
    while (client->conns[i].state != CLIENT_CONNECTION_FREE)
        i++;

    client->conns[i].trace = false;
    client->conns[i].state = CLIENT_CONNECTION_INIT;
    http_engine_init(&client->conns[i].engine, 1, client_memfunc, NULL);

    client->num_conns++;

    *handle = (HTTP_RequestHandle) { client, i, client->conns[i].gen };
    return 0;
}

static void client_connection_update(ClientConnection *conn)
{
    HTTP_ASSERT(conn->state == CLIENT_CONNECTION_WAIT);

    socket_update(&conn->socket);

    while (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) {

        HTTP_EngineState engine_state;
        
        engine_state = http_engine_state(&conn->engine);

        if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) {
            int len;
            char *buf;
            buf = http_engine_recvbuf(&conn->engine, &len);
            if (buf) {
                int ret = socket_read(&conn->socket, buf, len);
                if (conn->trace)
                    print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret });
                http_engine_recvack(&conn->engine, ret);
            }
        } else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) {
            int len;
            char *buf;
            buf = http_engine_sendbuf(&conn->engine, &len);
            if (buf) {
                int ret = socket_write(&conn->socket, buf, len);
                if (conn->trace)
                    print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret });
                http_engine_sendack(&conn->engine, ret);
            }
        }

        engine_state = http_engine_state(&conn->engine);

        if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED ||
            engine_state == HTTP_ENGINE_STATE_CLIENT_READY)
            socket_close(&conn->socket);
    }

    if (socket_state(&conn->socket) == SOCKET_STATE_DIED)
        conn->state = CLIENT_CONNECTION_DONE;
}

int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle)
{
    while (client->ready_count == 0) {

        int num_polled = 0;
        int indices[CLIENT_MAX_CONNS];
        struct pollfd polled[CLIENT_MAX_CONNS];

        for (int i = 0, j = 0; j < client->num_conns; i++) {

            HTTP_ASSERT(i < CLIENT_MAX_CONNS);
            ClientConnection *conn = &client->conns[i];

            if (conn->state == CLIENT_CONNECTION_FREE)
                continue;
            j++;

            int events = 0;
            if (conn->state == CLIENT_CONNECTION_WAIT) {
                switch (conn->socket.event) {
                    case SOCKET_WANT_READ : events = POLLIN;  break;
                    case SOCKET_WANT_WRITE: events = POLLOUT; break;
                    case SOCKET_WANT_NONE : events = 0;       break;
                }
            }

            if (events) {
                indices[num_polled] = i;
                polled[num_polled].fd = conn->socket.fd;
                polled[num_polled].events = events;
                polled[num_polled].revents = 0;
                num_polled++;
            }
        }

        if (num_polled == 0)
            return -1;

        poll(polled, num_polled, -1);

        for (int i = 0; i < num_polled; i++) {

            int connidx = indices[i];
            ClientConnection *conn = &client->conns[connidx];

            if (conn->state != CLIENT_CONNECTION_WAIT)
                continue;

            if (polled[i].revents == 0)
                continue;

            // TODO: handle error revents

            client_connection_update(conn);

            if (conn->state == CLIENT_CONNECTION_DONE) {
                int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
                client->ready[tail] = connidx;
                client->ready_count++;
            }
        }
    }

    int index = client->ready[client->ready_head];
    client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS;
    client->ready_count--;
    *handle = (HTTP_RequestHandle) { client, index, client->conns[index].gen };
    return 0;
}

static ClientConnection *handle2clientconn(HTTP_RequestHandle handle)
{
    if (handle.data0 == NULL)
        return NULL;

    HTTP_Client *client = handle.data0;

    if (handle.data1 >= CLIENT_MAX_CONNS)
        return NULL;

    ClientConnection *conn = &client->conns[handle.data1];

    if (handle.data2 != conn->gen)
        return NULL;

    return conn;
}

void http_request_trace(HTTP_RequestHandle handle, bool trace)
{
    ClientConnection *conn = handle2clientconn(handle);
    if (conn == NULL)
        return;
    if (conn->state != CLIENT_CONNECTION_INIT)
        return;

    conn->trace = trace;
}

void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_String url)
{
    ClientConnection *conn = handle2clientconn(handle);
    if (conn == NULL)
        return;
    if (conn->state != CLIENT_CONNECTION_INIT)
        return;

    HTTP_Client *client = handle.data0;

    HTTP_URL parsed_url;
    int ret = http_parse_url(url.ptr, url.len, &parsed_url);
    if (ret != url.len) {
        // TODO
        ERROR;
        return;
    }

    bool secure = false;
    if (http_streq(parsed_url.scheme, HTTP_STR("https"))) {
        secure = true;
    } else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) {
        // TODO
        ERROR;
        return;
    }

    int port = parsed_url.authority.port;
    if (port == 0) {
        if (secure)
            port = 443;
        else
            port = 80;
    }

    SocketGroup *group = secure ? &client->group : NULL;
    switch (parsed_url.authority.host.mode) {
        case HTTP_HOST_MODE_IPV4: socket_connect_ipv4(&conn->socket, group, parsed_url.authority.host.ipv4, port); break;
        case HTTP_HOST_MODE_IPV6: socket_connect_ipv6(&conn->socket, group, parsed_url.authority.host.ipv6, port); break;
        case HTTP_HOST_MODE_NAME: socket_connect     (&conn->socket, group, parsed_url.authority.host.name, port); break;

        case HTTP_HOST_MODE_VOID:
        // TODO
        ERROR;
        return;
    }

    http_engine_url(&conn->engine, method, url, 1);
}

void http_request_header(HTTP_RequestHandle handle, HTTP_String str)
{
    ClientConnection *conn = handle2clientconn(handle);
    if (conn == NULL)
        return;
    if (conn->state != CLIENT_CONNECTION_INIT)
        return;

    http_engine_header(&conn->engine, str);
}

void http_request_body(HTTP_RequestHandle handle, HTTP_String str)
{
    ClientConnection *conn = handle2clientconn(handle);
    if (conn == NULL)
        return;
    if (conn->state != CLIENT_CONNECTION_INIT)
        return;

    http_engine_body(&conn->engine, str);
}

void http_request_submit(HTTP_RequestHandle handle)
{
    ClientConnection *conn = handle2clientconn(handle);
    if (conn == NULL)
        return;
    if (conn->state != CLIENT_CONNECTION_INIT)
        return;

    http_engine_done(&conn->engine);
    conn->state = CLIENT_CONNECTION_WAIT;
}

HTTP_Response *http_request_result(HTTP_RequestHandle handle)
{
    ClientConnection *conn = handle2clientconn(handle);
    if (conn == NULL)
        return NULL;
    if (conn->state != CLIENT_CONNECTION_DONE)
        return NULL;
    HTTP_EngineState engine_state = http_engine_state(&conn->engine);
    if (engine_state != HTTP_ENGINE_STATE_CLIENT_READY)
        return NULL;
    return http_engine_getres(&conn->engine);
}

void http_request_free(HTTP_RequestHandle handle)
{
    HTTP_Client *client = handle.data0;
    ClientConnection *conn = handle2clientconn(handle);
    if (conn == NULL)
        return;
    if (conn->state != CLIENT_CONNECTION_DONE)
        return;
    http_engine_free(&conn->engine);
    socket_free(&conn->socket);
    conn->state = CLIENT_CONNECTION_FREE;
    client->num_conns--;
}

static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called

static HTTP_Client *get_default_client(void)
{
    if (default_client___ == NULL)
        default_client___ = http_client_init();
    return default_client___;
}

HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_RequestHandle *phandle)
{
    HTTP_Client *client = get_default_client();
    if (client == NULL)
        return NULL;

    HTTP_RequestHandle handle;
    int ret = http_client_request(client, &handle);
    if (ret < 0)
        return NULL;

    http_request_line(handle, HTTP_METHOD_GET, url);

    for (int i = 0; i < num_headers; i++)
        http_request_header(handle, headers[i]);

    http_request_submit(handle);

    ret = http_client_wait(client, NULL); // TODO: it's assumed there is only one request pending
    if (ret < 0) {
        http_request_free(handle); // TODO: currently free only works on completed request handles
        return NULL;
    }

    HTTP_Response *res = http_request_result(handle);
    if (res == NULL) {
        http_request_free(handle);
        return NULL;
    }

    *phandle = handle;
    return res;
}

HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body, HTTP_RequestHandle *phandle)
{
    HTTP_Client *client = get_default_client();
    if (client == NULL)
        return NULL;

    HTTP_RequestHandle handle;
    int ret = http_client_request(client, &handle);
    if (ret < 0)
        return NULL;

    http_request_line(handle, HTTP_METHOD_GET, url);

    for (int i = 0; i < num_headers; i++)
        http_request_header(handle, headers[i]);

    http_request_body(handle, body);

    http_request_submit(handle);

    ret = http_client_wait(client, NULL); // TODO: it's assumed there is only one request pending
    if (ret < 0) {
        http_request_free(handle); // TODO: currently free only works on completed request handles
        return NULL;
    }

    HTTP_Response *res = http_request_result(handle);
    if (res == NULL) {
        http_request_free(handle);
        return NULL;
    }

    *phandle = handle;
    return res;
}