import sys, time, math, ctypes
import numpy as np
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GL.shaders import compileProgram, compileShader

# ---------- Globals ----------
window = None
width, height = 1280, 720
shader_main = None
shader_overlay = None
vao = None
start_time = time.time()

# ---------- Params ----------
NUM_SLOTS = 128   # conservative saturation per strand
NUM_TEXTURES = 16 # for multi-channel glyph overlay

# ---------- Shaders ----------
VERT_SRC = """
#version 330
layout(location=0) in vec2 position;
out vec2 uv;
void main() {
    uv = 0.5 * (position + 1.0); // map [-1,1] to [0,1]
    gl_Position = vec4(position, 0.0, 1.0);
}
"""

FRAG_MAIN = """
#version 330
in vec2 uv;
out vec4 fragColor;

uniform float phi, phiInv, omegaTime;
uniform float fibTable[128];
uniform float primeTable[128];

const int NUM_SLOTS = """ + str(NUM_SLOTS) + """;

float prismatic(int id, float r){
    float phi_h = pow(phi, float(id % 16));
    float fib_h = fibTable[id % 128];
    float prime_h = primeTable[id % 128];
    float Omega = 0.5 + 0.5*sin(omegaTime + float(id)*0.01);
    float r_dim = pow(r, float((id%7)+1));
    return sqrt(phi_h * fib_h * prime_h * Omega) * r_dim;
}

void main(){
    float r = length(uv - 0.5) * 2.0;
    int bits[NUM_SLOTS];
    for(int i=0;i<NUM_SLOTS;i++){
        float v = prismatic(i, r);
        bits[i] = v > sqrt(phi) ? 1 : 0;
    }
    fragColor = vec4(
        float(bits[0]*8 + bits[1]*4 + bits[2]*2 + bits[3]) / 15.0,
        float(bits[4]*8 + bits[5]*4 + bits[6]*2 + bits[7]) / 15.0,
        float(bits[8]*8 + bits[9]*4 + bits[10]*2 + bits[11]) / 15.0,
        float(bits[12]*8 + bits[13]*4 + bits[14]*2 + bits[15]) / 15.0
    );
}
"""

FRAG_OVERLAY = """
#version 330
in vec2 uv;
out vec4 fragColor;

uniform float omegaTime;
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform sampler2D tex3;
uniform sampler2D tex4;
uniform sampler2D tex5;
uniform sampler2D tex6;
uniform sampler2D tex7;

void main(){
    vec4 sum = vec4(0.0);
    sum += texture(tex0, uv);
    sum += texture(tex1, uv);
    sum += texture(tex2, uv);
    sum += texture(tex3, uv);
    sum += texture(tex4, uv);
    sum += texture(tex5, uv);
    sum += texture(tex6, uv);
    sum += texture(tex7, uv);
    float pulse = 0.5 + 0.5*sin(omegaTime*2.0);
    fragColor = sum * pulse / 8.0;
}
"""

# ---------- Shader Init ----------
def init_shaders():
    global shader_main, shader_overlay
    shader_main = compileProgram(
        compileShader(VERT_SRC, GL_VERTEX_SHADER),
        compileShader(FRAG_MAIN, GL_FRAGMENT_SHADER)
    )
    shader_overlay = compileProgram(
        compileShader(VERT_SRC, GL_VERTEX_SHADER),
        compileShader(FRAG_OVERLAY, GL_FRAGMENT_SHADER)
    )

# ---------- Geometry ----------
def init_quad():
    global vao
    vertices = np.array([
        -1.0, -1.0,
         1.0, -1.0,
        -1.0,  1.0,
         1.0,  1.0,
    ], dtype=np.float32)

    vao = glGenVertexArrays(1)
    vbo = glGenBuffers(1)

    glBindVertexArray(vao)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

    glEnableVertexAttribArray(0)
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)

# ---------- Fib + Prime Tables ----------
def fib_primes(n=128):
    fibs = [0,1]
    for i in range(2,n): fibs.append(fibs[-1]+fibs[-2])
    primes = []
    candidate = 2
    while len(primes) < n:
        if all(candidate % p for p in range(2,int(math.sqrt(candidate))+1)):
            primes.append(candidate)
        candidate += 1
    return np.array(fibs,dtype=np.float32), np.array(primes,dtype=np.float32)

fibTable, primeTable = fib_primes()

# ---------- Rendering ----------
def display():
    global start_time
    t = time.time() - start_time

    glClear(GL_COLOR_BUFFER_BIT)

    # --- Pass 1: main glyph into screen ---
    glUseProgram(shader_main)
    glUniform1f(glGetUniformLocation(shader_main,"phi"), (1+math.sqrt(5))/2)
    glUniform1f(glGetUniformLocation(shader_main,"phiInv"), (math.sqrt(5)-1)/2)
    glUniform1f(glGetUniformLocation(shader_main,"omegaTime"), t)
    glUniform1fv(glGetUniformLocation(shader_main,"fibTable"), len(fibTable), fibTable)
    glUniform1fv(glGetUniformLocation(shader_main,"primeTable"), len(primeTable), primeTable)

    glBindVertexArray(vao)
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
    glBindVertexArray(0)

    glutSwapBuffers()
    glutPostRedisplay()

def reshape(w,h):
    global width,height
    width,height = w,h
    glViewport(0,0,w,h)

def main():
    global window
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA)
    glutInitWindowSize(width, height)
    window = glutCreateWindow(b"HDGL SuperGlyphs 4")
    init_shaders()
    init_quad()
    glutDisplayFunc(display)
    glutReshapeFunc(reshape)
    glutMainLoop()

if __name__=="__main__":
    main()
