#include <gfx/gfx.h>
#include <gfx/private.h>
#include <sdl-1.2/gfx/private.h>
#include <SDL.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

int screen_w = SCREEN_W, screen_h = SCREEN_H;
static bool fullscreen;
static SDL_Surface *screen;
static struct gfx_sprite **list;
static size_t list_len;

void gfx_deinit(void)
{
    /* screen should be already freed by SDL_Quit. */
    free(list);
    SDL_QuitSubSystem(SDL_INIT_VIDEO);
}

static struct
{
    int w, h;
} display, windowed;

void gfx_register_sprite(struct gfx_sprite *const s)
{
    list = realloc(list, (list_len + 1) * sizeof *list);

    if (list)
        list[list_len++] = s;
}

static int resize_screen(int w, int h, const bool full_screen)
{
    Uint32 flags = SDL_HWSURFACE | SDL_RESIZABLE | SDL_ANYFORMAT | SDL_DOUBLEBUF;

    const SDL_VideoInfo *const info = SDL_GetVideoInfo();

    if (!info)
    {
        fprintf(stderr, "SDL_GetVideoInfo: %s\n", SDL_GetError());
        return -1;
    }

    static bool init;

    if (!init)
    {
        display.w = info->current_w;
        display.h = info->current_h;
        init = true;
    }

    if (fullscreen)
    {
        flags |= SDL_FULLSCREEN;
        w = display.w;
        h = display.h;
    }
    else
    {
        windowed.w = w;
        windowed.h = h;
    }

    int bpp = info->vfmt->BitsPerPixel;

    if (screen)
        SDL_FreeSurface(screen);

    const int max_bpp = SDL_VideoModeOK(w, h, 0, flags);

    if (max_bpp < 0)
    {
        fprintf(stderr, "SDL_VideoModeOK: %s\n", SDL_GetError());
        return -1;
    }
    else if (bpp > max_bpp)
        bpp = max_bpp;

    if (!(screen = SDL_SetVideoMode(w, h, 0, flags)))
    {
        fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError());
        return -1;
    }

    for (size_t i = 0; i < list_len; i++)
        if (sprite_screen_resize_ev(list[i]))
            return -1;

    screen_w = w;
    screen_h = h;
    fullscreen = full_screen;
    return 0;
}

SDL_Surface *gfx_screen(void)
{
    return screen;
}

int gfx_display_size(short *const w, short *const h)
{
    *w = display.w;
    *h = display.h;
    return 0;
}

int gfx_init(void)
{
    if (SDL_InitSubSystem(SDL_INIT_VIDEO))
    {
        fprintf(stderr, "%s: SDL_InitSubSystem: %s\n",
            __func__, SDL_GetError());
        return -1;
    }

    return resize_screen(screen_w, screen_h, fullscreen);
}

bool gfx_inside_drawenv(const short x, const short y, const short w,
    const short h)
{
    return (x + w >= 0)
        && x < screen_w
        && (y + h >= 0)
		&& y < screen_h;
}

static int get_resize_events(void)
{
    int n;
    SDL_Event ev;

    while ((n = SDL_PeepEvents(&ev, 1, SDL_GETEVENT,
                SDL_VIDEORESIZEMASK)) > 0)
    {
        if (ev.type == SDL_VIDEORESIZE)
        {
            const SDL_ResizeEvent *const res = &ev.resize;

            if (resize_screen(res->w, res->h, false))
                return -1;
        }
    }

    if (n < 0)
    {
        fprintf(stderr, "%s: SDL_PeepEvents: %s\n",
            __func__, SDL_GetError());
        return -1;
    }

    return 0;
}

int gfx_toggle_fullscreen(void)
{
    fullscreen ^= true;

    const int w = fullscreen ? display.w : windowed.w;
    const int h = fullscreen ? display.h : windowed.h;

    return resize_screen(w, h, fullscreen);
}

int gfx_set_fullscreen(const short w, const short h)
{
    return resize_screen(display.w = w, display.h = h, fullscreen = true);
}

bool gfx_fullscreen_available(void)
{
    return true;
}

bool gfx_fullscreen(void)
{
    return fullscreen;
}

int gfx_draw(void)
{
    enum {FPS = 50, REFRESH_MS = 1000 / FPS};
    static Uint32 prev;
    const Uint32 cur = SDL_GetTicks();

    if (cur - prev < REFRESH_MS)
        SDL_Delay(REFRESH_MS - (cur - prev));

    prev = SDL_GetTicks();

    if (SDL_Flip(screen))
    {
        fprintf(stderr, "SDL_Flip: %s\n", SDL_GetError());
        return -1;
    }

    get_resize_events();
    return 0;
}
