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

# ------------------ CONFIG ------------------
NUM_SLOTS = 4096
NUM_STRANDS = 32
BLOCK_SIZE = 4          # 4 strands per RGBA
WINDOW_WIDTH = 1024
WINDOW_HEIGHT = 768

# ------------------ CAMERA ------------------
yaw = 0.0
pitch = 0.0
distance = 5.0
last_x, last_y = None, None
left_dragging = False

# ------------------ GLSL ------------------
VERTEX_SHADER = """
#version 330
layout(location=0) in vec3 position;
uniform mat4 viewProj;
void main() {
    gl_Position = viewProj * vec4(position,1.0);
}
"""

# Fragment shaders remain the same as in superglyphs18.py
# FRAGMENT_SHADER_BLOCK and FRAGMENT_SHADER_FINAL reused here

# ------------------ TEXTURE HELPERS ------------------
# generate_fib_table, generate_prime_table, pack_strands_to_rgba,
# create_1d_texture, create_2d_framebuffer remain unchanged

# ------------------ CAMERA HELPERS ------------------
def get_view_proj():
    cx = distance * cos(radians(pitch)) * sin(radians(yaw))
    cy = distance * sin(radians(pitch))
    cz = distance * cos(radians(pitch)) * cos(radians(yaw))
    eye = np.array([cx, cy, cz], dtype=np.float32)
    center = np.array([0.0,0.0,0.0], dtype=np.float32)
    up = np.array([0.0,1.0,0.0], dtype=np.float32)

    f = center - eye
    f /= np.linalg.norm(f)
    s = np.cross(f, up)
    s /= np.linalg.norm(s)
    u = np.cross(s, f)
    view = np.identity(4, dtype=np.float32)
    view[:3,0] = s
    view[:3,1] = u
    view[:3,2] = -f
    view[:3,3] = -eye @ np.stack([s,u,-f],axis=1)
    # Perspective projection
    fov = radians(45.0)
    aspect = WINDOW_WIDTH / WINDOW_HEIGHT
    znear, zfar = 0.1, 100.0
    f_p = 1.0 / tan(fov/2)
    proj = np.zeros((4,4), dtype=np.float32)
    proj[0,0] = f_p/aspect
    proj[1,1] = f_p
    proj[2,2] = (zfar+znear)/(znear-zfar)
    proj[2,3] = 2*zfar*znear/(znear-zfar)
    proj[3,2] = -1.0
    return proj @ view

# ------------------ MOUSE CALLBACKS ------------------
def mouse(button, state, x, y):
    global left_dragging, last_x, last_y
    if button==GLUT_LEFT_BUTTON:
        left_dragging = (state==GLUT_DOWN)
        last_x, last_y = x, y

def motion(x, y):
    global yaw, pitch, last_x, last_y
    if left_dragging:
        dx = x - last_x
        dy = y - last_y
        yaw += dx * 0.5
        pitch += dy * 0.5
        pitch = max(-89.0,min(89.0,pitch))
        last_x, last_y = x, y

def mouse_wheel(button, dir, x, y):
    global distance
    distance -= dir*0.3
    distance = max(1.0,min(20.0,distance))

# ------------------ INIT ------------------
def init_gl():
    global shader_block, shader_final, tex_handles, fbo_handles, fb_tex
    # Compile shaders and generate textures as before
    # [Reuse superglyphs18.py init_gl()]
    pass

# ------------------ MAIN ------------------
def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA)
    glutInitWindowSize(WINDOW_WIDTH,WINDOW_HEIGHT)
    glutCreateWindow(b"32-Strand Superglyphs + OrbitMouse")
    init_gl()

    omegaTime = 0.0

    def display():
        nonlocal omegaTime
        omegaTime += 0.01
        viewProj = get_view_proj()

        # --- Multi-pass block rendering ---
        # Bind FBO, render each block with shader_block
        # Pass viewProj uniform to vertex shader
        # [Reuse superglyphs18.py display(), but add viewProj uniform]

        glutSwapBuffers()
        glutPostRedisplay()

    glutDisplayFunc(display)
    glutMouseFunc(mouse)
    glutMotionFunc(motion)
    # For wheel: some GLUT implementations need special handler

    glutMainLoop()

if __name__=="__main__":
    main()
