#include "game_session.h"

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

void game_session_init(Game_Session *session, Game_Session_Settings settings) {
    assert(session != NULL);

    session->settings = settings;

    session->simulation_world = simulation_create_world(
        session->settings.seed,
        session->settings.online,
        session->settings.max_players,
        session->settings.level_width,
        session->settings.level_height
    );

    session->players        = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player));
    session->players_prev   = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player));
}

void game_session_dispose(Game_Session *session) {
    if(session == NULL) {
        return;
    }

    simulation_destroy_world(&session->simulation_world);

    free(session->players);
    session->players = NULL;
    free(session->players_prev);
    session->players_prev = NULL;

    memset(session, 0, sizeof(Game_Session));
}

void game_session_init_default_settings(bool online, Game_Session_Settings *out_settings) {
    out_settings->seed         = 1337; // TODO: SS - Randomize.
    out_settings->online       = online;
    out_settings->tickrate     = 10.0;
    out_settings->level_width  = 64;
    out_settings->level_height = 48;
    out_settings->max_players  = online ? 8 : 2;
}

void game_session_tick(Game_Session *session) {
    for(uint16_t i = 0; i < session->settings.max_players; i++) {
        Game_Session_Player *session_player = &session->players[i];
        
        { // Check if any players have joined/disconnected by comparing against the previous session_player.
            Game_Session_Player *session_player_prev = &session->players_prev[i];
            
            bool was_active = session_player_prev->active;
            bool is_active  = session_player->active;
            
            if (!was_active && is_active) {
                simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) {
                    .type = Simulation_Command_Type_Add_Player,
                    .player_id = i
                });
            } else if (was_active && !is_active) {
                simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) {
                    .type = Simulation_Command_Type_Remove_Player,
                    .player_id = i
                });
            }
        }
        
        if(!session_player->active) {
            continue;
        }
        
        // Update input.
        simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) {
            .type = Simulation_Command_Type_Set_Player_Input,
            .player_id = i,
            .player_input = session_player->input,
        });
    }
    
    // Tick.
    simulation_world_tick(&session->simulation_world);

    // Copy 'players' from session to 'players_prev'.
    memcpy(session->players_prev, session->players, sizeof(Game_Session_Player) * session->settings.max_players);
}

static Game_Session_Player *game_session_get_player_from_id(Game_Session *session, uint16_t player_index) {
    assert(session != NULL);
    if(player_index >= session->settings.max_players) {
        return NULL;
    }
    
    return &session->players[player_index];
}

uint16_t game_session_get_amount_of_active_players(Game_Session *session) {
    assert(session != NULL);
    uint16_t amount = 0;

    for(uint16_t i = 0; i < session->settings.max_players; i++) {
        Game_Session_Player *player = &session->players[i];
        if(player->active) {
            amount += 1;
        }
    }
    
    return amount;
}

bool game_session_create_player(Game_Session *session, uint16_t *out_player_index) {
    assert(session != NULL);

    int32_t found_index = -1;
    for(uint16_t i = 0; i < session->settings.max_players; i++) {
        Game_Session_Player *session_player = &session->players[i];
        assert(session_player != NULL);
        if(!session_player->active) {
            found_index = i;

            session_player->active = true;
            break;
        }
    }

    if(found_index < 0) {
        return false;
    }

    *out_player_index = (uint16_t)found_index;
    return true;
}

void game_session_destroy_player(Game_Session *session, uint16_t player_index) {
    assert(session != NULL);
    assert(player_index < session->settings.max_players);

    Game_Session_Player *player = &session->players[player_index];
    assert(player != NULL);
    assert(player->active);

    memset(player, 0, sizeof(Game_Session_Player));
}

bool game_session_set_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input) {
    Game_Session_Player *player = game_session_get_player_from_id(session, player_index);
    assert(player != NULL);

    player->input = input;
    return true;
}