/*
 * MIT License
 * Author: Mark Allyn
 *
 * graticule.cpp — incandescent bearing graticule and cursor overlay.
 */

#include "graticule.h"
#include "left_panel.h"   // for TextRenderer definition
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdio>
#include <cstring>

static const float PI = 3.14159265f;

// ---- Shader helpers (same pattern as phosphor.cpp) ----
static std::string gReadFile(const std::string& p) {
    std::ifstream f(p);
    if (!f.is_open()) { fprintf(stderr,"graticule: can't open %s\n",p.c_str()); return ""; }
    std::ostringstream ss; ss << f.rdbuf(); return ss.str();
}
static GLuint gCompile(GLenum type, const std::string& src) {
    const char* s = src.c_str();
    GLuint sh = glCreateShader(type);
    glShaderSource(sh,1,&s,nullptr); glCompileShader(sh);
    GLint ok=0; glGetShaderiv(sh,GL_COMPILE_STATUS,&ok);
    if(!ok){char l[1024]; glGetShaderInfoLog(sh,1024,nullptr,l); fprintf(stderr,"grat shader: %s\n",l);}
    return sh;
}
static GLuint gLink(const std::string& vp, const std::string& fp) {
    std::string vs=gReadFile(vp), fs=gReadFile(fp);
    if(vs.empty()||fs.empty()) return 0;
    GLuint v=gCompile(GL_VERTEX_SHADER,vs), f=gCompile(GL_FRAGMENT_SHADER,fs);
    GLuint p=glCreateProgram(); glAttachShader(p,v); glAttachShader(p,f); glLinkProgram(p);
    GLint ok=0; glGetProgramiv(p,GL_LINK_STATUS,&ok);
    if(!ok){char l[1024]; glGetProgramInfoLog(p,1024,nullptr,l); fprintf(stderr,"grat prog: %s\n",l);}
    glDeleteShader(v); glDeleteShader(f); return p;
}

// ----------------------------------------------------------------

static float brgToRadians(float deg, float offset) {
    // Convert bearing (CW from north) to math angle (CCW from east),
    // subtracting any display offset. Result in radians.
    float a = 90.0f - (deg - offset);
    return a * PI / 180.0f;
}

// ----------------------------------------------------------------

