import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

class VisualFractalHDGL_VM:
    def __init__(self):
        # Upper Field
        self.upper = {
            'prism': 105.0,
            'recursion_mode': 99.9999999999,
            'inf_recursion': 9.9999999999,
            'p3': 4.2360679775,
            'pi': 3.1415926535,
            'phi_phi': 2.6180339887,
            'phi': 1.6180339887
        }

        # D registers + dimensionality switch
        self.D = np.array([1.,2.,3.,4.,5.,6.,7.,8.,1.], dtype=float)

        # Fibonacci / DNA braids
        self.P = np.zeros(8)
        self.P[4:] = [6.8541019662, 11.09016994, 17.94427191, 29.03444654]

        # Void and Ω
        self.void = 0.0
        self.omega = 1.0

        # Lower Field
        self.lower = {
            'neg_inf': 1e-10,
            'inv_P7': 0.0344465435,
            'inv_P6': 0.05572809,
            'inv_P5': 0.0901699437,
            'inv_P4': 0.1458980338,
            'inv_P3': 0.2360679775,
            'inv_phi_phi': 0.3819660113,
            'inv_phi': 0.6180339887
        }

        # Prime weights for D1..D8
        primes = [2,3,5,7,11,13,17,19]
        self.weights = np.array([2**n + p for n,p in enumerate(primes)], dtype=float)
        self.weights /= np.sum(self.weights)

        # φ^n decay
        self.phi_decay = 0.5
        self.phi_powers = np.array([self.upper['phi']**(n) * (self.phi_decay**n) for n in range(len(self.D))])

        # Fibonacci recursive weights
        self.fib_weights = np.array([1,1,2,3,5,8,13,21,34], dtype=float)
        self.fib_weights /= np.sum(self.fib_weights)

        # Stabilization parameters
        self.max_D_mean = 10.0
        self.max_void = 1000.0
        self.max_omega = 10.0

        # Instruction program
        self.program = []
        self.ip = 0

        # History for plotting
        self.history_D = []
        self.history_void = []
        self.history_omega = []

    # --------------------------------------
    # Harmonic superposition (single step)
    # --------------------------------------
    def harmonic_superpose(self, blend_factor=0.05):
        D_prev = self.D.copy()
        phi_phi = self.upper['phi_phi']
        weighted_sum = np.sum(D_prev[:8] * self.weights)

        for i in range(len(self.D)):
            p_val = self.P[i] if i < len(self.P) else 0
            fib_val = self.fib_weights[i] if i < len(self.fib_weights) else 0
            phi_val = self.phi_powers[i] if i < len(self.phi_powers) else 1.0

            self.D[i] = D_prev[i] + blend_factor * (
                phi_val * D_prev[i] +
                phi_phi * p_val +
                weighted_sum * fib_val +
                self.omega
            )

        # Normalize D registers
        mean_D = np.mean(self.D)
        if mean_D > self.max_D_mean:
            self.D *= self.max_D_mean / mean_D

        # Update Void and Ω with caps
        self.void += np.mean(self.D) * blend_factor
        if self.void > self.max_void:
            self.void = self.max_void

        self.omega += 0.01 * blend_factor
        if self.omega > self.max_omega:
            self.omega = self.max_omega

    # --------------------------------------
    # Fractal instruction recursion
    # --------------------------------------
    def fractal_execute(self, instr, depth=0, max_depth=3, decay=0.5):
        blend_factor = 0.05 * (decay**depth)
        self.harmonic_superpose(blend_factor)

        # Record history for plotting
        self.history_D.append(self.D.copy())
        self.history_void.append(self.void)
        self.history_omega.append(self.omega)

        # Fibonacci sub-instructions
        if depth < max_depth:
            num_sub = int(self.fib_weights[depth % len(self.fib_weights)] * 5)
            for _ in range(num_sub):
                self.fractal_execute(instr, depth=depth+1, max_depth=max_depth, decay=decay)

    # --------------------------------------
    # VM execution
    # --------------------------------------
    def load_program(self, instructions):
        self.program = instructions
        self.ip = 0

    def step(self):
        if self.ip >= len(self.program):
            return False
        instr = self.program[self.ip]
        self.fractal_execute(instr)
        self.ip += 1
        return True

    def run(self):
        while self.step():
            pass

    # --------------------------------------
    # Real-time visualization
    # --------------------------------------
    def visualize(self):
        fig, axes = plt.subplots(2,1, figsize=(10,6))
        ax1, ax2 = axes

        D_lines = ax1.plot(self.history_D)
        ax1.set_title("D Registers Evolution")
        ax1.set_xlabel("Step")
        ax1.set_ylabel("D Value")
        ax1.legend([f"D{i+1}" for i in range(len(self.D))])

        ax2.plot(self.history_void, label="Void")
        ax2.plot(self.history_omega, label="Omega")
        ax2.set_title("Void & Omega Evolution")
        ax2.set_xlabel("Step")
        ax2.set_ylabel("Value")
        ax2.legend()

        plt.tight_layout()
        plt.show()


# --------------------------
# Example usage
# --------------------------
if __name__ == "__main__":
    vm = VisualFractalHDGL_VM()
    vm.load_program([0,1,2,3,1,0,2,3,1,0])
    vm.run()
    vm.visualize()
