#include "systems.h"

#include "../game_state.h"
#include "../input.h"
#include "../buildings.h"
#include "../pathfinding.h"

#include <rlImGui.h>
#include <raymath.h>
#include <rlgl.h>

ecs_entity_t queryEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag);

bool selectEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag);
void selectUnits(BzSpatialGrid *entityGrid, Rectangle area);

void addEntityToInspected(ecs_entity_t entity, Game *game);

static void iterateSelectedUnits(ecs_query_t *query, void (*fn)(ecs_entity_t entity, Position *pos, Size *size));
static void iterRemovePaths(ecs_entity_t entity, Position *pos, Size *size);

void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces);

void resetInputState(InputState *input) {
    input->cursor   = CURSOR_NONE;
    input->state    = INPUT_NONE;
    input->building = BUILDING_NONE;
}

void inputPrimaryAction(Game *game, InputState *input) {
    const MouseButton primaryBtn = input->mapping.primaryBtn;
    if (isInputBtnDragged(input, primaryBtn)) {
        Vector2 start = input->mouseDownWorld;
        Vector2 end = input->mouseWorld;
        if (start.x > end.x) {
            f32 tmp = start.x;
            start.x = end.x;
            end.x = tmp;
        }
        if (start.y > end.y) {
            f32 tmp = start.y;
            start.y = end.y;
            end.y = tmp;
        }
        input->pickArea = (Rectangle) {start.x, start.y, end.x - start.x, end.y - start.y};
        selectUnits(game->entityGrid, input->pickArea);
    }
    i32 selectedCount = ecs_query_entity_count(input->queries.selected);
    if (isInputBtnJustDragged(input, primaryBtn) && selectedCount > 0) {
        resetInputState(input);
        input->state = INPUT_SELECTED_UNITS;
    }
    if (IsKeyDown(KEY_LEFT_ALT) && isInputBtnJustUp(input, primaryBtn)) {
        ecs_entity_t entity = queryEntity(game->entityGrid, input->mouseDownWorld, ecs_id(GameEntity));
        if (entity) addEntityToInspected(entity, game);
    } else if (isInputBtnJustUp(input, primaryBtn)) {
        InputType type = input->state;
        if (selectEntity(game->entityGrid, input->mouseDownWorld, ecs_id(Unit)))
            type = INPUT_SELECTED_UNITS;
        else if (selectEntity(game->entityGrid, input->mouseDownWorld, ecs_id(Harvestable)))
            type = INPUT_SELECTED_OBJECT;
        else if (selectEntity(game->entityGrid, input->mouseDownWorld, ecs_id(Building)))
            type = INPUT_SELECTED_BUILDING;
        selectedCount = ecs_query_entity_count(input->queries.selected);
        if (selectedCount > 0) {
            resetInputState(input);
            input->state = type;
        }
    }

    if (selectedCount == 0)
        resetInputState(input);
}
void inputUnitAction(Game *game, InputState *input) {
    ecs_query_t *query = input->queries.selected;
    BzTileMap   *map   = &game->map;
    const i32 numUnits = ecs_query_entity_count(query);
    BZ_ASSERT(numUnits > 0);

    const MouseButton actionBtn = input->mapping.secondaryBtn;

    input->cursor = CURSOR_NONE;
    ecs_entity_t taskEntity;
    if ((taskEntity = queryEntity(game->entityGrid, input->mouseWorld, Harvestable))) {
        input->cursor = CURSOR_COLLECT_WOOD;
        if (isInputBtnJustUp(input, actionBtn)) {
            ecs_defer_begin(ECS);
            ecs_iter_t it = ecs_query_iter(ECS, query);
            while (ecs_query_next(&it)) {
                for (i32 i = 0; i < it.count; i++) {
                    const ecs_entity_t entity = it.entities[i];
                    const Position target = *ecs_get(ECS, taskEntity, Position);
                    setAIBehaviour(entity, game->BTs.workerHarvest, &(AIBlackboard) {
                        .as.worker = {
                                .harvestType = RES_WOOD,
                                .harvestTarget = taskEntity,
                                .harvestPos = target,
                        },
                        .proximity = 6.0f,
                    });
                    /*
                    setUnitAI(entity, game, &(const UnitAI) {
                        .type = AI_WORKER_HARVEST,
                        .as.workerHarvest.resource = RES_WOOD,
                        .as.workerHarvest.target = taskEntity,
                        .as.workerHarvest.targetPosition = target
                    });
                     */
                    //addAction(entity, game, &(const Action) {
                    //    .type = ACTION_MOVE_TO,
                    //    .as.moveTo.target = target,
                    //    .as.moveTo.proximityThreshold = 10.0f,
                    //});
                    //ecs_set(ECS, entity, HarvestTask, {taskEntity});
                    goto while_break;
                }
            }
            while_break:
            ecs_iter_fini(&it);
            ecs_defer_end(ECS);
        }
        return;
    }

    if (isInputBtnJustUp(input, actionBtn)) {
        // Note: We mustn't use ecs ecs_remove_all since this will also
        // remove ongoing paths that are not part of this query.
        ecs_defer_begin(ECS);
        iterateSelectedUnits(query, iterRemovePaths);
        ecs_defer_end(ECS);

        const Position target = input->mouseWorld;

        ecs_iter_t it = ecs_query_iter(ECS, query);
        ecs_defer_begin(ECS);
        while (ecs_iter_next(&it)) {
            for (i32 i = 0; i < it.count; i++) {
                const ecs_entity_t entity = it.entities[i];
                setAIBehaviour(entity, game->BTs.moveTo, &(AIBlackboard) {
                    .moveToPos = target,
                    .proximity = 6.0f,
                });
                /*
                clearActions(entity, game);
                addAction(entity, game, &(const Action) {
                    .type = ACTION_MOVE_TO,
                    .as.moveTo.target = target,
                    .as.moveTo.proximityThreshold = 6.0f,
                });
                 */
            }
        }
        ecs_defer_end(ECS);
    }

}