bool Graticule::init(const std::string& shaderDir,
                     TextRenderer& tr,
                     float cx, float cy, float radius)
{
    cx_ = cx; cy_ = cy; r_ = radius;
    tr_ = &tr;

    prog_ = gLink(shaderDir + "graticule.vert", shaderDir + "graticule.frag");
    if (!prog_) return false;

    buildRingGeometry();
    buildTickGeometry();

    // Pre-compute label positions at GRAT_LABEL_INTERVAL_DEG steps
    labels_.clear();
    float labelR = r_ * GRAT_LABEL_RING_FRAC;
    for (int deg = 0; deg < 360; deg += GRAT_LABEL_INTERVAL_DEG) {
        float rad = brgToRadians(static_cast<float>(deg), 0.0f);
        LabelPos lp;
        lp.x = cx_ + labelR * std::cos(rad);
        lp.y = cy_ - labelR * std::sin(rad);   // screen y is inverted
        snprintf(lp.text, sizeof(lp.text), "%d", deg);
        labels_.push_back(lp);
    }

    // Cursor geometry is dynamic; create empty VAO/VBO now
    glGenVertexArrays(1, &cursVAO_);
    glGenBuffers(1, &cursVBO_);
    glBindVertexArray(cursVAO_);
    glBindBuffer(GL_ARRAY_BUFFER, cursVBO_);
    glBufferData(GL_ARRAY_BUFFER, 200 * sizeof(float) * 2, nullptr, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
    glBindVertexArray(0);

    return true;
}

void Graticule::buildRingGeometry() {
    const int N = GRAT_SEGMENTS;
    std::vector<float> verts;
    verts.reserve((N + 1) * 2 * 2);  // inner + outer ring

    for (int ring = 0; ring < 2; ++ring) {
        float rad = r_ * (ring == 0 ? GRAT_INNER_RING_FRAC : GRAT_OUTER_RING_FRAC);
        for (int i = 0; i <= N; ++i) {
            float a = 2.0f * PI * i / N;
            verts.push_back(cx_ + rad * std::cos(a));
            verts.push_back(cy_ - rad * std::sin(a));  // flip y for screen
        }
    }
    ringVertCount_ = static_cast<int>(verts.size()) / 2;

    glGenVertexArrays(1, &ringVAO_);
    glGenBuffers(1, &ringVBO_);
    glBindVertexArray(ringVAO_);
    glBindBuffer(GL_ARRAY_BUFFER, ringVBO_);
    glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
    glBindVertexArray(0);
}

void Graticule::buildTickGeometry() {
    std::vector<float> verts;
    verts.reserve(360 * 4);  // 2 verts × 2 floats per tick

    float innerR = r_ * GRAT_INNER_RING_FRAC;

    for (int deg = 0; deg < 360; ++deg) {
        float rad  = brgToRadians(static_cast<float>(deg), 0.0f);
        float cR   = std::cos(rad);
        float sR   = std::sin(rad);

        // Major ticks every 10 degrees, minor otherwise
        float len = (deg % 10 == 0)
                    ? r_ * GRAT_TICK_MAJOR_FRAC
                    : r_ * GRAT_TICK_MINOR_FRAC;

        float x0 = cx_ + innerR * cR;
        float y0 = cy_ - innerR * sR;
        float x1 = cx_ + (innerR - len) * cR;
        float y1 = cy_ - (innerR - len) * sR;

        verts.push_back(x0); verts.push_back(y0);
        verts.push_back(x1); verts.push_back(y1);
    }
    tickVertCount_ = static_cast<int>(verts.size()) / 2;

    glGenVertexArrays(1, &tickVAO_);
    glGenBuffers(1, &tickVBO_);
    glBindVertexArray(tickVAO_);
    glBindBuffer(GL_ARRAY_BUFFER, tickVBO_);
    glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
    glBindVertexArray(0);
}

// ----------------------------------------------------------------

void Graticule::render(float viewportW, float viewportH,
                       float intensity, float /*bearingOffset*/) const
{
    if (!prog_) return;

    glUseProgram(prog_);
    glUniform2f(glGetUniformLocation(prog_, "u_viewportSize"), viewportW, viewportH);

    float r = GRAT_R * intensity;
    float g = GRAT_G * intensity;
    float b = GRAT_B * intensity;

    glUniform4f(glGetUniformLocation(prog_, "u_color"), r, g, b, 1.0f);

    // Inner and outer rings (LINE_STRIP segments)
    glBindVertexArray(ringVAO_);
    int half = ringVertCount_ / 2;
    glDrawArrays(GL_LINE_STRIP, 0,    half);  // inner
    glDrawArrays(GL_LINE_STRIP, half, half);  // outer
    glBindVertexArray(0);

    // Tick marks
    glBindVertexArray(tickVAO_);
    glDrawArrays(GL_LINES, 0, tickVertCount_);
    glBindVertexArray(0);

    // Text labels
    if (tr_) {
        for (const auto& lp : labels_) {
            tr_->renderText(lp.text, lp.x, lp.y,
                            static_cast<float>(GRATICULE_LABEL_FONT_SIZE) / 16.0f,
                            r, g, b,
                            viewportW, viewportH,
                            true /* centred */);
        }
    }
}

void Graticule::renderCursor(float viewportW, float viewportH,
                             float brgDeg, float rngNorm) const
{
    if (!prog_ || !cursVAO_) return;

    // Build cursor geometry: 10° arc + bearing crossline
    std::vector<float> verts;

    const float arcSpan  = CURSOR_ARC_SPAN_DEG;
    const float arcSteps = 20.0f;
    const float cursorR  = r_ * rngNorm;

    // Arc
    for (int i = 0; i <= (int)arcSteps; ++i) {
        float a = brgDeg - arcSpan * 0.5f + arcSpan * i / arcSteps;
        float rad = brgToRadians(a, 0.0f);
        verts.push_back(cx_ + cursorR * std::cos(rad));
        verts.push_back(cy_ - cursorR * std::sin(rad));
    }

    // Bearing crossline: scope centre → just beyond arc
    float lineRad = brgToRadians(brgDeg, 0.0f);
    float lineLen = cursorR + r_ * 0.04f;  // slightly beyond arc
    verts.push_back(cx_);
    verts.push_back(cy_);
    verts.push_back(cx_ + lineLen * std::cos(lineRad));
    verts.push_back(cy_ - lineLen * std::sin(lineRad));

    int nVerts = static_cast<int>(verts.size()) / 2;
    int arcEnd = static_cast<int>(arcSteps) + 1;

    glBindBuffer(GL_ARRAY_BUFFER, cursVBO_);
    glBufferSubData(GL_ARRAY_BUFFER, 0, verts.size() * sizeof(float), verts.data());

    glUseProgram(prog_);
    glUniform2f(glGetUniformLocation(prog_, "u_viewportSize"), viewportW, viewportH);
    glUniform4f(glGetUniformLocation(prog_, "u_color"),
                CURSOR_R, CURSOR_G, CURSOR_B, 1.0f);

    glBindVertexArray(cursVAO_);
    glDrawArrays(GL_LINE_STRIP, 0, arcEnd);   // arc
    glDrawArrays(GL_LINES,    arcEnd, 2);     // crossline
    glBindVertexArray(0);
}

Graticule::~Graticule() {
    if (prog_)    glDeleteProgram(prog_);
    if (ringVAO_) glDeleteVertexArrays(1, &ringVAO_);
    if (ringVBO_) glDeleteBuffers(1, &ringVBO_);
    if (tickVAO_) glDeleteVertexArrays(1, &tickVAO_);
    if (tickVBO_) glDeleteBuffers(1, &tickVBO_);
    if (cursVAO_) glDeleteVertexArrays(1, &cursVAO_);
    if (cursVBO_) glDeleteBuffers(1, &cursVBO_);
}
