//
// Created by Tristan on 11/26/2025.
//

#include "shader.h"
#include "../util.h"
#include "../logger/log.h"
#include "../logger/gl_log.h"


// creates a shader program from a vertex and fragment shader
// vertex_shader_str - a null terminated string of text containing a vertex shader
// fragment_shader_str - a null terminated string of text containing a fragment shader
// returns a new, valid shader program handle, or 0 if there was an issue
// assert on NULL parameters
GLuint create_shader_program_from_strings(const char *vertex_shader_str, const char *fragment_shader_str) {
    assert(vertex_shader_str && fragment_shader_str);

    GLuint shader_program = glCreateProgram();
    GLuint vertex_shader_handle = glCreateShader(GL_VERTEX_SHADER);
    GLuint fragment_shader_handle = glCreateShader(GL_FRAGMENT_SHADER);

    // compile shader and check for errors
    glShaderSource(vertex_shader_handle, 1, &vertex_shader_str, NULL);
    glCompileShader(vertex_shader_handle);
    int lparams = -1;
    glGetShaderiv(vertex_shader_handle, GL_COMPILE_STATUS, &lparams);

    if (GL_TRUE != lparams) {
        log_error("ERROR: vertex shader index %u did not compile", vertex_shader_handle);

        const int max_length = 2048;
        int actual_length = 0;
        char slog[2048];
        glGetShaderInfoLog(vertex_shader_handle, max_length, &actual_length, slog);
        log_error("shader info log for GL index %u:\n%s", vertex_shader_handle, slog);

        glDeleteShader(vertex_shader_handle);
        glDeleteShader(fragment_shader_handle);
        glDeleteProgram(shader_program);
        return 0;
    }

    // compile shader and check for errors
    glShaderSource(fragment_shader_handle, 1, &fragment_shader_str, NULL);
    glCompileShader(fragment_shader_handle);
    lparams = -1;
    glGetShaderiv(fragment_shader_handle, GL_COMPILE_STATUS, &lparams);

    if (GL_TRUE != lparams) {
        log_error("ERROR: fragment shader index %u did not compile", fragment_shader_handle);

        const int max_length = 2048;
        int actual_length = 0;
        char slog[2048];
        glGetShaderInfoLog(fragment_shader_handle, max_length, &actual_length, slog);
        gl_log_err(slog);

        glDeleteShader(vertex_shader_handle);
        glDeleteShader(fragment_shader_handle);
        glDeleteProgram(shader_program);
        return 0;
    }

    glAttachShader(shader_program, fragment_shader_handle);
    glAttachShader(shader_program, vertex_shader_handle);

    // link program and check for errors
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader_handle);
    glDeleteShader(fragment_shader_handle);
    lparams = -1;
    glGetProgramiv(shader_program, GL_LINK_STATUS, &lparams);

    if (GL_TRUE != lparams) {
        log_error("ERROR: could not link shader program GL index %u", shader_program);

        const int max_length = 2048;
        int actual_length = 0;
        char plog[2048];
        glGetProgramInfoLog(shader_program, max_length, &actual_length, plog);
        log_error("program info log for GL index %u:\n%s", shader_program, plog);

        glDeleteProgram(shader_program);
        return 0;
    }

    return shader_program;
}

GLuint create_shader_program_from_files(const char *vertex_shader_filename, const char *fragment_shader_filename) {
    assert(vertex_shader_filename && fragment_shader_filename);

    log_info("loading shader from files `%s` and `%s`", vertex_shader_filename, fragment_shader_filename);

    char vs_shader_str[MAX_SHADER_SZ];
    char fs_shader_str[MAX_SHADER_SZ];
    vs_shader_str[0] = fs_shader_str[0] = '\0';

    // read the vertex shader file into a buffer
    FILE *fp = fopen(vertex_shader_filename, "r");
    if (!fp) {
        log_error("ERROR: could not open vertex shader file `%s`", vertex_shader_filename);
        return 0;
    }

    size_t count = fread(vs_shader_str, 1, MAX_SHADER_SZ - 1, fp);
    assert(count < MAX_SHADER_SZ - 1); // file too long otherwise
    vs_shader_str[count] = '\0';
    fclose(fp);

    // read fragment shader file into a buffer
    fp = fopen(fragment_shader_filename, "r");
    if (!fp) {
        log_error("ERROR: could not open fragment shader file `%s`", fragment_shader_filename);
        return 0;
    }

    count = fread(fs_shader_str, 1, MAX_SHADER_SZ - 1, fp);
    assert(count < MAX_SHADER_SZ - 1); // file too long otherwise
    fs_shader_str[count] = '\0';
    fclose(fp);

    return create_shader_program_from_strings(vs_shader_str, fs_shader_str);
}

void shader_reload(GLuint *program, const char *vertex_shader_filename, const char *fragment_shader_filename) {
    assert(program && vertex_shader_filename && fragment_shader_filename);

    GLuint reloaded_program = create_shader_program_from_files(vertex_shader_filename, fragment_shader_filename);

    if (reloaded_program) {
        glDeleteProgram(*program);
        *program = reloaded_program;
    }
}
