import numpy as np
import matplotlib.pyplot as plt
import time

class Analog_HDGL_VM:
    def __init__(self, blend=0.05):
        # Registers
        self.D = np.array([1.,2.,3.,4.,5.,6.,7.,8.,1.])
        self.P = np.zeros(8)
        self.P[4:] = [6.8541, 11.0901, 17.9442, 29.0344]
        self.Void = 0.0
        self.Omega = 1.0
        self.blend = blend

        # Command queue for numeric vectors
        self.command_queue = []

        # Filesystem: numeric vectors
        self.fs = {}
        self.init_fs()

        # History for plotting
        self.history_D = []
        self.history_Void = []
        self.history_Omega = []

        # Plot handles (initialized but figure not shown yet)
        self.fig, (self.ax1, self.ax2) = plt.subplots(2,1, figsize=(10,6))
        self.lines_D = [self.ax1.plot([],[])[0] for _ in range(len(self.D))]
        self.line_Void, = self.ax2.plot([],[])
        self.line_Omega, = self.ax2.plot([],[])

    # -------------------------
    # Filesystem
    # -------------------------
    def init_fs(self):
        self.fs['/boot/grub'] = np.array([1.0,2.0,3.0])
        self.fs['log.txt'] = np.zeros(len(self.D))

    def fs_write(self, filename, vector):
        self.fs[filename] = vector.copy()

    def fs_read(self, filename):
        return self.fs.get(filename, np.zeros(len(self.D)))

    # -------------------------
    # Analog Step
    # -------------------------
    def analog_step(self):
        if self.command_queue:
            vec = self.command_queue.pop(0)
            for i in range(min(len(vec), len(self.D))):
                self.D[i] += self.blend * vec[i]
        else:
            P_pad = np.zeros(len(self.D))
            P_pad[:len(self.P)] = self.P
            self.D += self.blend * (P_pad + np.sin(np.arange(len(self.D))*1.618))

        self.Void += self.blend * np.mean(self.D)
        self.Omega += self.blend * 0.01

        self.history_D.append(self.D.copy())
        self.history_Void.append(self.Void)
        self.history_Omega.append(self.Omega)

        self.update_plot()

    # -------------------------
    # Convert text to numeric vector
    # -------------------------
    def text_to_vector(self, text):
        vec = np.zeros(len(self.D))
        for i,c in enumerate(text.ljust(len(self.D))[:len(self.D)]):
            vec[i] = ord(c) % 10
        return vec

    # -------------------------
    # Analog console output
    # -------------------------
    def analog_console(self):
        print("\n=== Analog VM State ===")
        print("D:", np.round(self.D,4))
        print("Void:", round(self.Void,4), "Omega:", round(self.Omega,4))
        print("Filesystem keys:", list(self.fs.keys()))
        print("======================\n")

    # -------------------------
    # Plot update (non-blocking)
    # -------------------------
    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()
        plt.pause(0.001)  # allow plot update without blocking CMD

    # -------------------------
    # Interactive CMD loop
    # -------------------------
    def run_cmd_loop(self):
        print("=== HDGL Analog CMD ===")
        # Show figure now
        self.fig.show()
        while True:
            try:
                cmd = input("CMD> ")
            except EOFError:
                break
            if cmd.lower() in ['exit','quit']:
                print("Exiting Analog CMD.")
                break
            elif cmd.startswith("fs write"):
                parts = cmd.split()
                filename = parts[2]
                data = self.text_to_vector(" ".join(parts[3:]))
                self.fs_write(filename, data)
                print(f"Wrote analog data to {filename}")
            elif cmd.startswith("fs read"):
                parts = cmd.split()
                filename = parts[2]
                data = self.fs_read(filename)
                print("Read analog vector:", np.round(data,3))
            elif cmd.startswith("step"):
                self.analog_step()
                self.analog_console()
            else:
                # Free text -> vector -> apply
                vec = self.text_to_vector(cmd)
                self.command_queue.append(vec)
                self.analog_step()
                self.analog_console()

    # -------------------------
    # Bootloader
    # -------------------------
    def bootloader(self):
        menu_vec = self.fs_read('/boot/grub')
        choice = int(menu_vec[0] % 3) + 1
        if choice==1:
            for _ in range(10): self.analog_step()
        elif choice==2:
            for _ in range(10): self.analog_step()
            for _ in range(5): self.D += np.sin(self.D)
        elif choice==3:
            self.run_cmd_loop()


# -------------------------
# Main
# -------------------------
if __name__=="__main__":
    vm = Analog_HDGL_VM()
    vm.fs['/boot/grub'] = np.array([3.0])  # start in interactive CMD
    vm.bootloader()
