#include "systems.h"
#include "game_state.h"
#include "buildings.h"
#include "pathfinding.h"
#include <rlImGui.h>
#include <raymath.h>

bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Rectangle *outBounds);
void pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t **outEntities);
void pickEntities(BzSpatialGrid *entityGrid, Rectangle area, ecs_entity_t **outEntities);

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

void drawSelectionCircles(ecs_entity_t **selected);

static bool isInputDragged(InputState *input) {
    return IsMouseButtonDown(input->LMB && input->mouseDownElapsed > input->CLICK_LIMIT);
}
static bool wasInputDragged(InputState *input) {
    return IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed > input->CLICK_LIMIT;
}
static bool wasInputClicked(InputState *input) {
    return IsMouseButtonReleased(input->LMB);
}

void updatePlayerInput(ecs_iter_t *it) {
    f32 dt = GetFrameTime();
    ImGuiIO *io = igGetIO();
    if (io->WantCaptureMouse || io->WantCaptureKeyboard)
        return;

    Game *game = ecs_singleton_get_mut(ECS, Game);
    InputState *input = ecs_singleton_get_mut(ECS, InputState);
    if (IsKeyDown(KEY_W)) game->camera.target.y -= 20;
    if (IsKeyDown(KEY_S)) game->camera.target.y += 20;
    if (IsKeyDown(KEY_A)) game->camera.target.x -= 20;
    if (IsKeyDown(KEY_D)) game->camera.target.x += 20;

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


    game->camera.zoom += ((float) GetMouseWheelMove() * 0.05f);
    BzTileMap *map = &game->map;

    if (IsMouseButtonPressed(input->LMB)) {
        input->mouseDown = GetMousePosition();
        input->mouseDownWorld = GetScreenToWorld2D(input->mouseDown, game->camera);
        input->mouseDownElapsed = 0;
    } else if (IsMouseButtonDown(input->LMB)) {
        input->mouseDownElapsed += dt;
    }

    Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera);
    BzTile tileX = 0, tileY = 0;
    bzTileMapPosToTile(map, worldPos, &tileX, &tileY);
    switch (input->state) {
        case INPUT_NONE:
            if (wasInputDragged(input)) {
                if (bzArraySize(input->entities) > 0) {
                    input->state = INPUT_SELECTED_UNITS;
                    break;
                }

            } else if (wasInputClicked(input)) {
                // Click

                // 1. Entity
                if (bzArraySize(input->entities) > 0) {
                    input->state = INPUT_SELECTED_UNITS;
                    break;
                }

                // 2. Object

                // 3. Building
            }
            break;
        case INPUT_BUILDING:
            if (IsKeyPressed(input->ESC) ||
                IsMouseButtonPressed(input->RMB) ||
                input->building == 0) {
                input->state = INPUT_NONE;
                input->building = 0;
                break;
            }
            BZ_ASSERT(input->building);
            BzTile sizeX = 0, sizeY = 0;
            getBuildingSize(input->building, &sizeX, &sizeY);
            bool canPlace = canPlaceBuilding(&game->map, input->building, tileX, tileY);
            if (canPlace && IsMouseButtonDown(input->LMB)) {
                placeBuilding(&game->map, input->building, tileX, tileY);
            }
            input->buildingCanPlace = canPlace;
            input->buildingPos = (TilePosition) {tileX, tileY};
            input->buildingSize = (TileSize) {sizeX, sizeY};
            break;
        case INPUT_SELECTED_UNITS:
            if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB)) {
                bzArrayClear(input->entities);
                input->state = INPUT_NONE;
                break;
            }
            if (bzArraySize(input->entities) > 1 && wasInputDragged(input)) {
                // TODO: For click it should just move them
                i32 numUnits = bzArraySize(input->entities);
                f32 unitSpacing = 3.5f;
                bzArrayClear(input->unitPositions);
                placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos,
                           map, &input->unitPositions);

                BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits);

                bzArrayFor(input->unitPositions, i) {
                    Position target = bzArrayGet(input->unitPositions, i);
                    ecs_entity_t entity = bzArrayGet(input->entities, i);

                    const Position *start = ecs_get(ECS, entity, Position);
                    Path path = {NULL, 0};
                    pathfindAStar(&(PathfindingDesc) {
                            .start=*start,
                            .target=target,
                            .map=map,
                            .outPath=&path,
                            .pool=game->pools.pathData
                    });
                    if (!path.paths) continue;
                    ecs_set_ptr(ECS, entity, Path, &path);
                }
            } else if (wasInputClicked(input)) {
                bzArrayFor(input->entities, i) {
                    ecs_entity_t entity = bzArrayGet(input->entities, i);
                    ecs_remove(ECS, entity, Path);

                    const Position *start = ecs_get(ECS, entity, Position);
                    Path path = {NULL, 0};
                    pathfindAStar(&(PathfindingDesc) {
                            .start=*start,
                            .target=worldPos,
                            .map=map,
                            .outPath=&path,
                            .pool=game->pools.pathData
                    });
                    if (!path.paths) continue;
                    ecs_set_ptr(ECS, entity, Path, &path);
                }

            }
            break;
        case INPUT_SELECTED_OBJECT:
            break;
        case INPUT_SELECTED_BUILDING:
            break;
    }
}

