import math
import random
import time

# -------------------------------
# Constants
# -------------------------------
phi = 1.6180339887
sqrt_phi = math.sqrt(phi)

# -------------------------------
# HDGL Instance (8 Slots per 32-bit Lattice / 4 Slots per Core)
# -------------------------------
class HDGLInstance:
    def __init__(self, name, n_base, r_dim, slot_start):
        self.name = name
        self.n_base = n_base
        self.r_dim = r_dim
        self.Ω = 1 / (phi**n_base)**7
        self.slot_start = slot_start
        self.D = [0.0]*4             # 4 analog slots per instance
        self.binary = [0]*4          # thresholded binary
        self.shared_slots = []       # references to cross-core resonance slots
        self.pc = 0                  # program counter
        self.program = []

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

    def threshold(self):
        for i in range(4):
            self.binary[i] = 1 if self.D[i] >= sqrt_phi else 0

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

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

        # Opcode execution
        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

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

        # Analog dynamics + resonance coupling
        self.D[0] += 0.05 * self.r_dim
        self.D[1] += 0.03 * self.r_dim
        if self.shared_slots:
            self.D[2] = sum([slot[2] for slot in self.shared_slots])/len(self.shared_slots) * 0.95
        self.D[3] += 0.02 * self.r_dim

        # Threshold update
        self.threshold()

# -------------------------------
# HDGL Multi-Core Machine
# -------------------------------
class HDGLMachine:
    def __init__(self):
        self.instances = []
        self.shared_slots = [ [0.0]*4 for _ in range(8) ]  # D3 resonance bus for each instance
        r_dims = [0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]

        # Create 8 instances with 4 slots each
        for i in range(8):
            inst = HDGLInstance(name=f"Strand {chr(65+i)}",
                                n_base=i+1,
                                r_dim=r_dims[i],
                                slot_start=i*4)
            inst.shared_slots = self.shared_slots[:i] + self.shared_slots[i+1:]  # reference all other instances
            self.instances.append(inst)

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

    def step(self):
        # Execute instructions for all cores
        for inst in self.instances:
            inst.execute_instruction()
        # Update shared resonance slots (D2/D3)
        for i, inst in enumerate(self.instances):
            self.shared_slots[i][2] = inst.D[2]

    def aggregate_lattice(self):
        """Return full 32-bit binary string and hex encoding"""
        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):
        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("="*80)

# -------------------------------
# Generate Random Programs for Each Core
# -------------------------------
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(20):  # simulate 20 cycles
        print(f"=== Tick {tick+1} ===")
        machine.step()
        machine.display()
        time.sleep(0.1)
