#include <rlImGui.h>

#include "systems.h"
#include "utils/building_types.h"
#include "components.h"
#include "game_state.h"
#include "map_init.h"
#include "map_layers.h"
#include "buildings.h"

#include "pathfinding.h"

#include <time.h>
#include <raymath.h>

ECS_COMPONENT_DECLARE(Game);
ECS_COMPONENT_DECLARE(InputState);

ecs_world_t *ECS = NULL;

static ecs_entity_t renderCollidersSystem;
static ecs_entity_t renderDebugPathSystem;

bool init(void *userData);
void deinit(void *userData);

void update(float dt, void *userData);
void render(float dt, void *userData);
void imguiRender(float dt, void *userData);


bool bzMain(BzAppDesc *appDesc, int argc, const char **argv) {
    appDesc->width = 1280;
    appDesc->height = 720;
    appDesc->title = "PixelDefense";
    appDesc->fps = 60;

    appDesc->init = (BzAppInitFunc) init;
    appDesc->deinit = (BzAppDeinitFunc) deinit;
    appDesc->update = (BzAppUpdateFunc) update;
    appDesc->render = (BzAppRenderFunc) render;
    appDesc->imguiRender = (BzAppRenderFunc) imguiRender;

    appDesc->userData = NULL;
    return true;
}

