import tkinter as tk
from tkinter import ttk
from typing import Dict, List
from MaMa import MaMa

class MaMaGUI:
    def __init__(self, mama: MaMa, delay: int = 400) -> None:
        self.delay = delay
        self.do_autoplay = False
        self.machine = mama
        self.journal = self.machine.run()
        if not self.journal:
            raise ValueError("Journal is empty.")
        self.journal_index = 0

        # Root window
        self.root = tk.Tk()
        self.root.title("MaMa GUI")
        self.root.geometry("1000x600")
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        self.root.rowconfigure(1, weight=0)
        self.root.rowconfigure(2, weight=0)

        # Main flex container
        container = ttk.Frame(self.root, padding=10)
        container.grid(row=0, column=0, sticky="nsew")
        container.columnconfigure(0, weight=3)
        container.columnconfigure(1, weight=2)
        container.rowconfigure(0, weight=1)

        # Program display
        prog_frame = ttk.LabelFrame(container, text="Program", padding=5)
        prog_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
        prog_frame.rowconfigure(0, weight=1)
        prog_frame.columnconfigure(0, weight=1)
        self.prog_list = tk.Listbox(prog_frame, font=("Consolas", 11))
        self.prog_list.grid(row=0, column=0, sticky="nsew")

        # Stack display
        stack_frame = ttk.LabelFrame(container, text="Stack", padding=5)
        stack_frame.grid(row=0, column=1, sticky="nsew")
        stack_frame.rowconfigure(0, weight=1)
        stack_frame.columnconfigure(0, weight=1)
        self.stack_list = tk.Listbox(stack_frame, font=("Consolas", 11))
        self.stack_list.grid(row=0, column=0, sticky="nsew")
        self.conf_label = ttk.Label(stack_frame, text="(s, p, S) = (-, -, [])",
                                    font=("Consolas", 11), anchor="e")
        self.conf_label.grid(row=1, column=0, sticky="ew", pady=(4, 0))

        # Control buttons
        control = ttk.Frame(self.root, padding=5)
        control.grid(row=1, column=0, sticky="ew")
        control.columnconfigure(0, weight=1)
        for text, cmd in [
            ("▶ Start", self.__start),
            ("⏸ Stop", self.__stop),
            ("⏭ Next", self.__next_step),
            ("↩ Prev", self.__prev_step),
            ("⟲ Reset", self.__reset),
        ]:
            ttk.Button(control, text=text, command=cmd).pack(side="left", padx=5)

        ttk.Label(control, text="Speed:").pack(side="left", padx=5)
        self.speed_scale = ttk.Scale(control, from_=2000, to=100,
                                     orient="horizontal", command=self.__update_speed)
        self.speed_scale.set(self.delay)
        self.speed_scale.pack(side="left", padx=5, fill="x", expand=True)

        # Status bar
        self.status = ttk.Label(self.root, text="Ready", padding=5, anchor="w")
        self.status.grid(row=2, column=0, sticky="ew")

        self.__update()

    # --- UI update ---
    def __update(self) -> None:
        # Current journal state
        state = self.journal[self.journal_index]
        stack = dict(state["stack"])
        p_prog, p_stack = int(state["p_prog"]), int(state["p_stack"])
        step, micro = int(state["step"]), str(state["micro"])

        # --- Program display ---
        self.prog_list.delete(0, tk.END)
        for addr, instr in sorted(self.machine.prog.items()):
            prefix = ">> " if addr == p_prog else "   "
            line = f"{prefix}{addr:>3}:  {instr}"
            self.prog_list.insert(tk.END, line)
            if addr == p_prog:
                self.prog_list.itemconfig(tk.END, {"bg": "#ffd966"})

        # --- Stack display ---
        self.stack_list.delete(0, tk.END)
        if stack:
            for i in range(p_stack, -1, -1):
                val = stack.get(i, 0)
                mark = "<-- top" if i == p_stack else ""
                self.stack_list.insert(tk.END, f"[{i:>2}] {val:>6} {mark}")
                if i == p_stack:
                    self.stack_list.itemconfig(tk.END, {"bg": "#cfe2f3"})
        else:
            self.stack_list.insert(tk.END, "Empty")

        # --- Configuration & status ---
        self.conf_label.config(text=f"({p_stack}, {p_prog}, {list(stack.values())})")
        self.status.config(
            text=f"Step {step}/{len(self.journal)-1} | PC={p_prog} | SP={p_stack} | Micro: {micro}"
        )

    # --- Speed control ---
    def __update_speed(self, value: str) -> None:
        self.delay = int(float(value))

    # --- Autoplay controls ---
    def __start(self) -> None:
        if not self.do_autoplay:
            self.do_autoplay = True
            self.__auto_step()

    def __stop(self) -> None:
        self.do_autoplay = False

    def __auto_step(self) -> None:
        if self.do_autoplay and self.journal_index < len(self.journal) - 1:
            self.journal_index += 1
            self.__update()
            self.root.after(self.delay, self.__auto_step)
        else:
            self.do_autoplay = False

    # --- Step navigation ---
    def __next_step(self) -> None:
        if self.journal_index < len(self.journal) - 1:
            self.journal_index += 1
            self.__update()

    def __prev_step(self) -> None:
        if self.journal_index > 0:
            self.journal_index -= 1
            self.__update()

    def __reset(self) -> None:
        self.journal_index = 0
        self.__update()

    # --- Run GUI ---
    def display(self) -> None:
        self.root.mainloop()