void updatePlayerInput() {
    f32 dt = GetFrameTime();

    Game *game = ecs_singleton_get_mut(ECS, Game);
    InputState *input = ecs_singleton_get_mut(ECS, InputState);

    const f32 maxZoom = 4.5f;
    const f32 minZoom = 0.9f;
    if (input->canUseKeyboard) {
        float moveSpeed = 100.0f * dt * (1 + maxZoom - game->camera.zoom);
        if (IsKeyDown(KEY_W)) game->camera.target.y -= moveSpeed;
        if (IsKeyDown(KEY_S)) game->camera.target.y += moveSpeed;
        if (IsKeyDown(KEY_A)) game->camera.target.x -= moveSpeed;
        if (IsKeyDown(KEY_D)) game->camera.target.x += moveSpeed;

        if (IsKeyDown(KEY_Q)) game->camera.rotation--;
        if (IsKeyDown(KEY_E)) game->camera.rotation++;

        if (IsKeyReleased(input->mapping.backBtn)) {
            i32 selectedCount = ecs_query_entity_count(input->queries.selected);
            if (selectedCount == 0) {
                game->screen = SCREEN_PAUSE_MENU;
            } else {
                ecs_remove_all(ECS, Selected);
                resetInputState(input);
            }

        }

    }
    if (!input->canUseMouse)
        return;

    // https://gamedev.stackexchange.com/questions/9330/zoom-to-cursor-calculation
    float wheel = GetMouseWheelMove();
    if (wheel != 0.0f) {
        const float zoomIncrement = 0.125f;
        f32 oldZoom = game->camera.zoom;
        f32 newZoom = oldZoom + wheel * zoomIncrement;
        newZoom = Clamp(newZoom, minZoom, maxZoom);

        Vector2 mouse = GetMousePosition();
        f32 mapWidth = GetScreenWidth() / oldZoom;
        f32 mapHeight = GetScreenHeight() / oldZoom;
        f32 widthRatio = (mouse.x - (GetScreenWidth() / 2)) / GetScreenWidth();

        f32 newMapWidth = GetScreenWidth() / newZoom;
        f32 newMapHeight = GetScreenHeight() / newZoom;
        f32 heightRatio = (mouse.y - (GetScreenHeight() / 2)) / GetScreenHeight();

        game->camera.target.x += (mapWidth - newMapWidth) * widthRatio;
        game->camera.target.y += (mapHeight - newMapHeight) * heightRatio;

        game->camera.zoom = newZoom;
    }

    // Limit camera to world
    {
        const f32 zoom = game->camera.zoom;
        const f32 width = GetScreenWidth() / zoom;
        const f32 height = GetScreenHeight() / zoom;
        const f32 mapWidth = game->map.width * game->map.tileWidth;
        const f32 mapHeight = game->map.height * game->map.tileHeight;
        game->camera.target.x = Clamp(game->camera.target.x,
                                      width * 0.5f - game->map.tileWidth,
                                      mapWidth - width + game->map.tileWidth + width * 0.5f);
        game->camera.target.y = Clamp(game->camera.target.y,
                                      height * 0.5f - game->map.tileHeight,
                                      mapHeight - height + game->map.tileHeight + height * 0.5f);
    }


    BzTileMap *map = &game->map;

    Vec2i tileXY = bzTileMapPosToTile(map, input->mouseWorld);
    BzTile tileX = tileXY.x;
    BzTile tileY = tileXY.y;

    const MouseButton primaryBtn   = input->mapping.primaryBtn;
    const MouseButton secondaryBtn = input->mapping.secondaryBtn;

    switch (input->state) {
        case INPUT_NONE: {
            inputPrimaryAction(game, input);
            break;
        }
        case INPUT_BUILDING: {
            if (input->building <= BUILDING_NONE || input->building >= BUILDING_COUNT) {
                input->state = INPUT_NONE;
                return;
            }
            BzTile sizeX = 0, sizeY = 0;
            getBuildingSize(input->building, &sizeX, &sizeY);
            bool canPlace = canPlaceBuilding(game, input->building, tileX, tileY);
            if (canPlace && isInputBtnDown(input, primaryBtn)) {
                placeBuilding(game, input->building, tileX, tileY, (Owner) {-1});
            }
            input->buildingCanPlace = canPlace;
            input->buildingPos = (Vec2i) {tileX, tileY};
            input->buildingSize = (Vec2i) {sizeX, sizeY};
            break;
        }
        case INPUT_SELECTED_UNITS: {
            inputPrimaryAction(game, input);
            if (input->state != INPUT_SELECTED_UNITS) break;
            inputUnitAction(game, input);
            break;
        }
        case INPUT_SELECTED_OBJECT: {
            inputPrimaryAction(game, input);
            if (input->state != INPUT_SELECTED_OBJECT) break;
            break;
        }
        case INPUT_SELECTED_BUILDING: {
            inputPrimaryAction(game, input);
            if (input->state != INPUT_SELECTED_BUILDING) break;
            break;
        }

    }
}

