#ifndef FLECS_REST_IMPL
#include "flecs_rest.h"
#endif
#include <stdio.h>
#include <ctype.h>

static
bool parse_filter(
    ecs_world_t *world,
    EcsHttpRequest *request,
    ecs_filter_t *filter)
{
    char buffer[1024];

    if (ecs_http_get_query_param(
        request->params, "include", buffer, sizeof(buffer)))
    {
        filter->include = ecs_type_from_str(world, buffer);
        filter->include_kind = EcsMatchAny;
        if (!filter->include) {
            return false;
        }
    }

    if (ecs_http_get_query_param(
        request->params, "exclude", buffer, sizeof(buffer)))
    {
        filter->exclude = ecs_type_from_str(world, buffer);
        if (!filter->exclude) {
            return false;
        }
    }

    return true;
}

static
bool parse_select(
    ecs_world_t *world,
    EcsHttpRequest *request,
    ecs_type_t *select)
{
    char buffer[1024];

    *select = NULL;

    if (ecs_http_get_query_param(
        request->params, "select", buffer, sizeof(buffer)))
    {
        *select = ecs_type_from_str(world, buffer);
        if (!*select) {
            return false;
        }
    }

    return true; 
}

static
bool parse_entity(
    ecs_world_t *world,
    EcsHttpRequest *request,
    ecs_entity_t *e_out)
{
    ecs_entity_t e = 0;

    if (request->relative_url && strlen(request->relative_url)) {
        /* Request scope of entity */
        const char *name = request->relative_url;

        /* If name starts with a number, lookup by entity id */
        if (isdigit(name[0])) {
            e = atoi(name);
        } else {
            e = ecs_lookup_path_w_sep(world, 0, name, "/", NULL);
            if (!e) {
                return false;
            }
        }
    }

    *e_out = e;

    return true;
}

static
bool endpoint_filter(
    ecs_world_t *world,
    ecs_entity_t entity,
    EcsHttpEndpoint *endpoint,
    EcsHttpRequest *request,
    EcsHttpReply *reply)
{
    ecs_filter_t filter = { 0 };
    ecs_type_t select = NULL;

    if (!parse_filter(world, request, &filter)) {
        return false;
    }

    if (!parse_select(world, request, &select)) {
        return false;
    }

    ecs_iter_t it = ecs_filter_iter(world, &filter);

    reply->body = ecs_iter_to_json(world, &it, ecs_filter_next, select);

    return true;
}

static
bool endpoint_scope(
    ecs_world_t *world,
    ecs_entity_t entity,
    EcsHttpEndpoint *endpoint,
    EcsHttpRequest *request,
    EcsHttpReply *reply)
{
    ecs_entity_t e = 0;
    ecs_filter_t filter = { 0 };
    ecs_type_t select = NULL;
    
    if (!parse_entity(world, request, &e)) {
        return false;
    }

    if (!parse_filter(world, request, &filter)) {
        return false;
    }

    if (!parse_select(world, request, &select)) {
        return false;
    }

    ecs_iter_t it = ecs_scope_iter_w_filter(world, e, &filter);

    reply->body = ecs_iter_to_json(world, &it, ecs_scope_next, select);

    return true;
}

static
bool endpoint_entity_new(
    ecs_world_t *world,
    EcsHttpReply *reply,
    const char *name,
    ecs_type_t select)
{
    ecs_entity_t e = ecs_new_from_path(world, 0, name);
    if (select) {
        ecs_add_type(world, e, select);
    }

    return true;
}

static
bool endpoint_entity_add(
    ecs_world_t *world,
    EcsHttpReply *reply,
    ecs_entity_t e,
    ecs_type_t select)
{
    if (select) {
        ecs_add_type(world, e, select);
    }

    return true;
}

static
bool endpoint_entity_remove(
    ecs_world_t *world,
    EcsHttpReply *reply,
    ecs_entity_t e,
    ecs_type_t select)
{
    if (select) {
        ecs_remove_type(world, e, select);
    } else {
        ecs_delete(world, e);
    }

    return true;
}

static
bool endpoint_entity_get(
    ecs_world_t *world,
    EcsHttpReply *reply,
    ecs_entity_t e,
    ecs_type_t select)
{
    reply->body = ecs_entity_to_json(world, e, select);
    return true;    
}

static
bool endpoint_entity(
    ecs_world_t *world,
    ecs_entity_t entity,
    EcsHttpEndpoint *endpoint,
    EcsHttpRequest *request,
    EcsHttpReply *reply)
{
    ecs_entity_t e = 0;
    ecs_type_t select = NULL;

    if (!parse_select(world, request, &select)) {
        return false;
    }

    if (request->method == EcsHttpPost) {
        return endpoint_entity_new(world, reply, request->relative_url, select);
    }    

    if (!parse_entity(world, request, &e)) {
        return false;
    }

    if (request->method == EcsHttpGet) {
        return endpoint_entity_get(world, reply, e, select);
    } else if (request->method == EcsHttpDelete) {
        return endpoint_entity_remove(world, reply, e, select);
    } else if (request->method == EcsHttpPut) {
        return endpoint_entity_add(world, reply, e, select);
    }

    return false;
}

static
void serialize_browse_info(
  ecs_world_t *world,
  ecs_entity_t parent,
  ecs_entity_t e,
  ecs_strbuf_t *str)
{
    ecs_strbuf_list_append(str, "\"id\":%u", (int32_t)e);

    char *type = ecs_type_to_json(world, ecs_get_type(world, e));
    ecs_strbuf_list_append(str, "\"type\":%s", type);
    
    const char *name = ecs_get_name(world, e);
    if (name) {
        ecs_strbuf_list_append(str, "\"name\":\"%s\"", name);

        char *fullpath = ecs_get_fullpath(world, e);
        ecs_strbuf_list_append(str, "\"path\":\"%s\"", fullpath);
    }

    int32_t child_count = ecs_get_child_count(world, e);
    ecs_strbuf_list_append(str, "\"child_count\":%u", child_count);
}