bool init(void *userData) {
    BZ_UNUSED(userData);
    SetExitKey(0);

    ECS = ecs_init();

    initComponentIDs(ECS);

    // For ecs explorer
    //ecs_singleton_set(ECS, EcsRest, {0});
    //ECS_IMPORT(ECS, FlecsMonitor);

    ECS_COMPONENT_DEFINE(ECS, Game);
    ecs_singleton_set(ECS, Game, {});
    Game *game = ecs_singleton_get_mut(ECS, Game);

    ECS_COMPONENT_DEFINE(ECS, InputState);
    ecs_singleton_set(ECS, InputState, {});
    InputState *input = ecs_singleton_get_mut(ECS, InputState);

    input->LMB = MOUSE_LEFT_BUTTON;
    input->RMB = MOUSE_RIGHT_BUTTON;
    input->MMB = MOUSE_MIDDLE_BUTTON;
    input->ESC = KEY_ESCAPE;
    input->CLICK_LIMIT = 0.2f;

    input->entities = bzArrayCreate(ecs_entity_t, 16);
    input->unitPositions = bzArrayCreate(Position, 16);


    // init pools
    game->pools.pathData = bzObjectPoolCreate(&(BzObjectPoolDesc) {
        .objectSize=sizeof(PathData),
        .numObjects=512
    });


    int screenWidth = 1280;
    int screenHeight = 720;

    game->camera = (Camera2D){ 0 };
    game->camera.target = (Vector2) {0, 0};
    game->camera.offset = (Vector2) {screenWidth / 2.0f, screenHeight / 2.0f};
    game->camera.rotation = 0.0f;
    game->camera.zoom = 1.0f;
    game->frameDuration = 0.16f;

    game->terrainTileset = bzTilesetCreate( &(BzTilesetDesc) {
        .path="assets/terrain.tsj",
        .texturePath="assets/terrain.png"
    });
    game->buildingsTileset = bzTilesetCreate(&(BzTilesetDesc) {
        .path="assets/buildings.tsj",
        .texturePath="assets/buildings.png"
    });
    game->entitiesTileset = bzTilesetCreate(&(BzTilesetDesc) {
        .path="assets/entities.tsj",
        .texturePath="assets/entities.png"
    });

    game->map = bzTileMapCreate(&(BzTileMapDesc) {
        .path="assets/maps/test.tmj",
        .generateCollisionMap=true,
        .tilesets[0]=game->terrainTileset,
        .tilesets[1]=game->buildingsTileset,
        .tilesets[2]=game->entitiesTileset,

        .layers[LAYER_TERRAIN]=(BzTileLayerDesc) {"Terrain", .applyColliders=true},
        .layers[LAYER_FOLIAGE]=(BzTileLayerDesc) {"Foliage"},
        .layers[LAYER_TREES]=(BzTileLayerDesc) {"Trees"},
        .layers[LAYER_TREES2]=(BzTileLayerDesc) {"TreesS"},
        .layers[LAYER_BUILDINGS]=(BzTileLayerDesc) {"Buildings", .applyColliders=true},
        .layers[LAYER_BUILDING_OWNER]=(BzTileLayerDesc) {"BuildingOwnership", BZ_TILE_LAYER_SKIP_RENDER},

        .objectGroups[OBJECTS_GAME]=(BzTileObjectsDesc) {"Game"},
        .objectGroups[OBJECTS_ENTITIES]=(BzTileObjectsDesc ) {"Entities"}
    });
    game->entityGrid = bzSpatialGridCreate(&(BzSpatialGridDesc) {
        .maxWidth=game->map.width * game->map.tileWidth,
        .maxHeight=game->map.height * game->map.tileHeight,
        .cellWidth=game->map.tileWidth * 4,
        .cellHeight=game->map.tileHeight * 4,
        .userDataSize=sizeof(ecs_entity_t)
    });

    bzTileMapOverrideLayer(&game->map, LAYER_TREES, initTreesLayer);
    bzTileMapOverrideLayer(&game->map, LAYER_TREES2, initTreesLayer);

    bzTileMapOverrideLayer(&game->map, LAYER_BUILDING_OWNER, initBuildingsLayer);

    bzTileMapOverrideObjectGroup(&game->map, OBJECTS_GAME, initGameObjectsLayer);
    bzTileMapOverrideObjectGroup(&game->map, OBJECTS_ENTITIES, initEntityObjectsLayer);

    ECS_OBSERVER(ECS, entitySpatialRemoved, EcsOnRemove, Position, SpatialGridID);
    ECS_OBSERVER(ECS, pathRemoved, EcsOnRemove, Path);

    ECS_SYSTEM(ECS, entityUpdateSpatialID, EcsOnUpdate, Position, Size, Velocity, SpatialGridID);
    ECS_SYSTEM(ECS, entityUpdateKinematic, EcsOnUpdate, Position, Rotation, Velocity, AngularVelocity, SteeringOutput);
    ECS_SYSTEM(ECS, entityFollowPath, EcsOnUpdate, Position, Rotation, Velocity, AngularVelocity, SteeringOutput, Path);

    ECS_SYSTEM(ECS, renderDebugPath, EcsOnUpdate, Path);

    ECS_SYSTEM(ECS, renderTerrain, EcsOnUpdate, Position, Size, Rotation, TextureRegion, TextureTerrain);
    ECS_SYSTEM(ECS, renderBuildings, EcsOnUpdate, Position, Size, Rotation, TextureRegion, TextureBuildings);
    ECS_SYSTEM(ECS, renderEntities, EcsOnUpdate, Position, Size, Rotation, TextureRegion, TextureEntities);

    ECS_SYSTEM(ECS, renderColliders, EcsOnUpdate, Position, Size);

    renderDebugPathSystem = renderDebugPath;
    renderCollidersSystem = renderColliders;

    game->debugDraw.mapColliders = true;
    game->debugDraw.spatialGrid = true;
    game->debugDraw.path = true;

    return true;
}
void deinit(void *userData) {
    BZ_UNUSED(userData);
    Game *game = ecs_singleton_get_mut(ECS, Game);
    InputState *input = ecs_singleton_get_mut(ECS, InputState);

    bzTileMapDestroy(&game->map);
    bzTilesetDestroy(&game->terrainTileset);
    bzTilesetDestroy(&game->buildingsTileset);
    bzTilesetDestroy(&game->entitiesTileset);

    Game gameCopy = *game;
    InputState inputCopy = *input;

    ecs_fini(ECS);
    ECS = NULL;

    bzArrayDestroy(inputCopy.entities);
    bzArrayDestroy(inputCopy.unitPositions);
    bzObjectPoolDestroy(gameCopy.pools.pathData);
    bzSpatialGridDestroy(gameCopy.entityGrid);
}