void drawPlayerInputUIGround() {
    const Game       *game  = ecs_singleton_get_mut(ECS, Game);
    const InputState *input = ecs_singleton_get_mut(ECS, InputState);

    const MouseButton primaryBtn = input->mapping.primaryBtn;

    switch (input->state) {
        case INPUT_BUILDING: {
            const Color placeColor = input->buildingCanPlace ?
                               (Color) {0, 255, 0, 200} :
                               (Color) {255, 0, 0, 200};

            const BzTile width = game->map.tileWidth;
            const BzTile height = game->map.tileHeight;
            DrawRectangleLines(input->buildingPos.x * width,
                               input->buildingPos.y * height,
                               input->buildingSize.x * width,
                               input->buildingSize.y * height, placeColor);
            break;
        }
        default: break;
    }
    ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected);
    rlSetLineWidth(2.0f);
    while (ecs_query_next(&it)) {
        Position *pos  = ecs_field(&it, Position, 1);
        Size     *size = ecs_field(&it, Size, 2);
        for (i32 i = 0; i < it.count; i++) {
            ecs_entity_t entity = it.entities[i];
            f32 radius = size[i].x;
            if (size[i].y > radius)
                radius = size[i].y;
            radius *= 0.5f;
            const f32 lineThickness = 1.0f;
            if (ecs_has(ECS, entity, Building)) {
                const f32 padding = 2.0f;
                Rectangle bounds = {
                        pos[i].x - size[i].x * 0.5f - padding,
                        pos[i].y - size[i].y * 0.5f - padding,
                        size[i].x + padding * 2,
                        size[i].y + padding * 2,
                };
                DrawRectangleLinesEx(bounds, lineThickness, GREEN);
            } else {
                DrawRing(pos[i], radius, radius + lineThickness, 0, 360, 12, GREEN);
            }
        }
    }

}

void drawPlayerInputUI() {
    const Game       *game  = ecs_singleton_get_mut(ECS, Game);
    const InputState *input = ecs_singleton_get_mut(ECS, InputState);

    const MouseButton primaryBtn   = input->mapping.primaryBtn;

    if (isInputBtnDragged(input, primaryBtn)) {
        const Rectangle area = input->pickArea;
        DrawRectangleLines(area.x, area.y, area.width, area.height, RED);
    }

    switch (input->cursor) {
        case CURSOR_COLLECT_WOOD: {
            const Vector2 point = input->mouseWorld;
            DrawCircle(point.x, point.y, 2.0f, RED);
            DrawText("Collect wood", point.x, point.y, 10.0f, RED);
            break;
        }
        default: break;
    }

}

