#include "state_ingame.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>

#include "raylib.h"
#include "raygui.h"

#include "session/game_session.h"

#include "shared/wang_hash.h"

#define GRID_CELL_SIZE 8
#define ENTITY_PRESENTATION_Y_OFFSET -2

static void state_enter(Presentation_State *state) {
    Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
    (void)ctx;

    printf("Entered ingame.\n");

    printf("Setting up session ...\n");
    
    assert(g_current_session != NULL);
    Game_Session_Settings *settings = &g_current_session->settings;
    printf(
        "Singleplayer? %i.\n"
        "Settings:\n"
        "- Seed: %u\n"
        "- Level width: %u, height: %u\n"
        "- Max players: %u\n"
        ,
        g_current_session->is_singleplayer,
        settings->seed,
        settings->level_width, settings->level_height,
        settings->max_players
    );
    
    
    printf("Setting up context ...\n");
    ctx->main_camera = (Camera2D) {
        .offset     = (Vector2) { 0, 0 },
        .target     = (Vector2) { 0, 0 },
        .rotation   = 0.0f,
        .zoom       = 3.0f
    };

    // TODO: SS - Maybe put the textures in an array and index them using an enum?
    // These should probably be loaded at start-up instead of here.
    ctx->texture_shadow_basic = LoadTexture("assets/sprites/spr_shadow_basic.png");
    assert(IsTextureValid(ctx->texture_shadow_basic));
    ctx->texture_grass = LoadTexture("assets/sprites/spr_floor_grass.png");
    assert(IsTextureValid(ctx->texture_grass));
    ctx->texture_apple = LoadTexture("assets/sprites/spr_food_apple.png");
    assert(IsTextureValid(ctx->texture_apple));
    ctx->texture_snake_head = LoadTexture("assets/sprites/spr_snake_head.png");
    assert(IsTextureValid(ctx->texture_snake_head));
    ctx->texture_snake_body = LoadTexture("assets/sprites/spr_snake_body.png");
    assert(IsTextureValid(ctx->texture_snake_body));
}

static void state_tick(Presentation_State *state) {
    Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
    (void)ctx;
    
    Game_Session *session = g_current_session;

    { // Push local input to queue.
        game_session_enqueue_player_input(session, session->local_player_index, (Simulation_Game_Input) {
            .up     = IsKeyDown(KEY_UP)     || IsKeyDown(KEY_W),
            .down   = IsKeyDown(KEY_DOWN)   || IsKeyDown(KEY_S),
            .right  = IsKeyDown(KEY_RIGHT)  || IsKeyDown(KEY_D),
            .left   = IsKeyDown(KEY_LEFT)   || IsKeyDown(KEY_A)
        });
    }

    double delta_time = GetFrameTime();
    game_session_update(session, delta_time);
    
    { // TEMP: SS
        if(IsKeyPressed(KEY_ESCAPE)) {
            presentation_state_machine_go_to(&presentation_state_main_menu);
        }
    }
}

uint32_t floor_texture_variant(uint32_t x, uint32_t y, uint32_t seed, uint32_t num_variants) {
    return (wang_hash(x * 73856093u ^ y * 19349663u ^ seed)) % num_variants;
}

