import numpy as np
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import glfw

# ---------------- Globals ----------------
NUM_SLOTS = 4096
NUM_INSTANCES = 8
SCREEN_W, SCREEN_H = 1024, 768
ASCII_SAMPLE = 8192  # number of glyphs

# ---------------- Fibonacci Table ----------------
def generate_fib_table_gpu(n):
    fibs = np.zeros(n, dtype=np.float32)
    fibs[0], fibs[1] = 0.0, 1.0
    for i in range(2, n):
        fibs[i] = fibs[i-1] + fibs[i-2]
        if fibs[i] > 1e6:
            fibs /= fibs[i]
    return fibs / fibs.max() if fibs.max() != 0 else fibs

# ---------------- Shaders ----------------
VERTEX_SRC = """
#version 430
layout(location=0) in vec2 pos;
out vec2 uv;
void main() {
    uv = pos*0.5 + 0.5;
    gl_Position = vec4(pos,0.0,1.0);
}
"""

FRAGMENT_SRC = f"""
#version 430
out vec4 fragColor;
in vec2 uv;
uniform float t;
uniform float phi;
layout(std430, binding=0) buffer FibBuffer {{
    float fibTable[];
}};

float compute_slot(int strand_id, int slot_id, vec2 uv, float time) {{
    float phi_harm = pow(phi, float(slot_id % 16));
    float fib_harm = fibTable[slot_id % {NUM_SLOTS}];
    float dyadic = float(1 << int(mod(float(slot_id),16.0)));
    float omega = 0.5 + 0.5*sin(time + float(slot_id)*0.01);
    float r = length(uv-0.5);
    float rec_bias = 0.05*float(strand_id);
    return sqrt(phi_harm*fib_harm*dyadic*omega)*r + rec_bias;
}}

vec4 holographic_strands() {{
    float strands[8];
    for(int s=0; s<8; s++) {{
        float val = 0.0;
        int stride = {NUM_SLOTS}/8;
        for(int i=0; i<stride; i++) {{
            int idx = i + s*stride;
            val += compute_slot(s, idx, uv, t);
        }}
        strands[s] = val / float(stride);
    }}
    return mix(vec4(strands[0], strands[1], strands[2], strands[3]),
               vec4(strands[4], strands[5], strands[6], strands[7]), 0.5);
}}

void main() {{
    vec4 holo = holographic_strands();

    // Map holographic value to ASCII "intensity" (0-1)
    float intensity = clamp((holo.r + holo.g + holo.b + holo.a)/4.0, 0.0, 1.0);
    
    // ASCII approximation: map intensity to luminance
    fragColor = vec4(vec3(intensity), 1.0);
}}
"""

# ---------------- OpenGL Setup ----------------
def init_glfw():
    if not glfw.init():
        raise Exception("GLFW init failed")
    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR,4)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR,3)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
    window = glfw.create_window(SCREEN_W, SCREEN_H, "HDGL Superglyph Overlay + ASCII", None, None)
    glfw.make_context_current(window)
    return window

def init_shaders():
    shader = compileProgram(
        compileShader(VERTEX_SRC, GL_VERTEX_SHADER),
        compileShader(FRAGMENT_SRC, GL_FRAGMENT_SHADER)
    )
    return shader

def create_fib_ssbo(fib_table):
    ssbo = glGenBuffers(1)
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo)
    glBufferData(GL_SHADER_STORAGE_BUFFER, fib_table.nbytes, fib_table, GL_STATIC_DRAW)
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo)
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0)
    return ssbo

# ---------------- Main ----------------
def main():
    window = init_glfw()
    shader = init_shaders()

    fib_table = generate_fib_table_gpu(NUM_SLOTS)
    ssbo = create_fib_ssbo(fib_table)

    quad = np.array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1], dtype=np.float32)
    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)
    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, quad.nbytes, quad, GL_STATIC_DRAW)
    glEnableVertexAttribArray(0)
    glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,0,None)

    t_loc = glGetUniformLocation(shader,"t")
    phi_loc = glGetUniformLocation(shader,"phi")

    t = 0.0
    while not glfw.window_should_close(window):
        glfw.poll_events()
        glClearColor(0.0,0.0,0.0,1.0)
        glClear(GL_COLOR_BUFFER_BIT)

        glUseProgram(shader)
        glUniform1f(phi_loc,1.6180339887)
        glUniform1f(t_loc,t)

        glDrawArrays(GL_TRIANGLES,0,6)
        glfw.swap_buffers(window)
        t += 0.02

    glfw.terminate()

if __name__=="__main__":
    main()