ecs_entity_t queryEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) {
    BzSpatialGridIter it = bzSpatialGridIter(entityGrid, point.x, point.y, 0.0f, 0.0f);
    f32 closestDst = INFINITY;
    ecs_entity_t closest = 0;
    while (bzSpatialGridQueryNext(&it)) {
        ecs_entity_t entity = *(ecs_entity_t *) it.data;
        if (!ecs_is_alive(ECS, entity)) continue;
        if (!ecs_has_id(ECS, entity, Selectable)) continue;
        if (!ecs_has_id(ECS, entity, tag)) continue;
        Vector2 pos;
        Rectangle bounds;
        if (!getEntityBounds(entity, &pos, NULL, &bounds)) continue;

        if (!CheckCollisionPointRec(point, bounds)) continue;

        f32 curDst = Vector2Distance(point, pos);
        if (closestDst > curDst) {
            closestDst = curDst;
            closest = entity;
        }
    }
    return closest;
}

bool selectEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) {
    ecs_remove_all(ECS, Selected);
    const ecs_entity_t entity = queryEntity(entityGrid, point, tag);
    if (entity) {
        ecs_add(ECS, entity, Selected);
        return true;
    }
    return false;
}

void selectUnits(BzSpatialGrid *entityGrid, Rectangle area) {
    ecs_remove_all(ECS, Selected);
    BzSpatialGridIter it = bzSpatialGridIter(entityGrid, area.x, area.y, area.width, area.height);
    while (bzSpatialGridQueryNext(&it)) {
        ecs_entity_t entity = *(ecs_entity_t *) it.data;
        if (!ecs_has_id(ECS, entity, ecs_id(Unit))) continue;
        Rectangle bounds;
        if (!getEntityBounds(entity, NULL, NULL, &bounds)) continue;

        if (!CheckCollisionRecs(area, bounds)) continue;
        ecs_add(ECS, entity, Selected);
    }
}

void addEntityToInspected(ecs_entity_t entity, Game *game) {
    bool alreadyInspecting = false;
    for (i32 i = 0; i < bzArraySize(game->debug.inspecting); i++) {
        if (game->debug.inspecting[i] == entity) {
            alreadyInspecting = true;
            break;
        }
    }
    if (!alreadyInspecting)
        bzArrayPush(game->debug.inspecting, entity);
}

static bool isUnitObstructed(f32 x, f32 y, BzTileMap *map) {
    return bzTileMapHasCollision(map, x / map->tileWidth, y / map->tileHeight);
}

static bool canPlaceUnit(Vector2 pos, f32 space, BzTileMap *map) {
    for (i32 y = -1; y <= 1; y++) {
        for (i32 x = -1; x <= 1; x++) {
            if (isUnitObstructed(pos.x + x * space, pos.y + y * space, map))
                return false;
        }
    }

    return true;
}

void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces) {
    f32 angle = Vector2Angle(start, end);

    f32 lineLength = Vector2Distance(start, end);

    Vector2 pos = Vector2Zero();
    pos.x = unitSpacing;

    for (i32 i = 0; i < numUnits; i++) {
        if (pos.x + unitSpacing * 2.0f > lineLength) {
            pos.x = unitSpacing;
            pos.y += unitSpacing * 2.0f;
        }
        Vector2 unitPos = Vector2Add(start, Vector2Rotate(pos, angle));
        if (!canPlaceUnit(unitPos, 4.0f, map)) {
            i--;
        } else {
            bzArrayPush(*outPlaces, unitPos);
        }
        pos.x += unitSpacing * 2.0f;
    }
}

static void iterateSelectedUnits(ecs_query_t *query, void (*fn)(ecs_entity_t entity, Position *pos, Size *size)) {
    ecs_iter_t it = ecs_query_iter(ECS, query);
    while (ecs_iter_next(&it)) {
        Position *pos  = ecs_field(&it, Position, 1);
        Size     *size = ecs_field(&it, Size, 2);

        for (i32 i = 0; i < it.count; i++) {
            ecs_entity_t entity = it.entities[i];

            fn(entity, pos + i, size + i);
        }
    }
}
static void iterRemovePaths(ecs_entity_t entity, Position *pos, Size *size) {
    ecs_remove(ECS, entity, Path);
}

