#include "simulation_world.h"

#include <string.h>
#include <stdio.h>
#include <assert.h>

#define SIM_COMMANDS_PER_PLAYER 4

Simulation_World simulation_create_world(uint32_t seed, uint8_t max_players, uint16_t width, uint16_t height) {
    Simulation_World w;
    memset(&w, 0, sizeof(Simulation_World));

    w.tick = 0;
    
    w.game_world    = game_world_create(seed, width, height);
    assert(w.game_world != NULL);
    
    w.match_state   = Simulation_Match_State_Waiting_To_Start;
    
    w.players       = (Simulation_Player *)calloc(max_players, sizeof(Simulation_Player));
    w.max_players   = max_players;
    
    w.max_commands  = w.max_players * SIM_COMMANDS_PER_PLAYER;
    w.command_count = 0;
    w.commands      = (Simulation_Command *)calloc(w.max_commands, sizeof(Simulation_Command));

    return w;
}

void simulation_destroy_world(Simulation_World *simulation_world) {
    assert(simulation_world != NULL);
    
    game_world_destroy(simulation_world->game_world);
    simulation_world->game_world = NULL;
    
    simulation_world->match_state = Simulation_Match_State_Waiting_To_Start;
    
    free(simulation_world->players);
    simulation_world->players = NULL;
    
    free(simulation_world->commands);
    simulation_world->commands = NULL;
}