static
bool endpoint_browse(
    ecs_world_t *world,
    ecs_entity_t entity,
    EcsHttpEndpoint *endpoint,
    EcsHttpRequest *request,
    EcsHttpReply *reply)
{
    ecs_filter_t filter = { 0 };
    ecs_entity_t e;

    if (!parse_entity(world, request, &e)) {
        return false;
    }

    if (!parse_filter(world, request, &filter)) {
        return false;
    }

    ecs_iter_t it = ecs_scope_iter_w_filter(world, e, &filter);
    ecs_strbuf_t str = ECS_STRBUF_INIT;

    ecs_strbuf_list_push(&str, "[", ",");
    while (ecs_scope_next(&it)) {
        int32_t i;
        for (i = 0; i < it.count; i ++) {
            ecs_strbuf_list_next(&str);
            ecs_strbuf_list_push(&str, "{", ",");
            serialize_browse_info(world, e, it.entities[i], &str);
            ecs_strbuf_list_pop(&str, "}");
        }
    }
    ecs_strbuf_list_pop(&str, "]");

    reply->body = ecs_strbuf_get(&str);

    return true;
}

static
bool endpoint_info(
    ecs_world_t *world,
    ecs_entity_t entity,
    EcsHttpEndpoint *endpoint,
    EcsHttpRequest *request,
    EcsHttpReply *reply)
{
    ecs_entity_t e;

    if (!parse_entity(world, request, &e)) {
        return false;
    }

    ecs_strbuf_t str = ECS_STRBUF_INIT;
    ecs_strbuf_list_push(&str, "{", ",");
    serialize_browse_info(world, e, e, &str);
    ecs_strbuf_list_pop(&str, "}");

    reply->body = ecs_strbuf_get(&str);

    return true;
}

static
void EcsRestSetServer(ecs_iter_t *it) {
    EcsRestServer *server_data = ecs_column(it, EcsRestServer, 1);
    ecs_entity_t ecs_typeid(EcsHttpServer) = ecs_column_entity(it, 2);
    ecs_entity_t ecs_typeid(EcsHttpEndpoint) = ecs_column_entity(it, 3);
    ecs_entity_t EcsRestInitialized = ecs_column_entity(it, 4);

    ecs_world_t *world = it->world;

    int i;
    for (i = 0; i < it->count; i ++) {
        ecs_entity_t server = it->entities[i];

        // Get filtered list of entities
        ecs_set(world, server, EcsHttpServer, {.port = server_data[i].port});

        if (ecs_has_entity(world, server, EcsRestInitialized)) {
            /* Don't add endpoints more than once */
            continue;
        }

          ecs_entity_t e_filter = ecs_new_w_entity(world, ECS_CHILDOF | server);
            ecs_set(world, e_filter, EcsName, {"e_filter"});
            ecs_set(world, e_filter, EcsHttpEndpoint, {
                .url = "filter",
                .action = endpoint_filter,
                .synchronous = true,
                .ctx = NULL
            });
          
          // Get filtered list of children
          ecs_entity_t e_scope = ecs_new_w_entity(world, ECS_CHILDOF | server);
            ecs_set(world, e_scope, EcsName, {"e_scope"});
            ecs_set(world, e_scope, EcsHttpEndpoint, {
                .url = "scope",
                .action = endpoint_scope,
                .synchronous = true,
                .ctx = NULL
            });

          // Get component data for single entity
          ecs_entity_t e_entity = ecs_new_w_entity(world, ECS_CHILDOF | server);
            ecs_set(world, e_entity, EcsName, {"e_entity"});
            ecs_set(world, e_entity, EcsHttpEndpoint, {
                .url = "entity",
                .action = endpoint_entity,
                .synchronous = true,
                .ctx = NULL
            });  

          // Browse entity information
          ecs_entity_t e_browse = ecs_new_w_entity(world, ECS_CHILDOF | server);
            ecs_set(world, e_browse, EcsName, {"e_browse"});
            ecs_set(world, e_browse, EcsHttpEndpoint, {
                .url = "browse",
                .action = endpoint_browse,
                .synchronous = true,
                .ctx = NULL
            }); 

          // Browse entity information for single entity
          ecs_entity_t e_info = ecs_new_w_entity(world, ECS_CHILDOF | server);
            ecs_set(world, e_info, EcsName, {"e_info"});
            ecs_set(world, e_info, EcsHttpEndpoint, {
                .url = "info",
                .action = endpoint_info,
                .synchronous = true,
                .ctx = NULL
            });

        ecs_add_entity(world, server, EcsRestInitialized);
    }
}

void FlecsRestImport(
    ecs_world_t *world)
{
    ECS_MODULE(world, FlecsRest);

    ecs_set_name_prefix(world, "EcsRest");

    ECS_IMPORT(world, FlecsMeta);
    ECS_IMPORT(world, FlecsComponentsHttp);

    ECS_META(world, EcsRestServer);
    ECS_TAG(world, EcsRestInitialized);

    ECS_SYSTEM(world, EcsRestSetServer, EcsOnSet, Server, 
        :flecs.components.http.Server, 
        :flecs.components.http.Endpoint,
        :Initialized);

    ECS_EXPORT_COMPONENT(EcsRestServer);
}
