import math
import random
import time

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

# -------------------------------
# Instruction Set Opcodes
# -------------------------------
OP_NOP = 0
OP_LOAD = 1
OP_ADD = 2
OP_JMP = 3

# -------------------------------
# HDGL Instance Definition (Core)
# -------------------------------
class HDGLInstance:
    def __init__(self, name, n_base, r_dim):
        self.name = name
        self.n_base = n_base
        self.r_dim = r_dim       # recursion bias
        self.Ω = 1 / (phi**n_base)**7
        self.D = [0.0]*4         # analog slots D1-D4
        self.binary = [0]*4      # thresholded binary
        self.shared = None       # shared resonance bus
        self.pc = 0              # program counter
        self.program = []        # list of instructions
        self.jump_flag = False

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

    def threshold(self):
        """Discretize analog slots into binary using sqrt(phi)"""
        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', [])

        if op == OP_NOP:
            pass
        elif op == OP_LOAD:
            reg, val = args
            self.D[reg] = val
        elif op == OP_ADD:
            reg_target, reg_a, reg_b = args
            self.D[reg_target] = self.D[reg_a] + self.D[reg_b]
        elif op == 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 self.jump_flag:
            self.pc = (self.pc + 1) % len(self.program)
        else:
            self.jump_flag = False

        # Apply analog increment + resonance coupling
        self.D[0] += 0.1 * self.r_dim
        self.D[1] += 0.05 * self.r_dim
        if self.shared is not None:
            # Resonance coupling via D2/D3
            self.D[2] = (self.D[2] + self.shared.D[2]*0.5) * 0.95
        self.D[3] += 0.02 * self.r_dim

        # Threshold update
        self.threshold()

# -------------------------------
# Resonance Bus (Cross-Core)
# -------------------------------
class ResonanceBus:
    def __init__(self):
        self.D = [0.0]*4

    def propagate(self, instances):
        """Average D2/D3 across all cores to maintain analog coherence"""
        avg = sum([inst.D[2] for inst in instances]) / len(instances)
        for inst in instances:
            inst.shared.D[2] = avg

# -------------------------------
# HDGL Multi-Core Machine
# -------------------------------
class HDGLMachine:
    def __init__(self):
        self.instances = []
        # Create 8 instances (A-H) with increasing r_dim
        for idx, r in enumerate([0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0], start=1):
            inst = HDGLInstance(name=f"Strand {chr(64+idx)}", n_base=idx, r_dim=r)
            inst.shared = HDGLInstance(name="ResBus", n_base=0, r_dim=0)
            self.instances.append(inst)
        self.bus = ResonanceBus()
        for inst in self.instances:
            inst.shared = self.bus

    def load_core_programs(self, programs):
        """Assign programs to each core"""
        for inst, prog in zip(self.instances, programs):
            inst.load_program(prog)

    def step(self):
        """One execution step for all cores"""
        for inst in self.instances:
            inst.execute_instruction()
        # Update resonance bus after all cores have executed
        self.bus.propagate(self.instances)

    def display(self):
        """Show analog and binary state of all cores"""
        for inst in self.instances:
            print(f"{inst.name}: Analog {['%.3f'%d for d in inst.D]} | Binary {inst.binary}")
        print("-"*70)

# -------------------------------
# Example Programs for Each Core
# -------------------------------
def generate_program():
    """Simple oscillating program for demonstration"""
    return [
        {'op': OP_LOAD, 'args': [0, random.uniform(0, 1)]},
        {'op': OP_ADD, 'args': [2, 0, 1]},
        {'op': OP_JMP, 'args': [2, 0]},  # jump if D2 >= √φ
        {'op': OP_NOP}
    ]

# -------------------------------
# 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)
