import numpy as np
import matplotlib.pyplot as plt

# -------------------------
# HDGL Analog VM
# -------------------------
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 / Base(∞) history
        self.history_D = []
        self.history_void = []
        self.history_omega = []

        # In-VM filesystem
        self.fs = {}
        self.init_filesystem()

        # Live plotting
        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
    # -------------------------
    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(∞)
        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)

        # Update live plot
        self.update_plot()

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

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

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

    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
    # -------------------------
    def python_shell(self):
        print("Entering Python sandbox. Access: D, VOID, OMEGA, P, step(), run()")
        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']:
                    self.D = local_ns['D']
                    self.void = local_ns['VOID']
                    self.omega = local_ns['OMEGA']
                    break
                exec(cmd, {}, local_ns)
                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}")

    # -------------------------
    # Initialize in-VM filesystem & GRUB
    # -------------------------
    def init_filesystem(self):
        # Bootloader directory
        self.fs['/boot'] = {}
        # GRUB config
        self.fs['/boot']['grub.cfg'] = {
            'menu': [
                ('Standard HDGL VM', 'kernel_standard'),
                ('Fractal HDGL VM', 'kernel_fractal'),
                ('Python Sandbox Only', 'python_shell')
            ]
        }
        # Example kernels
        self.fs['/boot']['kernel_standard'] = 'run_standard_kernel'
        self.fs['/boot']['kernel_fractal'] = 'run_fractal_kernel'
        self.fs['/boot']['python_shell'] = 'run_python_shell'

        # Optional example file
        self.fs['/boot']['readme.txt'] = "Welcome to HDGL Analog VM with GRUB"

    # -------------------------
    # Filesystem helpers
    # -------------------------
    def fs_ls(self):
        return list(self.fs.keys())

    def fs_cat(self, filename):
        return self.fs.get(filename, f"File '{filename}' not found.")

    def fs_write(self, filename, content):
        self.fs[filename] = content
        return f"Written to {filename}"

    def fs_rm(self, filename):
        if filename in self.fs:
            del self.fs[filename]
            return f"Deleted {filename}"
        return f"File '{filename}' not found."

# -------------------------
# Bootloader
# -------------------------
def bootloader(vm):
    grub_cfg = vm.fs.get('/boot', {}).get('grub.cfg')
    if not grub_cfg:
        print("No GRUB found, starting standard VM...")
        return
    print("HDGL VM Bootloader:")
    menu = grub_cfg['menu']
    for idx, entry in enumerate(menu, 1):
        print(f"{idx}) {entry[0]}")

    choice = input("Select option: ").strip()
    if not choice.isdigit() or int(choice)<1 or int(choice)>len(menu):
        print("Invalid choice, starting default VM...")
        choice = 1
    cmd = menu[int(choice)-1][1]

    if cmd=='run_standard_kernel':
        print("Booting standard HDGL VM...")
        vm.run(steps=10)
    elif cmd=='run_fractal_kernel':
        print("Booting fractal HDGL VM...")
        for _ in range(10):
            vm.step()
    elif cmd=='run_python_shell':
        print("Entering Python sandbox directly...")
        vm.python_shell()

# -------------------------
# REPL
# -------------------------
def vm_repl(vm):
    print("HDGL 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: step [n], run [n], state, reset, python, fs <cmd>, help, exit")
        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()
        elif cmd_lower.startswith('fs'):
            parts = cmd.split()
            if len(parts)<2:
                print("FS commands: ls, cat <file>, write <file>, rm <file>, echo <file> 'text'")
                continue
            fs_cmd = parts[1].lower()
            if fs_cmd=='ls':
                print(vm.fs_ls())
            elif fs_cmd=='cat' and len(parts)>=3:
                print(vm.fs_cat(parts[2]))
            elif fs_cmd=='write' and len(parts)>=3:
                filename = parts[2]
                content = input(f"Enter content for {filename} (end with empty line):\n")
                lines = []
                while content != "":
                    lines.append(content)
                    content = input()
                vm.fs_write(filename, "\n".join(lines))
                print(f"Written to {filename}")
            elif fs_cmd=='rm' and len(parts)>=3:
                print(vm.fs_rm(parts[2]))
            elif fs_cmd=='echo' and len(parts)>=4:
                filename = parts[2]
                text = " ".join(parts[3:])
                vm.fs_write(filename, text)
                print(f"Written to {filename}")
            else:
                print("Unknown FS command.")
        else:
            print("Unknown command. Type 'help'.")

# -------------------------
# Main
# -------------------------
if __name__=="__main__":
    vm = HDGL_VM()
    bootloader(vm)   # GRUB menu appears at start
    vm_repl(vm)