void update(float dt, void *userData) {
    BZ_UNUSED(userData);

    char titleBuf[32];
    snprintf(titleBuf, sizeof(titleBuf), "FPS: %d | %.2f ms", GetFPS(), GetFrameTime() * 1000);
    SetWindowTitle(titleBuf);

    updatePlayerInput(NULL);
}

void render(float dt, void *userData) {
    BZ_UNUSED(userData);
    Game *game = ecs_singleton_get_mut(ECS, Game);
    InputState *input = ecs_singleton_get_mut(ECS, InputState);

    ClearBackground(RAYWHITE);
    BeginMode2D(game->camera);

    bzTileMapDraw(&game->map);

    drawPlayerInputUI(NULL);

    ecs_progress(ECS, dt);
    ecs_enable(ECS, renderDebugPathSystem, game->debugDraw.path);
    ecs_enable(ECS, renderCollidersSystem, game->debugDraw.entityColliders);
    if (game->debugDraw.mapColliders)
        bzTileMapDrawCollisions(&game->map);
    if (game->debugDraw.spatialGrid)
        bzSpatialGridDrawDebugGrid(game->entityGrid);

    EndMode2D();
}

void imguiRender(float dt, void *userData) {
    BZ_UNUSED(userData);
    Game *game = ecs_singleton_get_mut(ECS, Game);
    InputState *input = ecs_singleton_get_mut(ECS, InputState);

    igSetNextWindowSize((ImVec2){300, 400}, ImGuiCond_FirstUseEver);
    igBegin("Debug Menu", NULL, 0);
    igText("Num paths from pool available: %llu", bzObjectPoolCalcNumFree(game->pools.pathData));
    const char *inputState = "NONE";
    switch (input->state) {
        case INPUT_NONE:
            inputState = "NONE";
            break;
        case INPUT_BUILDING:
            inputState = "BUILDING";
            break;
        case INPUT_SELECTED_UNITS:
            inputState = "SELECTED_UNITS";
            break;
        case INPUT_SELECTED_OBJECT:
            inputState = "SELECTED_OBJECT";
            break;
        case INPUT_SELECTED_BUILDING:
            inputState = "SELECTED_BUILDING";
            break;
    }
    igText("Input state: %s", inputState);
    if (igCollapsingHeader_TreeNodeFlags("Selection", 0)) {
        switch (input->state) {
            case INPUT_SELECTED_UNITS:
                igText("Selected units:");
                for (i32 i = 0; i < bzArraySize(input->entities); i++) {
                    igText("\t%llu", bzArrayGet(input->entities, i));
                }
                break;
            case INPUT_SELECTED_OBJECT:
                break;
            case INPUT_SELECTED_BUILDING:
                break;
            default:
                igText("NONE");
                break;
        }

    }
    if (igCollapsingHeader_TreeNodeFlags("Resources", 0)) {
        igText("Wood:       %lld", game->resources.wood);
        igText("Iron:       %lld", game->resources.iron);
        igText("Food:       %lld", game->resources.food);
        igText("Gold:       %lld", game->resources.gold);
        igText("Population: %lld", game->resources.pop);
    }
    if (igCollapsingHeader_TreeNodeFlags("BuildMenu", 0)) {
        for (int i = 0; i < BUILDINGS_COUNT; i++) {
            if (igSelectable_Bool(getBuildingStr(i), input->building == i, 0, (ImVec2){0, 0}))
                input->building = i;
        }
        if (input->building)
            input->state = INPUT_BUILDING;

    }
    if (igCollapsingHeader_TreeNodeFlags("DebugDraw", 0)) {
        igCheckbox("map colliders", &game->debugDraw.mapColliders);
        igCheckbox("entity colliders", &game->debugDraw.entityColliders);
        igCheckbox("spatial grid", &game->debugDraw.spatialGrid);
        igCheckbox("path", &game->debugDraw.path);

    }
    if (igCollapsingHeader_TreeNodeFlags("Entities", 0)) {
        igSliderFloat("Frame duration", &game->frameDuration, 0.0f, 1.0f, NULL, 0);
    }
    igEnd();
}