void simulation_world_tick(Simulation_World *simulation_world) {
    assert(simulation_world != NULL);

    // printf("TICK: %lu.\n", simulation_world->tick);

    for(uint16_t i = 0; i < simulation_world->command_count; i++) {
        Simulation_Command *cmd = &simulation_world->commands[i];
        // printf("Command type: %i, player id: %i.\n", cmd->type, cmd->player_id);

        Simulation_Player *player = &simulation_world->players[cmd->player_id];
        assert(player != NULL);

        switch(cmd->type) {
            case Simulation_Command_Type_Add_Player: {
                // printf("Simulation_Command_Type_Add_Player\n");
                assert(!player->active);
                player->active = true;

                assert(game_world_create_entity(
                    simulation_world->game_world,
                    Entity_Type_Snake_Head,
                    i+6, 15, // NOTE: SS - Hardcoded spawn-position.
                    &player->entity_id
                ));

                Entity *player_entity = game_world_try_get_entity_by_id(simulation_world->game_world, player->entity_id);

                // Set initial move-direction for player-entities.
                // The initial direction should probably take the spawn-position in to account.
                player_entity->move_direction = Entity_Movement_Direction_Up;

                break;
            }
            case Simulation_Command_Type_Remove_Player: {
                // printf("Simulation_Command_Type_Remove_Player\n");
                assert(player->active);
                player->active = false;

                game_world_destroy_entity(simulation_world->game_world, player->entity_id);
                
                break;
            }
            case Simulation_Command_Type_Set_Player_Input: {
                // printf("Simulation_Command_Type_Set_Player_Input\n");
                assert(player->active);
                player->input = cmd->player_input;
                
                break;
            }
        }
    }
    simulation_world->command_count = 0;

    switch(simulation_world->match_state) {
        case Simulation_Match_State_Waiting_To_Start: {
            bool is_singleplayer = simulation_world->max_players == 1;
            
            if(is_singleplayer) {
                simulation_world->match_state = Simulation_Match_State_Counting_Down;
            }
            else {
                if(simulation_world_amount_of_active_players(simulation_world) > 1) {
                    simulation_world->match_state = Simulation_Match_State_Counting_Down;
                }
            }
            
            break;
        }
        case Simulation_Match_State_Counting_Down: {
            if(simulation_world->tick > 50) { // TEMP: SS - Hardcoded number and condition to break out of this state.
                simulation_world->match_state = Simulation_Match_State_Active;
            }
            
            break;
        }
        case Simulation_Match_State_Active: {
            if(simulation_world->tick > 500) { // TEMP: SS - Hardcoded number and condition to break out of this state.
                simulation_world->match_state = Simulation_Match_State_Ended;
            }
            else {

                Game_World *gw = simulation_world->game_world;
        
                // Loop over all players in the simulation.
                for(uint16_t i = 0; i < simulation_world->max_players; i++) {
                    Simulation_Player *player = &simulation_world->players[i];
                    if(!player->active) {
                        continue;
                    }
    
                    // printf("* Input for player %i - up: %i, down: %i, right: %i, left: %i.\n",
                    //     i,
                    //     player->input.up, player->input.down, player->input.right, player->input.left
                    // );
    
                    Entity *player_entity = game_world_try_get_entity_by_id(gw, player->entity_id);
                    assert(player_entity != NULL);
    
                    // Snakes can't go backwards. They have to turn.
    
                    switch(player_entity->move_direction) {
                        case Entity_Movement_Direction_None: {
                            break;
                        }
                        case Entity_Movement_Direction_Up:
                        case Entity_Movement_Direction_Down: // Snakes can only go right/left if it's currently moving up/down.
                        {
                            if(player->input.right) {
                                player_entity->move_direction = Entity_Movement_Direction_Right;
                            }
                            else if(player->input.left) {
                                player_entity->move_direction = Entity_Movement_Direction_Left;
                            }
    
                            break;
                        }
                        case Entity_Movement_Direction_Right:
                        case Entity_Movement_Direction_Left: // Snakes can only go up/down if it's currently moving right/left.
                        {
                            if(player->input.up) {
                                player_entity->move_direction = Entity_Movement_Direction_Up;
                            }
                            else if(player->input.down) {
                                player_entity->move_direction = Entity_Movement_Direction_Down;
                            }
    
                            break;
                        }
                    }
                }
                
                // Game-logic! :)
    
                for(uint16_t i = 0; i < gw->max_entities; i++) {
                    Entity *entity = &gw->entities[i];
                    if(!entity->active) {
                        continue;
                    }

                    switch(entity->type) {
                        case Entity_Type_Snake_Head: {

                            break;
                        }
                        case Entity_Type_Snake_Body: {
                            // Skip.
                            break;
                        }
                        case Entity_Type_Food: {
                            // Skip (for now).
                            break;
                        }
                        default: {
                            printf("Unhandled entity-type %i.\n", entity->type);
                            break;
                        }
                    }
    
                    { // Move entities.
                        // Entity *entity_to_follow = entity;
                        // if(entity->parent != INVALID_ENTITY_ID) {
                        //     entity_to_follow = game_world_try_get_entity_by_id(gw, entity->parent);
                        //     assert(entity_to_follow != NULL);

                        //     entity->move_direction = entity_to_follow->prev_move_direction;
                        // }
                        
                        int16_t dx = 0;
                        int16_t dy = 0;
    
                        switch(entity->move_direction) {
                            case Entity_Movement_Direction_None: {
                                break;
                            }
                            case Entity_Movement_Direction_Up: {
                                dy = -1;
                                break;
                            }
                            case Entity_Movement_Direction_Down: {
                                dy = 1;
                                break;
                            }
                            case Entity_Movement_Direction_Right: {
                                dx = 1;
                                break;
                            }
                            case Entity_Movement_Direction_Left: {
                                dx = -1;
                                break;
                            }
                        }

                        entity->prev_move_direction = entity->move_direction;
    
                        if(dx != 0 || dy != 0) {
                            // Try moving.
    
                            // Figure out what cell we're on and what cell we want to go to.
                            Grid_Cell *current_cell = grid_get_cell(&gw->grid, entity->x, entity->y);
                            Grid_Cell *start_cell = current_cell;
                            assert(current_cell != NULL);
                            Grid_Cell *target_cell  = grid_get_cell(&gw->grid, entity->x + dx, entity->y + dy);
    
                            // { // Debug-logging.
                            //     printf("Current cell = (x: %u, y: %u, entity: %p).\n", current_cell->x, current_cell->y, current_cell->entity);
                            //     if(target_cell != NULL) {
                            //         printf("Target cell = (x: %u, y: %u, entity: %p).\n", target_cell->x, target_cell->y, target_cell->entity);
                            //     }
                            //     else {
                            //         printf("Target cell = NULL!\n");
                            //     }
                            // }
    
                            if(target_cell == NULL) {
                                // Target cell does not exist.
                                // TODO: SS - Check the session's settings.
                                // Maybe each session could decide whether the snake should "loop-around" or not.
                                // If the snake shouldn't loop around, die.
                            }
                            else {
                                bool should_move_to_target_cell = false;
                                bool snake_should_grow = false;

                                if(target_cell->entity != NULL) {
                                    // Target cell is occupied.
    
                                    // Check what type of entity it is and determine what should happen.
                                    switch(target_cell->entity->type) {
                                        case Entity_Type_Snake_Head: {
                                            // TODO: SS - Die.
                                            break;
                                        }
                                        case Entity_Type_Snake_Body: {
                                            // TODO: SS - Die.
                                            break;
                                        }
                                        case Entity_Type_Food: {
                                            // Eat!
                                            assert(grid_try_remove_entity_from_cell(target_cell));

                                            snake_should_grow = true;
                                            should_move_to_target_cell = true;

                                            break;
                                        }
                                    }
                                }
                                else {
                                    // Target cell is unoccupied and free to enter.
                                    should_move_to_target_cell = true;
                                }
                                
                                if(should_move_to_target_cell) {
                                    { // Move snake recursively.
                                        Entity *e = entity;
                                        Grid_Cell *t = target_cell;
                                        while(e != NULL) {
                                            Grid_Cell *a = grid_get_cell(&gw->grid, e->x, e->y);
                                            assert(a != NULL);
                                            
                                            grid_move_entity_from_cell_to_cell(a, t);

                                            e = game_world_try_get_entity_by_id(gw, e->child);
                                            t = a;
                                        }
                                    }
                                    // // TODO: SS - Recurse through this entity's children and move them too.
                                    // if(entity->child != INVALID_ENTITY_ID) {
                                    //     printf("TODO: SS - MOVE CHILDREN TOO!\n");
                                    // }


                                    if(snake_should_grow) {
                                        // TODO: SS - Spawn the entity and make it a child of the snake's last child.
                                        Entity_ID child_entity = INVALID_ENTITY_ID;
                                        assert(game_world_create_entity(gw, Entity_Type_Snake_Body, start_cell->x, start_cell->y, &child_entity));

                                        assert(entity->child == INVALID_ENTITY_ID);
                                        entity->child = child_entity;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            
            break;
        }
        case Simulation_Match_State_Ended: {
            printf("Match over!\n");
            
            break;
        }
    }

    simulation_world->tick += 1; // TODO: SS - Stop ticking if match has ended.
}

bool simulation_world_enqueue_command(Simulation_World *simulation_world, Simulation_Command command) {
    assert(simulation_world != NULL);

    if(simulation_world->command_count >= simulation_world->max_commands) {
        return false;
    }
    
    simulation_world->commands[simulation_world->command_count] = command;
    simulation_world->command_count += 1;
    
    return true;
}

uint16_t simulation_world_amount_of_active_players(Simulation_World *simulation_world) {
    assert(simulation_world != NULL);
    
    uint16_t count = 0;

    for(uint16_t i = 0; i < simulation_world->max_players; i++) {
        Simulation_Player *player = &simulation_world->players[i];
        if(player->active) {
            count += 1;
        }
    }
    
    return count;
}