import numpy as np
import time
import threading
import random

# -------------------------------
# Lattice Parameters
# -------------------------------
NUM_NODES = 8
NUM_CORES = 4
NUM_STRANDS = 8
SLOTS_PER_STRAND = 4
TOTAL_SLOTS = NUM_STRANDS * SLOTS_PER_STRAND

PHI = 1.6180339887
OMEGA_BASE = 1 / (PHI**np.arange(1, NUM_STRANDS+1))**7

FS = 1_000_000
BLOCK_SIZE = 4096
t = np.arange(BLOCK_SIZE) / FS

C_LIGHT = 3e8

# -------------------------------
# Node Initialization
# -------------------------------
def init_node():
    cores = []
    for _ in range(NUM_CORES):
        lattice = np.random.uniform(0.5, 1.0, (NUM_STRANDS, SLOTS_PER_STRAND))
        phases = np.random.uniform(0, 2*np.pi, (NUM_STRANDS, SLOTS_PER_STRAND))
        weights = np.ones((NUM_STRANDS, SLOTS_PER_STRAND))
        omega = OMEGA_BASE.copy()
        cores.append({'lattice': lattice, 'phases': phases, 'weights': weights, 'omega': omega})
    return cores

NODE_POSITIONS = np.array([[random.uniform(-50,50),
                            random.uniform(-50,50),
                            random.uniform(0,20)]
                           for _ in range(NUM_NODES)])
node_lattices = [init_node() for _ in range(NUM_NODES)]

# -------------------------------
# Moving Target Grid
# -------------------------------
GRID_RES = 4
TARGET_GRID = np.array([[x, y, z] 
                        for x in np.linspace(-10,10,GRID_RES)
                        for y in np.linspace(-10,10,GRID_RES)
                        for z in np.linspace(0,20,GRID_RES)])

def generate_moving_targets(t_tick):
    targets = []
    for i in range(3):
        x = 5 * np.sin(0.05 * t_tick + i)
        y = 5 * np.cos(0.05 * t_tick + i)
        z = 5 + i*2 + np.sin(0.03 * t_tick)
        targets.append([x, y, z])
    return np.array(targets)

# -------------------------------
# Continuous Intensity Function
# -------------------------------
def field_at_target(node_pos, cores, target):
    field = 0.0
    for core in cores:
        lattice = core['lattice']
        phases = core['phases']
        weights = core['weights']
        for s in range(NUM_STRANDS):
            for slot in range(SLOTS_PER_STRAND):
                amp = lattice[s, slot] * weights[s, slot]
                r = np.linalg.norm(node_pos - target)
                field += amp * np.exp(1j*(2*np.pi * r / C_LIGHT + phases[s, slot]))
    return field

def measure_intensity(node_id, cores, targets):
    node_pos = NODE_POSITIONS[node_id]
    return np.array([np.abs(field_at_target(node_pos, cores, t)) for t in targets])

# -------------------------------
# Continuous Gradient Flow Update
# -------------------------------
def evolve_lattice(node_id, cores, targets, dt=0.01, gain=0.5):
    base_intensity = measure_intensity(node_id, cores, targets)
    for core in cores:
        # Continuous analog update
        phase_grad = np.zeros_like(core['phases'])
        weight_grad = np.zeros_like(core['weights'])
        for s in range(NUM_STRANDS):
            for slot in range(SLOTS_PER_STRAND):
                # Approximate derivative using small displacement in phase and weight
                delta = 1e-4
                core['phases'][s, slot] += delta
                intensity_plus = measure_intensity(node_id, [core], targets)
                core['phases'][s, slot] -= delta
                phase_grad[s, slot] = ((intensity_plus - base_intensity).sum()) / delta

                core['weights'][s, slot] += delta
                intensity_plus = measure_intensity(node_id, [core], targets)
                core['weights'][s, slot] -= delta
                weight_grad[s, slot] = ((intensity_plus - base_intensity).sum()) / delta

        # Update smoothly over dt
        core['phases'] = (core['phases'] + gain * phase_grad * dt) % (2*np.pi)
        core['weights'] = np.clip(core['weights'] + gain * weight_grad * dt, 0.0, 2.0)

# -------------------------------
# Main Continuous Loop
# -------------------------------
def holographic_lattice_loop():
    t_tick = 0
    while True:
        targets = generate_moving_targets(t_tick)
        for node_id, cores in enumerate(node_lattices):
            evolve_lattice(node_id, cores, targets, dt=0.05, gain=1.0)
        t_tick += 1
        time.sleep(0.01)  # fine-tune for real-time convergence

# -------------------------------
# Launch as Thread
# -------------------------------
threading.Thread(target=holographic_lattice_loop, daemon=True).start()
print("Continuous Analog HDGL Lattice Running.")
