import numpy as np
import matplotlib.pyplot as plt

class HDGL_VM:
    def __init__(self, blend_factor=0.05):
        # Core VM state
        self.D = np.array([1.,2.,3.,4.,5.,6.,7.,8.,1.])
        self.P = np.zeros(8)
        self.P[4:] = [6.8541019662, 11.09016994, 17.94427191, 29.03444654]
        self.void = 0.0
        self.omega = 1.0
        self.phi_phi = 2.6180339887
        self.phi = 1.6180339887
        self.weights = np.ones(8)/8
        self.ip = 0
        self.program = [0,1,2,3,1,0,2,3,1,0]
        self.blend_factor = blend_factor

        # Fractal recursion history
        self.history_D = []
        self.history_void = []
        self.history_omega = []

        # Set up live plot
        self.fig, (self.ax1, self.ax2) = plt.subplots(2,1, figsize=(10,6))
        self.lines_D = [self.ax1.plot([], [], label=f"D{i+1}")[0] for i in range(len(self.D))]
        self.line_void, = self.ax2.plot([], [], label="Void")
        self.line_omega, = self.ax2.plot([], [], label="Omega")
        self.ax1.set_title("D Registers Evolution")
        self.ax2.set_title("Void & Omega Evolution")
        self.ax1.set_xlabel("Step")
        self.ax1.set_ylabel("D Value")
        self.ax2.set_xlabel("Step")
        self.ax2.set_ylabel("Value")
        self.ax1.legend()
        self.ax2.legend()
        plt.ion()
        plt.show()

    # -------------------------
    # Step VM with optional fractal recursion
    # -------------------------
    def step(self):
        D_prev = self.D.copy()
        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
            phi_val = self.phi ** i
            self.D[i] = D_prev[i] + self.blend_factor * (
                phi_val * D_prev[i] + self.phi_phi * p_val + weighted_sum + self.omega
            )

        # Fractal / Base(∞) recursion
        fractal_factor = np.mean(self.D) * 0.1
        self.D += fractal_factor * np.sin(np.arange(len(self.D)) * self.phi)
        self.void += np.mean(self.D) * self.blend_factor
        self.omega += 0.01 * self.blend_factor
        self.ip += 1

        # Update history
        self.history_D.append(self.D.copy())
        self.history_void.append(self.void)
        self.history_omega.append(self.omega)

        # Live plot
        self.update_plot()

    # -------------------------
    # Run N steps
    # -------------------------
    def run(self, steps=None):
        count = 0
        while steps is None or count < steps:
            self.step()
            count += 1

    # -------------------------
    # Reset VM
    # -------------------------
    def reset(self):
        self.__init__(blend_factor=self.blend_factor)
        print("VM reset.")

    # -------------------------
    # Show state
    # -------------------------
    def show_state(self):
        print(f"D: {np.round(self.D,5)}")
        print(f"Void: {self.void:.5f}, Omega: {self.omega:.5f}")

    # -------------------------
    # Live plot update
    # -------------------------
    def update_plot(self):
        for i, line in enumerate(self.lines_D):
            line.set_data(range(len(self.history_D)), [h[i] for h in self.history_D])
        self.line_void.set_data(range(len(self.history_void)), self.history_void)
        self.line_omega.set_data(range(len(self.history_omega)), self.history_omega)
        self.ax1.relim(); self.ax1.autoscale_view()
        self.ax2.relim(); self.ax2.autoscale_view()
        self.fig.canvas.draw()
        self.fig.canvas.flush_events()

    # -------------------------
    # Python sandbox inside VM
    # -------------------------
    def python_shell(self):
        print("Entering Python sandbox. Access VM variables: D, VOID, OMEGA, P, step()")
        local_ns = {
            'D': self.D,
            'VOID': self.void,
            'OMEGA': self.omega,
            'P': self.P,
            'phi': self.phi,
            'phi_phi': self.phi_phi,
            'step': self.step,
            'run': self.run
        }
        while True:
            try:
                cmd = input("HDGL-PY> ")
                if cmd.strip().lower() in ['exit','quit']:
                    # sync back VM state
                    self.D = local_ns['D']
                    self.void = local_ns['VOID']
                    self.omega = local_ns['OMEGA']
                    break
                exec(cmd, {}, local_ns)
                # sync back VM state for live plotting
                self.D = local_ns['D']
                self.void = local_ns['VOID']
                self.omega = local_ns['OMEGA']
                self.update_plot()
            except Exception as e:
                print(f"Error: {e}")

# -------------------------
# REPL for VM commands
# -------------------------
def vm_repl(vm):
    print("HDGL Analog VM CMD. Type 'help' for commands.")
    while True:
        cmd = input("HDGL> ").strip()
        if not cmd: continue
        cmd_lower = cmd.lower()
        if cmd_lower in ['quit','exit']: break
        elif cmd_lower=='help':
            print("Commands:")
            print(" step [n]   - Step VM n times (default 1)")
            print(" run [n]    - Run continuously or n steps")
            print(" state      - Show VM state")
            print(" reset      - Reset VM")
            print(" python     - Enter Python sandbox inside VM")
            print(" help       - Show this help")
            print(" exit/quit  - Exit CMD")
        elif cmd_lower.startswith('step'):
            parts = cmd.split()
            n = int(parts[1]) if len(parts)>1 else 1
            for _ in range(n): vm.step()
            vm.show_state()
        elif cmd_lower.startswith('run'):
            parts = cmd.split()
            n = int(parts[1]) if len(parts)>1 else None
            vm.run(steps=n)
            vm.show_state()
        elif cmd_lower=='state':
            vm.show_state()
        elif cmd_lower=='reset':
            vm.reset()
        elif cmd_lower=='python':
            vm.python_shell()
        else:
            print("Unknown command. Type 'help'.")

# -------------------------
# Main
# -------------------------
if __name__=="__main__":
    vm = HDGL_VM()
    vm_repl(vm)
