#include "button.h"
#include "strings.h"
#include "textbox.h"
#include "window.h"

#include <SDL_image.h>
#include <SDL_mixer.h>
#include <stdio.h>
#include <stdlib.h>

#define ERROR(...) fprintf(stderr, __VA_ARGS__)

#define MUSIC_VOLUME 48

/* Resources */
#define DEFINE_RESOURCE(rec_name) extern const char _binary_##rec_name##_start, _binary_##rec_name##_end
#define RESOURCE_DATA(rec_name) ((const void *) &_binary_##rec_name##_start)
#define RESOURCE_SIZE(rec_name) ((size_t) (&_binary_##rec_name##_end - &_binary_##rec_name##_start))

DEFINE_RESOURCE(font_ttf);
DEFINE_RESOURCE(background_png);
DEFINE_RESOURCE(button_png);
DEFINE_RESOURCE(button_pressed_png);
DEFINE_RESOURCE(mute_png);
DEFINE_RESOURCE(volume_png);
DEFINE_RESOURCE(bgm_wav);
DEFINE_RESOURCE(click_wav);
DEFINE_RESOURCE(hover_wav);
DEFINE_RESOURCE(release_wav);

/* Constants */
static const SDL_Color COLOR_BLACK = {0x0, 0x0, 0x0, 0xFF};
static const SDL_Color COLOR_WHITE = {0xFF, 0xFF, 0xFF, 0xFF};
static const ButtonSoundSet NO_BUTTON_SOUNDS = {NULL, NULL, NULL};

/* Assets */
static TTF_Font *font;
static SDL_Texture *backgroundTexture;
static SDL_Texture *buttonTexture;
static SDL_Texture *buttonPressedTexture;
static SDL_Texture *muteTexture;
static SDL_Texture *volumeTexture;
static Mix_Music *bgm;
static ButtonSoundSet buttonSounds = {NULL, NULL, NULL};

/* UI Elements */
static Button *backButton;
static Button *muteButton;
static TextBox *nameBox;

static Button *blackHoleButton;
static TextBox *blackHoleInfo;

static Button *haloButton;
static TextBox *haloInfo;

static Button *lightButton;
static TextBox *lightInfo;

static Button *planetsAndStarsButton;
static TextBox *planetsAndStarsInfo;

static Button *deathButton;
static TextBox *deathInfo;

/* Variables */
static Window *window;
static size_t locationCount;
static Button **locations;
static TextBox *currentBox = NULL;
static int quit = 0;
static int mute = 0;

/* Functions */
static void init(void);
static void loadAssets(void);
static void createUI(void);
static void drawLoop(void);
static void cleanupAssets(void);
static void cleanupObjects(void);
static void addLocation(Button *button);
static SDL_Texture *loadTexture(const char *data, size_t size);
static Mix_Music *loadMusic(const char *data, size_t size);
static Mix_Chunk *loadSound(const char *data, size_t size);
static void backButtonAction(Button *button, Window *window, float x, float y);
static void muteButtonAction(Button *button, Window *window, float x, float y);
static void blackHoleZoomAction(Button *button, Window *window, float x,
                                float y);
static void haloZoomAction(Button *button, Window *window, float x, float y);
static void lightZoomAction(Button *button, Window *window, float x, float y);
static void planetsAndStarsZoomAction(Button *button, Window *window, float x,
                                      float y);
static void deathZoomAction(Button *button, Window *window, float x, float y);

int main() {
    init();
    window = windowNew("IC Explorer", 500, 500);
    loadAssets();
    createUI();
    drawLoop();
    cleanupObjects();
    cleanupAssets();
    SDL_Quit();
    return 0;
}

void init() {
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO)) {
        ERROR("error: sdl2: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }
    if (!IMG_Init(IMG_INIT_PNG)) {
        ERROR("error: sdl2: image: %s\n", IMG_GetError());
        SDL_Quit();
        exit(EXIT_FAILURE);
    }
    if (TTF_Init() != 0) {
        ERROR("error: sdl2: ttf: %s\n", TTF_GetError());
        SDL_Quit();
        exit(EXIT_FAILURE);
    }
    if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) != 0) {
        ERROR("error: sdl2: mixer: %s\n", Mix_GetError());
        SDL_Quit();
        exit(EXIT_FAILURE);
    }
    Mix_VolumeMusic(MUSIC_VOLUME);
    locationCount = 0;
    locations = malloc(1);
    if (!locations) {
        ERROR("error: out of memory\n");
        SDL_Quit();
        exit(EXIT_FAILURE);
    }
}

