import sys, numpy as np
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GL.shaders import compileProgram, compileShader
from PIL import Image, ImageDraw, ImageFont

# ---------------- CONFIG ----------------
WINDOW_WIDTH, WINDOW_HEIGHT = 1280, 720
NUM_SLOTS = 4096
NUM_STRANDS = 32
GLYPH_COUNT = 8192
PHI = 1.6180339887

# ---------------- GPU DATA ----------------
def generate_fib_table(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]
    fibs /= fibs.max() if fibs.max() != 0 else 1.0
    return fibs

def generate_prime_table(n):
    primes = np.zeros(n, dtype=np.float32)
    primes_list = [2]
    candidate = 3
    while len(primes_list) < n:
        if all(candidate % p != 0 for p in primes_list): primes_list.append(candidate)
        candidate += 2
    primes[:n] = primes_list[:n]
    primes /= primes.max()
    return primes

fib_table = generate_fib_table(NUM_SLOTS)
prime_table = generate_prime_table(NUM_SLOTS)

# ---------------- ASCII GLYPH TEXTURE ----------------
def create_glyph_texture(glyph_count=GLYPH_COUNT, tex_size=512):
    img = Image.new("L", (tex_size, tex_size), 0)
    draw = ImageDraw.Draw(img)
    font = ImageFont.load_default()
    spacing = int(tex_size / np.sqrt(glyph_count))
    chars = [chr(33 + i % 93) for i in range(glyph_count)]
    for i, c in enumerate(chars):
        x = (i % (tex_size // spacing)) * spacing
        y = (i // (tex_size // spacing)) * spacing
        draw.text((x, y), c, fill=255, font=font)
    img_data = np.array(img, dtype=np.uint8)
    tex_id = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, tex_id)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, tex_size, tex_size, 0,
                 GL_RED, GL_UNSIGNED_BYTE, img_data)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glBindTexture(GL_TEXTURE_2D, 0)
    return tex_id

glyph_texture = None

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

FRAGMENT_SHADER = f"""
#version 330
out vec4 fragColor;
uniform float time;
uniform int num_slots;
uniform int num_strands;
uniform sampler2D glyphTex;
uniform float fibTable[{NUM_SLOTS}];
uniform float primeTable[{NUM_SLOTS}];

float prismaticRecursion(int strand, int slot, float r) {{
    float phi_harm = pow({PHI}, float((strand + slot) % 16));
    float fib_harm = fibTable[slot % {NUM_SLOTS}];
    float prime_harm = primeTable[slot % {NUM_SLOTS}];
    float Omega = 0.5 + 0.5*sin(time + float(strand*slot)*0.01);
    float r_dim = pow(r, float((slot % 7) + 1));
    return sqrt(phi_harm*fib_harm*prime_harm*Omega) * r_dim;
}}

void main() {{
    vec2 uv = gl_FragCoord.xy / vec2({WINDOW_WIDTH},{WINDOW_HEIGHT});
    float r = length(uv - 0.5)*2.0;
    vec4 acc = vec4(0.0);

    for(int s=0; s<num_strands; s++) {{
        float strand_offset = float(s)/float(num_strands);
        for(int slot=0; slot<num_slots; slot++) {{
            float pr = prismaticRecursion(s, slot, r);
            float phase = sin(time*0.1 + float(slot)*0.01 + strand_offset*6.28);
            float amp = pr * (0.5 + 0.5*phase);
            if(s%4==0) acc.r += amp;
            else if(s%4==1) acc.g += amp;
            else if(s%4==2) acc.b += amp;
            else acc.a += amp;
        }}
    }}
    acc /= float(num_strands*num_slots);

    // Animate glyph sampling
    float scroll = fract(time*0.05);
    vec2 glyph_uv = uv + vec2(scroll, scroll*0.5);
    float glyph_val = texture(glyphTex, glyph_uv).r;
    fragColor = acc * glyph_val;
}}
"""

# ---------------- OPENGL ----------------
window = None
shader = None

def init_gl():
    global shader, glyph_texture
    shader = compileProgram(
        compileShader(VERTEX_SHADER, GL_VERTEX_SHADER),
        compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
    )
    glUseProgram(shader)
    glClearColor(0.0,0.0,0.0,1.0)
    glyph_texture = create_glyph_texture()
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, glyph_texture)
    loc = glGetUniformLocation(shader, "glyphTex")
    glUniform1i(loc,0)

def display():
    glClear(GL_COLOR_BUFFER_BIT)
    glUniform1f(glGetUniformLocation(shader,"time"), glutGet(GLUT_ELAPSED_TIME)/1000.0)
    glUniform1i(glGetUniformLocation(shader,"num_slots"), NUM_SLOTS)
    glUniform1i(glGetUniformLocation(shader,"num_strands"), NUM_STRANDS)
    glBegin(GL_TRIANGLES)
    glVertex2f(-1,-1)
    glVertex2f(3,-1)
    glVertex2f(-1,3)
    glEnd()
    glutSwapBuffers()
    glutPostRedisplay()

def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA)
    glutInitWindowSize(WINDOW_WIDTH,WINDOW_HEIGHT)
    glutCreateWindow(b"HDGL Holographic Superglyphs")
    init_gl()
    glutDisplayFunc(display)
    glutMainLoop()

if __name__=="__main__":
    main()
