import math
import random
import time

# -------------------------------
# Constants
# -------------------------------
phi = 1.6180339887
sqrt_phi = math.sqrt(phi)
FIBS = [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610]  # Fibonacci
PRIMES = [2,3,5,7,11,13,17,19,23,29,31]              # Prime subset

# -------------------------------
# HDGL Instance (Core)
# -------------------------------
class HDGLInstance:
    def __init__(self, name, n_base, r_dim):
        self.name = name
        self.n_base = n_base
        self.r_dim = r_dim
        self.Ω = 1 / (phi**n_base)**7
        self.D = [0.0]*4
        self.binary = [0]*4
        self.shared_refs = []
        self.pc = 0
        self.program = []
        # Fibonacci & prime-based scaling per slot
        self.slot_scale = [FIBS[n_base+i % len(FIBS)] / PRIMES[i % len(PRIMES)] for i in range(4)]

    def load_program(self, program):
        self.program = program
        self.pc = 0

    def threshold(self, tick):
        # Dynamic threshold: decreases slightly over time to allow excitation
        dynamic_thresh = sqrt_phi * (1 - 0.01*min(tick,30))
        for i in range(4):
            self.binary[i] = 1 if self.D[i] >= dynamic_thresh else 0

    def execute_instruction(self, tick):
        if not self.program:
            return

        instr = self.program[self.pc]
        op = instr['op']
        args = instr.get('args', [])

        if op == 'NOP':
            pass
        elif op == 'LOAD':
            reg, val = args
            self.D[reg] = val
        elif op == 'ADD':
            reg_target, reg_a, reg_b = args
            self.D[reg_target] = self.D[reg_a] + self.D[reg_b]
        elif op == 'JMP':
            reg_cond, target = args
            if self.D[reg_cond] >= sqrt_phi:
                self.pc = target
                self.jump_flag = True

        if not getattr(self, 'jump_flag', False):
            self.pc = (self.pc + 1) % len(self.program)
        else:
            self.jump_flag = False

        # Analog increment with slot scale & r_dim
        for i in range(4):
            self.D[i] += 0.05 * self.r_dim * self.slot_scale[i]

        # Full resonance coupling
        for ref in self.shared_refs:
            for i in range(4):
                self.D[i] = (self.D[i] + ref.D[i]*0.5)/1.5

        # Threshold update
        self.threshold(tick)

# -------------------------------
# HDGL Multi-Core Machine
# -------------------------------
class HDGLMachine:
    def __init__(self):
        self.instances = []
        r_dims = [0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]

        for i in range(8):
            inst = HDGLInstance(name=f"Strand {chr(65+i)}", n_base=i+1, r_dim=r_dims[i])
            self.instances.append(inst)

        for inst in self.instances:
            inst.shared_refs = [other for other in self.instances if other != inst]

    def load_core_programs(self, programs):
        for inst, prog in zip(self.instances, programs):
            inst.load_program(prog)

    def step(self, tick):
        for inst in self.instances:
            inst.execute_instruction(tick)

    def aggregate_lattice(self):
        lattice_bin = []
        for inst in self.instances:
            lattice_bin.extend(inst.binary)
        lattice_str = ''.join(str(b) for b in reversed(lattice_bin))  # MSB first
        lattice_hex = hex(int(lattice_str,2))
        return lattice_str, lattice_hex

    def display(self, tick):
        print(f"=== Tick {tick+1} ===")
        for inst in self.instances:
            print(f"{inst.name}: Analog {['%.3f'%d for d in inst.D]} | Binary {inst.binary}")
        lattice_bin, lattice_hex = self.aggregate_lattice()
        print(f"Aggregated Lattice: {lattice_bin} | Hex: {lattice_hex}")
        print("="*90)

# -------------------------------
# Generate Random Programs
# -------------------------------
def generate_program():
    return [
        {'op':'LOAD','args':[0,random.uniform(0,2)]},
        {'op':'ADD','args':[2,0,1]},
        {'op':'JMP','args':[2,0]},
        {'op':'NOP'}
    ]

# -------------------------------
# Run Simulation
# -------------------------------
if __name__=="__main__":
    machine = HDGLMachine()
    programs = [generate_program() for _ in range(8)]
    machine.load_core_programs(programs)

    for tick in range(50):  # simulate 50 cycles
        machine.step(tick)
        machine.display(tick)
        time.sleep(0.05)
