/*
 * This screensaver demonstrates 2D shader effects through multiple textures.
 */

#define SGP_UNIFORM_CONTENT_SLOTS 16
#define SOKOL_IMPL
#include "sokol/sokol_gfx.h"
#include "sokol/sokol_gp.h"
#include "sokol/sokol_app.h"
#include "sokol/sokol_glue.h"
#include "sokol/sokol_log.h"
#include "sokol/sokol_time.h"

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#define STBI_NO_SIMD
#define STBI_ONLY_PNG
#include "stb/stb_image.h"

#define SOKOL_SHDC_IMPL
#include "sample-effect.glsl.h"

#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <unistd.h>

static void set_macos_working_directory(void) {
    CFBundleRef bundle = CFBundleGetMainBundle();
    if (bundle) {
        CFStringRef id = CFBundleGetIdentifier(bundle);
        if (id) {
            CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle);
            if (resourcesURL) {
                char path[PATH_MAX];
                if (CFURLGetFileSystemRepresentation(resourcesURL, true, (UInt8*)path, PATH_MAX)) {
                    chdir(path);
                }
                CFRelease(resourcesURL);
            }
        }
    }
}
#endif

static sg_pipeline pip;
static sg_shader shd;
static sg_image image;
static sg_sampler linear_sampler;
static sg_image perlin_image;

static void event(const sapp_event* ev) {
    switch (ev->type) {
        case SAPP_EVENTTYPE_KEY_DOWN:
            if (ev->key_code == SAPP_KEYCODE_ESCAPE)
                sapp_request_quit();
            break;

        default:
            break;
    }
}

static void frame(void) {
    // begin draw commands queue
    int window_width = sapp_width(), window_height = sapp_height();
    sgp_begin(window_width, window_height);

    float secs = sapp_frame_count() * sapp_frame_duration();
    sg_image_desc image_desc = sg_query_image_desc(image);
    float window_ratio = window_width / (float)window_height;
    float image_ratio = image_desc.width / (float)image_desc.height;
    effect_fs_uniforms_t uniforms = {0};
    uniforms.iVelocity.x = 0.02f;
    uniforms.iVelocity.y = 0.01f;
    uniforms.iPressure = 0.3f;
    uniforms.iTime = secs;
    uniforms.iWarpiness = 0.2f;
    uniforms.iRatio = image_ratio;
    uniforms.iZoom = 0.4f;
    uniforms.iLevel = 1.0f;
    sgp_set_pipeline(pip);
    sgp_set_pipeline(pip);
    sgp_set_uniform(NULL, 0, &uniforms, sizeof(effect_fs_uniforms_t));
    sgp_set_image(IMG_iTexChannel0, image);
    sgp_set_image(IMG_iTexChannel1, perlin_image);
    sgp_set_sampler(SMP_iSmpChannel0, linear_sampler);
    sgp_set_sampler(SMP_iSmpChannel1, linear_sampler);
    float width = (window_ratio >= image_ratio) ? window_width : image_ratio*window_height;
    float height = (window_ratio >= image_ratio) ? window_width/image_ratio : window_height;
    sgp_draw_filled_rect(0, 0, width, height);
    sgp_reset_image(IMG_iTexChannel0);
    sgp_reset_image(IMG_iTexChannel1);
    sgp_reset_sampler(SMP_iSmpChannel0);
    sgp_reset_sampler(SMP_iSmpChannel1);
    sgp_reset_pipeline();

    // dispatch draw commands
    sg_pass pass = {.swapchain = sglue_swapchain()};
    sg_begin_pass(&pass);
    sgp_flush();
    sgp_end();
    sg_end_pass();
    sg_commit();
}


static sg_image load_image(const char *filename) {
    int width, height, channels;
    uint8_t* data = stbi_load(filename, &width, &height, &channels, 4);
    sg_image img = {SG_INVALID_ID};
    if(!data)
        return img;
    sg_image_desc image_desc = {0};
    image_desc.width = width;
    image_desc.height = height;
    image_desc.data.subimage[0][0].ptr = data;
    image_desc.data.subimage[0][0].size = (size_t)(width * height * 4);
    img = sg_make_image(&image_desc);
    stbi_image_free(data);
    return img;
}

static void init(void) {
#ifdef __APPLE__
    set_macos_working_directory();
#endif

    // Initialize Sokol GFX
    sg_desc sgdesc = {
        .environment = sglue_environment(),
        .logger.func = slog_func
    };
    sg_setup(&sgdesc);
    if (!sg_isvalid()) {
        fprintf(stderr, "Failed to create Sokol GFX context!\n");
        exit(-1);
    }

    // Initialize Sokol GP
    sgp_desc sgpdesc = {0};
    sgp_setup(&sgpdesc);
    if (!sgp_is_valid()) {
        fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error()));
        exit(-1);
    }

    // Load image
    image = load_image("data/images/majora.png");
    perlin_image = load_image("data/images/perlin.png");
    if (sg_query_image_state(image) != SG_RESOURCESTATE_VALID || sg_query_image_state(perlin_image) != SG_RESOURCESTATE_VALID) {
        fprintf(stderr, "failed to load images");
        exit(-1);
    }

    // Create linear sampler
    sg_sampler_desc linear_sampler_desc = {
        .min_filter = SG_FILTER_LINEAR,
        .mag_filter = SG_FILTER_LINEAR,
        .wrap_u = SG_WRAP_REPEAT,
        .wrap_v = SG_WRAP_REPEAT,
    };
    linear_sampler = sg_make_sampler(&linear_sampler_desc);
    if (sg_query_sampler_state(linear_sampler) != SG_RESOURCESTATE_VALID) {
        fprintf(stderr, "failed to create linear sampler");
        exit(-1);
    }

    // Initialize shader
    shd = sg_make_shader(effect_program_shader_desc(sg_query_backend()));
    if (sg_query_shader_state(shd) != SG_RESOURCESTATE_VALID) {
        fprintf(stderr, "failed to make custom pipeline shader\n");
        exit(-1);
    }
    sgp_pipeline_desc pip_desc = {0};
    pip_desc.shader = shd;
    pip_desc.has_vs_color = true;
    pip = sgp_make_pipeline(&pip_desc);
    if (sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID) {
        fprintf(stderr, "failed to make custom pipeline\n");
        exit(-1);
    }
}

static void cleanup(void) {
    sg_destroy_image(image);
    sg_destroy_image(perlin_image);
    sg_destroy_pipeline(pip);
    sgp_shutdown();
    sg_shutdown();
}

sapp_desc sokol_main(int argc, char* argv[]) {
    (void)argc;
    (void)argv;
    return (sapp_desc){
        .window_title = "Majora's Screensaver",
        .width = 700,
        .height = 720,
        .high_dpi = true,
        .init_cb = init,
        .event_cb = event,
        .frame_cb = frame,
        .cleanup_cb = cleanup,
        .logger.func = slog_func,
    };
}