void drawPlayerInputUI(ecs_iter_t *it) {
    Game *game = ecs_singleton_get_mut(ECS, Game);
    InputState *input = ecs_singleton_get_mut(ECS, InputState);
    BzTileMap *map = &game->map;
    Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera);

    switch (input->state) {
        case INPUT_NONE:
            if (isInputDragged(input)) {
                Vector2 start = input->mouseDownWorld;
                Vector2 end = worldPos;
                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;
                }
                Rectangle area = {start.x, start.y, end.x - start.x, end.y - start.y};
                DrawRectangleLines(area.x, area.y, area.width, area.height, RED);

                pickEntities(game->entityGrid, area, &input->entities);
            }
            break;
        case INPUT_BUILDING: {
            Color placeColor = input->buildingCanPlace ?
                               (Color) {0, 255, 0, 200} :
                               (Color) {255, 0, 0, 200};

            BzTile width = game->map.tileWidth;
            BzTile height = game->map.tileHeight;
            DrawRectangleLines(input->buildingPos.x * width,
                               input->buildingPos.y * height,
                               input->buildingSize.sizeX * width,
                               input->buildingSize.sizeY * height, placeColor);
            break;
        }
        case INPUT_SELECTED_UNITS: {
            if (bzArraySize(input->entities) > 1 && isInputDragged(input)) {
                i32 numUnits = bzArraySize(input->entities);
                f32 unitSpacing = 3.5f;
                bzArrayClear(input->unitPositions);
                placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos,
                           map, &input->unitPositions);

                BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits);
                bzArrayFor(input->unitPositions, i) {
                    Position pos = bzArrayGet(input->unitPositions, i);
                    DrawCircle(pos.x, pos.y, 2.0f, ORANGE);
                }
            }

            break;
        }
        case INPUT_SELECTED_OBJECT:
            break;
        case INPUT_SELECTED_BUILDING:
            break;
    }
    drawSelectionCircles(&input->entities);



}

bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Rectangle *outBounds) {
    if (!ecs_is_alive(ECS, entity))
        return false;
    const Position *pos = ecs_get(ECS, entity, Position);
    if (!pos)
        return false;
    const Size *size = ecs_get(ECS, entity, Size);
    if (!size)
        return false;

    if (outPos) {
        *outPos = *pos;
    }
    if (outSize) {
        *outSize = *size;
    }
    if (outBounds) {
        *outBounds = (Rectangle) {
                pos->x - size->x * 0.5f,
                pos->y - size->y * 0.5f,
                size->x, size->y
        };
    }
    return true;
}

void pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t **outEntities) {
    BZ_ASSERT(*outEntities);
    bzArrayClear(*outEntities);
    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;
        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;
        }
    }
    if (closest) {
        bzArrayPush(*outEntities, closest);
    }
}

void pickEntities(BzSpatialGrid *entityGrid, Rectangle area, ecs_entity_t **outEntities) {
    BZ_ASSERT(*outEntities);
    bzArrayClear(*outEntities);

    BzSpatialGridIter it = bzSpatialGridIter(entityGrid, area.x, area.y, area.width, area.height);
    while (bzSpatialGridQueryNext(&it)) {
        ecs_entity_t entity = *(ecs_entity_t *) it.data;
        Rectangle bounds;
        if (!getEntityBounds(entity, NULL, NULL, &bounds)) continue;

        if (!CheckCollisionRecs(area, bounds)) continue;
        bzArrayPush(*outEntities, 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;
    }
}

void drawSelectionCircles(ecs_entity_t **selected) {
    bzArrayFor(*selected, i) {
        ecs_entity_t entity = bzArrayGet(*selected, i);
        Position pos = {0};
        if (!getEntityBounds(entity, &pos, NULL, NULL))
            continue;
        DrawCircleLines(pos.x, pos.y, 4.5f, GREEN);


    }
}