void loadAssets() {
    font = TTF_OpenFontRW(SDL_RWFromConstMem(RESOURCE_DATA(font_ttf), RESOURCE_SIZE(font_ttf)), SDL_TRUE, 512);
    backgroundTexture = loadTexture(RESOURCE_DATA(background_png), RESOURCE_SIZE(background_png));
    buttonTexture = loadTexture(RESOURCE_DATA(button_png), RESOURCE_SIZE(button_png));
    buttonPressedTexture = loadTexture(RESOURCE_DATA(button_pressed_png), RESOURCE_SIZE(button_pressed_png));
    muteTexture = loadTexture(RESOURCE_DATA(mute_png), RESOURCE_SIZE(mute_png));
    volumeTexture = loadTexture(RESOURCE_DATA(volume_png), RESOURCE_SIZE(volume_png));
    bgm = loadMusic(RESOURCE_DATA(bgm_wav), RESOURCE_SIZE(bgm_wav));
    buttonSounds.hover = loadSound(RESOURCE_DATA(hover_wav), RESOURCE_SIZE(hover_wav));
    buttonSounds.down = loadSound(RESOURCE_DATA(click_wav), RESOURCE_SIZE(click_wav));
    buttonSounds.up = loadSound(RESOURCE_DATA(release_wav), RESOURCE_SIZE(release_wav));
}

void createUI() {
    SDL_SetWindowMinimumSize(window->obj, 500, 500);
    window->background = backgroundTexture;
    backButton = buttonNew("Back", 0.05f, 0.05f, 0.15f, AUTOMATIC_SCALE,
                           buttonTexture, buttonPressedTexture, COLOR_BLACK,
                           font, buttonSounds, window);
    backButton->action = &backButtonAction;
    muteButton = buttonNew(NULL, 0.9f, 0.9f, 0.075f, 0.075f, volumeTexture,
                           NULL, COLOR_BLACK, font, NO_BUTTON_SOUNDS, window);
    muteButton->action = &muteButtonAction;
    nameBox = textBoxNew("IC 1101", 0.02, 0.02, 0.07f, COLOR_WHITE, NULL, font,
                         window);

    /* Black Hole */
    blackHoleButton =
        buttonNew(BLACK_HOLE_BUTTON_TEXT, 0.55f, 0.55f, 0.15f, AUTOMATIC_SCALE,
                  buttonTexture, buttonPressedTexture, COLOR_BLACK, font,
                  buttonSounds, window);
    blackHoleButton->action = &blackHoleZoomAction;
    addLocation(blackHoleButton);
    blackHoleInfo = textBoxNew(BLACK_HOLE_INFO_TEXT, 0.6f, 0.3f, 0.04,
                               COLOR_BLACK, buttonTexture, font, window);

    /* Halo */
    haloButton = buttonNew(HALO_BUTTON_TEXT, 0.2f, 0.25f, 0.1f, AUTOMATIC_SCALE,
                           buttonTexture, buttonPressedTexture, COLOR_BLACK,
                           font, buttonSounds, window);
    haloButton->action = &haloZoomAction;
    addLocation(haloButton);
    haloInfo = textBoxNew(HALO_INFO_TEXT, 0.5f, 0.3f, 0.04, COLOR_BLACK,
                          buttonTexture, font, window);

    /* Light */
    lightButton = buttonNew(
        LIGHT_BUTTON_TEXT, 0.55f, 0.4f, 0.12f, AUTOMATIC_SCALE, buttonTexture,
        buttonPressedTexture, COLOR_BLACK, font, buttonSounds, window);
    lightButton->action = &lightZoomAction;
    addLocation(lightButton);
    lightInfo = textBoxNew(LIGHT_INFO_TEXT, 0.45f, 0.15f, 0.04, COLOR_BLACK,
                           buttonTexture, font, window);

    /* Planets & Stars */
    planetsAndStarsButton =
        buttonNew(PLANETS_AND_STARS_BUTTON_TEXT, 0.6f, 0.08f, 0.34f,
                  AUTOMATIC_SCALE, buttonTexture, buttonPressedTexture,
                  COLOR_BLACK, font, buttonSounds, window);
    planetsAndStarsButton->action = &planetsAndStarsZoomAction;
    addLocation(planetsAndStarsButton);
    planetsAndStarsInfo =
        textBoxNew(PLANETS_AND_STARS_INFO_TEXT, 0.05f, 0.3f, 0.04, COLOR_BLACK,
                   buttonTexture, font, window);

    /* Death */
    deathButton = buttonNew(
        DEATH_BUTTON_TEXT, 0.1f, 0.85f, 0.12f, AUTOMATIC_SCALE, buttonTexture,
        buttonPressedTexture, COLOR_BLACK, font, buttonSounds, window);
    deathButton->action = &deathZoomAction;
    addLocation(deathButton);
    deathInfo = textBoxNew(DEATH_INFO_TEXT, 0.5f, 0.15f, 0.04, COLOR_BLACK,
                           buttonTexture, font, window);
}

