#include "gfx/gl_loader.h"
#include "gfx/gpu_compute_program.h"

#include <cstdio>
#include <regex>
#include <string>

namespace fn::gfx {

static int parse_err_line(const char* log) {
    std::regex re1(R"(ERROR:\s*\d+:(\d+):)");
    std::regex re2(R"(\d+\((\d+)\))");
    std::cmatch m;
    if (std::regex_search(log, m, re1)) return std::stoi(m[1].str());
    if (std::regex_search(log, m, re2)) return std::stoi(m[1].str());
    return -1;
}

static int count_lines(const std::string& s) {
    int n = 0;
    for (char c : s) if (c == '\n') ++n;
    return n;
}

static ComputeCompileResult compile_with_layout(const std::string& layout_line,
                                                const std::string& user_body,
                                                const std::string& preamble) {
    ComputeCompileResult r;

    // Header fijo. count = lineas que sumamos antes del user_body, para
    // restar al err_line del log.
    std::string header = "#version 430 core\n";
    header += layout_line;
    if (!preamble.empty()) {
        header += preamble;
        if (preamble.back() != '\n') header += '\n';
    }
    int header_lines = count_lines(header);

    const char* srcs[2] = { header.c_str(), user_body.c_str() };

    GLuint sh = glCreateShader(GL_COMPUTE_SHADER);
    glShaderSource(sh, 2, srcs, nullptr);
    glCompileShader(sh);

    GLint ok = 0;
    glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
    if (!ok) {
        GLint len = 0;
        glGetShaderiv(sh, GL_INFO_LOG_LENGTH, &len);
        r.err_msg.resize(static_cast<std::size_t>(len));
        if (len > 0) glGetShaderInfoLog(sh, len, nullptr, &r.err_msg[0]);
        int line = parse_err_line(r.err_msg.c_str());
        r.err_line = (line > header_lines) ? (line - header_lines) : line;
        glDeleteShader(sh);
        return r;
    }

    GLuint prog = glCreateProgram();
    glAttachShader(prog, sh);
    glLinkProgram(prog);
    glDeleteShader(sh);

    glGetProgramiv(prog, GL_LINK_STATUS, &ok);
    if (!ok) {
        GLint len = 0;
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
        r.err_msg.resize(static_cast<std::size_t>(len));
        if (len > 0) glGetProgramInfoLog(prog, len, nullptr, &r.err_msg[0]);
        r.err_line = parse_err_line(r.err_msg.c_str());
        glDeleteProgram(prog);
        return r;
    }

    r.program = prog;
    r.ok = true;
    return r;
}

ComputeCompileResult compile_compute(const std::string& user_body,
                                     int local_size_x,
                                     const std::string& preamble) {
    char buf[64];
    std::snprintf(buf, sizeof(buf),
                  "layout(local_size_x = %d) in;\n", local_size_x);
    return compile_with_layout(buf, user_body, preamble);
}

ComputeCompileResult compile_compute_2d(const std::string& user_body,
                                        int local_size_x,
                                        int local_size_y,
                                        const std::string& preamble) {
    char buf[96];
    std::snprintf(buf, sizeof(buf),
                  "layout(local_size_x = %d, local_size_y = %d) in;\n",
                  local_size_x, local_size_y);
    return compile_with_layout(buf, user_body, preamble);
}

void delete_compute_program(unsigned int program) {
    if (program) glDeleteProgram(program);
}

} // namespace fn::gfx
