#include "program.h"
#include "camera.h"
#include "game_world.h"
#include "physics_world.h"
#include "time.h"
#include "assets.h"
#include "debug.h"
#include "input.h"
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_image.h>

SDL_Window* g_window;
static double _target_delta_time = 0.0;
static double _delta_time;
static double _frame_start;
static double _game_start_time;


#define INITFLAGS SDL_INIT_EVENTS | SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK

static inline
double tstos(struct timespec ts) {
    return (double)ts.tv_sec + (double)ts.tv_nsec * 1E-09;
}

struct timespec get_time() {
    struct timespec ts;
    (void)timespec_get(&ts, TIME_UTC);
    return ts;
}

double get_time_s() {
    return tstos(get_time());
}

static
void program_tick(const struct ProgramSettings* settings, float delta_time) {
    settings->on_tick();
    game_world_update();
    physics_world_tick();
}

void program_run(const struct ProgramSettings* settings) {
    LOG_INFO("Starting program...");
    if(settings->target_fps <= 0) {
        _target_delta_time = 0;
    } else {
        _target_delta_time = 1.0f/settings->target_fps;
    }
    _game_start_time = get_time_s();
    _frame_start = _game_start_time;
    if(SDL_Init(INITFLAGS) != 0) {
        LOG_ERROR("SDL init error: %s", SDL_GetError());
    }

    g_window = SDL_CreateWindow(
        settings->title,
        SDL_WINDOWPOS_CENTERED_DISPLAY(0),
        SDL_WINDOWPOS_CENTERED_DISPLAY(0),
        settings->view_resolution.x,
        settings->view_resolution.y,
        SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_RESIZABLE);

    physics_world_init();
    render_init(g_window, settings);
    camera_init();
    assets_init();
    input_init();
    game_world_init();

    LOG_INFO("settings->on_play");
    settings->on_play();

    LOG_INFO("Starting program loop");
    for(;;) {
        render_present();

        double current_time  = get_time_s();
        _delta_time += current_time - _frame_start;
        _frame_start = current_time;

        program_handle_events();
        if(settings->target_fps == 0) {
            program_tick(settings, _delta_time);
            _delta_time = 0.f;
        } else {
            while(_delta_time > _target_delta_time) {
                _delta_time -= _target_delta_time;
                program_tick(settings, _target_delta_time);
            }
        }
        settings->on_draw();
        game_world_draw();
        SDL_Delay(1);
    }

    ASSERT_RETURN(0,, "Program escaped containment, nuking from orbit...");
    abort();
}

void program_quit() {
    game_world_close();
    input_clean();
    assets_clean();
    render_clean();
    physics_world_clean();
    SDL_DestroyWindow(g_window);
    SDL_Quit();
    exit(0);
}

void program_handle_events() {
    SDL_Event event;
    while(SDL_PollEvent(&event)) {
        switch(event.type) {
            default: break;
            case SDL_KEYUP:
            case SDL_KEYDOWN:
            case SDL_CONTROLLERBUTTONUP:
            case SDL_CONTROLLERBUTTONDOWN:
            case SDL_CONTROLLERAXISMOTION:
                input_handle_event(event);
            break;
            case SDL_WINDOWEVENT:
                if(event.window.windowID == SDL_GetWindowID(g_window)) {
                    program_handle_windowevent(&event.window);
                }
                break;
            case SDL_QUIT:
                program_quit();
                break;
        }
    }
}

void program_handle_windowevent(SDL_WindowEvent* event) {
    switch(event->type) {
        default:
            render_handle_resize();
            break;
    }
}

inline
float delta_time() {
    return (float)(_target_delta_time == 0 ? _delta_time : _target_delta_time);
}

inline
float game_time() {
    return (float)(get_time_s() - _game_start_time);
}