static void state_render(Presentation_State *state) {
    Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
    (void)ctx;

    ClearBackground((Color) { 240, 236, 226, 255 });

    Simulation_World *sim_world = &g_current_session->simulation_world;
    assert(sim_world != NULL);

    Game_World *world = sim_world->game_world;
    assert(world != NULL);
    Grid *grid = &world->grid;

    uint32_t grid_total_size = grid->width * grid->height;

    BeginMode2D(ctx->main_camera); {
        // Render the level.
        for(uint32_t i = 0; i < grid_total_size; i++) {
            uint32_t x = i % grid->width;
            uint32_t y = i / grid->width;

            uint32_t pres_x = x * GRID_CELL_SIZE;
            uint32_t pres_y = y * GRID_CELL_SIZE;

            uint32_t random_floor_index = floor_texture_variant(x, y, world->seed, 4);

            DrawTextureRec(
                ctx->texture_grass,
                (Rectangle) { GRID_CELL_SIZE * random_floor_index, 0, GRID_CELL_SIZE, GRID_CELL_SIZE },
                (Vector2) { pres_x, pres_y },
                WHITE
            );
            DrawRectangleLines(pres_x, pres_y, GRID_CELL_SIZE, GRID_CELL_SIZE, (Color) { 0, 0, 0, 2 }); // TODO: SS - Let the user customize the alpha locally.
        }
        
        for(uint32_t i = 0; i < grid_total_size; i++) {
            uint32_t x = i % grid->width;
            uint32_t y = i / grid->width;

            uint32_t pres_x = x * GRID_CELL_SIZE;
            uint32_t pres_y = y * GRID_CELL_SIZE;

            Grid_Cell *cell = &grid->cells[i];
            if(cell->entity != NULL) {
                Entity *entity = cell->entity;
                switch(entity->type) {
                    case Entity_Type_Snake_Head:
                    case Entity_Type_Snake_Body: {
                        Color tint = (Color) { 255, 135, 102, 255 }; // TODO: SS - Get tint based on player ID.
    
                        Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 };
    
                        float sin_frequency = 0.0f;
                        float sin_amplitude = 0.0f;
    
                        Texture2D *texture = NULL;
                        if(entity->type == Entity_Type_Snake_Head) {
                            texture = &ctx->texture_snake_head;
                        }
                        else {
                            texture = &ctx->texture_snake_body;
                            // TODO: SS - If it's a body, check what index it is and use that as an y-offset to make it look cool, like a wave.
                            sin_frequency = 4.0f;
                            sin_amplitude = 1.0f;
                        }
    
                        // Draw shadow.
                        DrawTextureEx(
                            ctx->texture_shadow_basic,
                            (Vector2) { pres_x, pres_y },
                            0.0f,
                            1.0f,
                            (Color) { 0, 0, 0, 32 }
                        );
                        
                        float rotation = 0.0f;
                        switch(entity->move_direction){
                            case Entity_Movement_Direction_None: {
                                rotation = 0.0f;
                                break;
                            }
                            case Entity_Movement_Direction_Up: {
                                rotation = -90.0f;
                                break;
                            }
                            case Entity_Movement_Direction_Down: {
                                rotation = 90.0f;
                                break;
                            }
                            case Entity_Movement_Direction_Right: {
                                rotation = 0.0f;
                                break;
                            }
                            case Entity_Movement_Direction_Left: {
                                rotation = 180.0f;
                                break;
                            }
                        }
                        
                        DrawTexturePro(
                            *texture,
                            (Rectangle) { // Source.
                                0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE
                            },
                            (Rectangle) { // Destination.
                                pres_x + origin.x,
                                pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - (sin(GetTime() * sin_frequency)) * sin_amplitude,
                                GRID_CELL_SIZE,
                                GRID_CELL_SIZE
                            },
                            origin, // Origin.
                            rotation, // Rotation.
                            tint // Tint.
                        );
    
                        break;
                    }
                    case Entity_Type_Food: {
                        uint32_t flash = ((sin(GetTime() * 8) + 1)/2) * 32;
                        Color tint = (Color) { 255-flash, 255-flash, 255-flash, 255 };
    
                        Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 };
    
                        // Draw shadow.
                        DrawTextureEx(
                            ctx->texture_shadow_basic,
                            (Vector2) { pres_x, pres_y },
                            0.0f,
                            1.0f,
                            (Color) { 0, 0, 0, 32 }
                        );
    
                        DrawTexturePro(
                            ctx->texture_apple,
                            (Rectangle) { // Source.
                                0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE
                            },
                            (Rectangle) { // Destination.
                                pres_x + origin.x,
                                pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - ((sin(GetTime() * 12) + 1)/2) * 1,
                                GRID_CELL_SIZE,
                                GRID_CELL_SIZE
                            },
                            origin, // Origin.
                            0, // Rotation.
                            tint // Tint.
                        );
                        break;
                    }
                }
            }
        }

        // TODO: SS - Switch on 'sim_world->match_state'.

        { // TEMP: SS - Render match state.
            char buf[512];
            snprintf(&buf[0], sizeof(buf), "Match-state: %i. Tick: %lu.", sim_world->match_state, sim_world->tick);

            DrawText(buf, 32, 32, 12, RED);
        }
    }
    EndMode2D();
}

static void state_exit(Presentation_State *state) {
    Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
    (void)ctx;
    printf("Exiting ingame\n");

    UnloadTexture(ctx->texture_shadow_basic);
    UnloadTexture(ctx->texture_grass);
    UnloadTexture(ctx->texture_apple);
    UnloadTexture(ctx->texture_snake_head);
    UnloadTexture(ctx->texture_snake_body);

    game_session_destroy();
}

Presentation_State presentation_state_ingame;

void presentation_state_ingame_init(Presentation_State_Ingame_Context *ctx) {
    presentation_state_ingame = (Presentation_State) {
        .name = "Ingame",
        .context = (void *)ctx,
        .enter = state_enter,
        .tick = state_tick,
        .render = state_render,
        .exit = state_exit
    };
}