#include "game_instance.h"

#include <assert.h>

void game_instance_init(Game_Instance *instance) {
    instance->amount_of_local_players = 0;
    instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context.
    instance->game_session = NULL;
    memset(&instance->game_networking, 0, sizeof(Game_Networking));

    for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) {
        Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i];
        squeue_init(
            &locally_controlled_player->input_queue,
            MAX_INPUTS_IN_QUEUE_PER_LOCAL_PLAYER,
            sizeof(Simulation_Game_Input)
        );
    }
}

void game_instance_dispose(Game_Instance *instance) {
    for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) {
        Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i];
        squeue_free(&locally_controlled_player->input_queue);
    }
}

bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings) {
    assert(instance != NULL);
    
    printf("Trying to host an %s session ...\n", settings.online ? "online" : "offline");

    if(instance->game_session != NULL) {
        printf("Failed. A session is already active.\n");
        return false;
    }

    if(settings.max_players == 0) {
        printf("Failed. Max-players is not allowed to be 0.\n");
        return false;
    }

    instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session));

    if(settings.online) {
        if(instance->game_networking.api != NULL) {
            printf("Failed. Network is already active.\n");
            free(instance->game_session);
            instance->game_session = NULL;
            return false;
        }
        
        uint16_t host_player_id = 0xFFFF;
        if(!networking_try_host(&instance->game_networking, instance->game_session, settings, &host_player_id)) {
            printf("Failed to host session.\n");
            
            free(instance->game_session);
            instance->game_session = NULL;
            return false;
        }

        game_instance_clear_local_players(instance);
        assert(game_instance_setup_local_player(instance, host_player_id));
    }
    else {
        if(settings.max_players > MAX_LOCAL_PLAYERS) {
            printf("Failed. Max amount of players (when playing locally) is %u. Max amount of players in the settings is %u.\n", MAX_LOCAL_PLAYERS, settings.max_players);
            free(instance->game_session);
            instance->game_session = NULL;
            return false;
        }
        
        game_session_init(instance->game_session, settings);

        // When playing locally/offline, create the player(s) here.
        game_instance_clear_local_players(instance);
        for(uint32_t i = 0; i < settings.max_players; i++) {
            uint16_t player_id = 0xFFFF;
            assert(game_session_create_player(instance->game_session, &player_id));
            assert(game_instance_setup_local_player(instance, player_id));
        }

        printf("Created %u local players.\n", settings.max_players);
    }
        
    return true;
}

bool game_instance_join_session(Game_Instance *instance, const char *session_id) {
    assert(instance != NULL);

    printf("Trying to join a session '%s' ...\n", session_id);

    if(instance->game_session != NULL) {
        printf("Failed. A session is already active.\n");
        return false;
    }

    if(instance->game_networking.api != NULL) {
        printf("Failed. Network is already active.\n");
        return false;
    }

    instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session));

    if(!networking_try_join(&instance->game_networking, instance->game_session, session_id)) {
        free(instance->game_session);
        instance->game_session = NULL;
        return false;
    }

    return true;
}

void game_instance_stop_session(Game_Instance *instance) {
    assert(instance != NULL);
    assert(instance->game_session != NULL);

    Game_Session *session = instance->game_session;
    Game_Networking *networking = &instance->game_networking;

    printf("Stopping session ...\n");

    if(session->settings.online) { // Are we online? Offline?
        printf("Session is online (session-id: '%s', host? %s).\n", networking->session_id, networking->is_host ? "true" : "false");
        networking_stop(networking);
    }
    else {
        printf("Session is offline/local.\n");
    }

    game_session_dispose(instance->game_session);
    free(instance->game_session);
    instance->game_session = NULL;
}

void game_instance_clear_local_players(Game_Instance *instance) {
    instance->amount_of_local_players = 0;
}

bool game_instance_setup_local_player(Game_Instance *instance, uint16_t player_id) {
    if(instance->amount_of_local_players >= MAX_LOCAL_PLAYERS) {
        return false;
    }

    Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[instance->amount_of_local_players];
    assert(locally_controlled_player != NULL);
    instance->amount_of_local_players += 1;

    locally_controlled_player->player_id = player_id;
    squeue_clear(&locally_controlled_player->input_queue);

    return true;
}

bool game_instance_push_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input input) {
    assert(local_player_index < MAX_LOCAL_PLAYERS);
    assert(local_player_index < instance->amount_of_local_players);

    Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index];
    assert(locally_controlled_player != NULL);

    return squeue_push(&locally_controlled_player->input_queue, (void *)&input);
}

bool game_instance_pop_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input *out_input) {
    assert(local_player_index < MAX_LOCAL_PLAYERS);
    assert(local_player_index < instance->amount_of_local_players);

    Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index];
    assert(locally_controlled_player != NULL);
    
    Simulation_Game_Input input = {0};
    if(!squeue_pop(&locally_controlled_player->input_queue, (void *)&input)) {
        return false;
    }

    *out_input = input;
    return true;
}