void drawLoop() {
    while (!quit) {
        if (!Mix_PlayingMusic()) {
            Mix_FadeInMusic(bgm, 0, 1000);
        }
        windowDraw(window);
        buttonDraw(muteButton, window);
        if (window->state == WINDOW_STATE_NORMAL) {
            textBoxDraw(nameBox, window);
            size_t i;
            for (i = 0; i < locationCount; ++i) {
                buttonDraw(locations[i], window);
            }
        } else if (window->state == WINDOW_STATE_ZOOMED) {
            buttonDraw(backButton, window);
            if (currentBox) {
                textBoxDraw(currentBox, window);
            }
        }
        windowUpdate(window);
        SDL_Event e;
        while (SDL_PollEvent(&e)) {
            if (e.type == SDL_QUIT) {
                quit = 1;
            } else {
                buttonProcessInput(muteButton, window, &e);
                if (window->state == WINDOW_STATE_NORMAL) {
                    size_t i;
                    for (i = 0; i < locationCount; ++i) {
                        buttonProcessInput(locations[i], window, &e);
                    }
                } else if (window->state == WINDOW_STATE_ZOOMED) {
                    if (e.type == SDL_KEYDOWN &&
                        e.key.keysym.sym == SDLK_ESCAPE) {
                        windowZoomOut(window);
                    } else {
                        buttonProcessInput(backButton, window, &e);
                    }
                }
            }
        }
    }
}

void cleanupAssets() {
    TTF_CloseFont(font);
    SDL_DestroyTexture(backgroundTexture);
    SDL_DestroyTexture(buttonTexture);
    SDL_DestroyTexture(buttonPressedTexture);
    SDL_DestroyTexture(muteTexture);
    SDL_DestroyTexture(volumeTexture);
    Mix_CloseAudio();
    Mix_FreeMusic(bgm);
    Mix_FreeChunk(buttonSounds.hover);
    Mix_FreeChunk(buttonSounds.down);
    Mix_FreeChunk(buttonSounds.up);
}

void cleanupObjects() {
    size_t i;
    for (i = 0; i < locationCount; ++i) { buttonDelete(locations[i]); }
    free(locations);
    buttonDelete(backButton);
    buttonDelete(muteButton);
    textBoxDelete(nameBox);
    textBoxDelete(blackHoleInfo);
    textBoxDelete(haloInfo);
    textBoxDelete(lightInfo);
    textBoxDelete(planetsAndStarsInfo);
    textBoxDelete(deathInfo);
    windowDelete(window);
}

void addLocation(Button *button) {
    locations = realloc(locations, sizeof(Button *) * (++locationCount));
    if (!locations) {
        ERROR("error: out of memory\n");
        exit(EXIT_FAILURE);
    }
    locations[locationCount - 1] = button;
}

SDL_Texture *loadTexture(const char *data, size_t size) {
    SDL_Surface *surface = IMG_Load_RW(SDL_RWFromConstMem(data, size), SDL_TRUE);
    if (!surface) {
        ERROR("error: sdl2: image: %s\n", IMG_GetError());
        exit(EXIT_FAILURE);
    }
    SDL_Texture *texture =
        SDL_CreateTextureFromSurface(window->render, surface);
    SDL_FreeSurface(surface);
    if (!texture) {
        ERROR("error: sdl2: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }
    return texture;
}

Mix_Music *loadMusic(const char *data, size_t size) {
    Mix_Music *m = Mix_LoadMUS_RW(SDL_RWFromConstMem(data, size), SDL_TRUE);
    if (!m) {
        ERROR("error: sdl2: mixer: %s\n", Mix_GetError());
        exit(EXIT_FAILURE);
    }
    return m;
}

Mix_Chunk *loadSound(const char *data, size_t size) {
    Mix_Chunk *c = Mix_LoadWAV_RW(SDL_RWFromConstMem(data, size), SDL_TRUE);
    if (!c) {
        ERROR("error: sdl2: mixer: %s\n", Mix_GetError());
        exit(EXIT_FAILURE);
    }
    return c;
}

void backButtonAction(Button *button, Window *window, float x, float y) {
    windowZoomOut(window);
}

void muteButtonAction(Button *button, Window *window, float x, float y) {
    if (mute) {
        Mix_Volume(-1, 128);
        Mix_VolumeMusic(MUSIC_VOLUME);
        mute = 0;
        button->inactive = volumeTexture;
        button->current = volumeTexture;
    } else {
        Mix_Volume(-1, 0);
        Mix_VolumeMusic(0);
        mute = 1;
        button->inactive = muteTexture;
        button->current = muteTexture;
    }
}

void blackHoleZoomAction(Button *button, Window *window, float x, float y) {
    windowZoomTo(window, 0.6f, 0.5f);
    currentBox = blackHoleInfo;
}

void haloZoomAction(Button *button, Window *window, float x, float y) {
    windowZoomTo(window, 0.25f, 0.3f);
    currentBox = haloInfo;
}

void lightZoomAction(Button *button, Window *window, float x, float y) {
    windowZoomTo(window, 0.5f, 0.35f);
    currentBox = lightInfo;
}

void planetsAndStarsZoomAction(Button *button, Window *window, float x,
                               float y) {
    windowZoomTo(window, 0.55f, 0.03f);
    currentBox = planetsAndStarsInfo;
}

void deathZoomAction(Button *button, Window *window, float x, float y) {
    windowZoomTo(window, 0.05f, 0.8f);
    currentBox = deathInfo;
}
