#!/usr/bin/env python3
"""
5D Random Walk — fullscreen saver for macOS & Linux
Requires Python 3 with tkinter (usually preinstalled on Mac; on Linux: python3-tk)

  python3 random_walk_saver.py           # fullscreen walk
  python3 random_walk_saver.py --window  # windowed preview

Move mouse or press any key to exit. Default speed matches the website (100).
"""
import argparse
import os
import random
import sys
import tempfile
import time
import tkinter as tk
from typing import Optional, Tuple

SPEED = 100
MAX_PX = 960 * 540  # internal buffer cap for smooth updates


class Walk:
    def __init__(self, w: int, h: int) -> None:
        self.w = max(2, w)
        self.h = max(2, h)
        self.x = self.w // 2
        self.y = self.h // 2
        self.r = self.g = self.b = 128
        self.pixels = bytearray(self.w * self.h * 3)
        self._paint()

    def _paint(self) -> None:
        i = (self.y * self.w + self.x) * 3
        self.pixels[i] = self.r
        self.pixels[i + 1] = self.g
        self.pixels[i + 2] = self.b

    def step(self) -> None:
        dim = random.randrange(5)
        delta = 1 if random.random() < 0.5 else -1
        if dim == 0:
            self.x = (self.x + delta) % self.w
        elif dim == 1:
            self.y = (self.y + delta) % self.h
        elif dim == 2:
            self.r = max(0, min(255, self.r + delta))
        elif dim == 3:
            self.g = max(0, min(255, self.g + delta))
        else:
            self.b = max(0, min(255, self.b + delta))
        self._paint()

    def to_ppm(self) -> bytes:
        header = f"P6\n{self.w} {self.h}\n255\n".encode("ascii")
        # transient marker
        i = (self.y * self.w + self.x) * 3
        saved = bytes(self.pixels[i : i + 3])
        self.pixels[i] = 255 - self.r
        self.pixels[i + 1] = 255 - self.g
        self.pixels[i + 2] = 255 - self.b
        data = header + bytes(self.pixels)
        self.pixels[i : i + 3] = saved
        return data


class App:
    def __init__(self, windowed: bool) -> None:
        self.root = tk.Tk()
        self.root.title("5D Random Walk")
        self.root.configure(bg="#000000")
        self.windowed = windowed
        self._exit_armed = False
        self._start_xy = None  # type: Optional[Tuple[int, int]]

        if windowed:
            sw, sh = 800, 600
            self.root.geometry("800x600")
        else:
            self.root.attributes("-fullscreen", True)
            self.root.configure(cursor="none")
            self.root.update_idletasks()
            sw = self.root.winfo_screenwidth()
            sh = self.root.winfo_screenheight()

        # Cap internal resolution
        iw, ih = sw, sh
        if iw * ih > MAX_PX:
            scale = (MAX_PX / (iw * ih)) ** 0.5
            iw = max(2, int(iw * scale))
            ih = max(2, int(ih * scale))

        self.walk = Walk(iw, ih)
        self.canvas = tk.Canvas(self.root, bg="#000", highlightthickness=0, width=sw, height=sh)
        self.canvas.pack(fill=tk.BOTH, expand=True)
        self.photo = None  # type: Optional[tk.PhotoImage]
        self._ppm_path = os.path.join(tempfile.gettempdir(), f"ficarra-rw-{os.getpid()}.ppm")
        self._last = time.perf_counter()
        self._accum = 0.0

        self.root.bind("<Key>", self._exit)
        self.root.bind("<Button>", self._exit)
        self.root.bind("<Motion>", self._motion)
        self.root.after(400, self._arm)
        self.root.after(16, self._tick)
        self.root.protocol("WM_DELETE_WINDOW", self._quit)

    def _arm(self) -> None:
        self._exit_armed = True
        try:
            self._start_xy = self.root.winfo_pointerxy()
        except tk.TclError:
            self._start_xy = None

    def _exit(self, _event=None) -> None:
        if self._exit_armed or self.windowed:
            self._quit()

    def _motion(self, event) -> None:
        if not self._exit_armed or self.windowed:
            return
        if self._start_xy is None:
            self._start_xy = (event.x_root, event.y_root)
            return
        if abs(event.x_root - self._start_xy[0]) > 10 or abs(event.y_root - self._start_xy[1]) > 10:
            self._quit()

    def _quit(self) -> None:
        try:
            if os.path.isfile(self._ppm_path):
                os.remove(self._ppm_path)
        except OSError:
            pass
        self.root.destroy()

    def _tick(self) -> None:
        now = time.perf_counter()
        dt_ms = min(100.0, max(0.0, (now - self._last) * 1000.0))
        self._last = now
        self._accum += dt_ms * SPEED
        steps = int(self._accum)
        if steps > 80000:
            steps = 80000
        if steps > 0:
            for _ in range(steps):
                self.walk.step()
            self._accum -= steps
            try:
                with open(self._ppm_path, "wb") as f:
                    f.write(self.walk.to_ppm())
                self.photo = tk.PhotoImage(file=self._ppm_path)
                self.canvas.delete("all")
                # scale to canvas
                cw = max(1, self.canvas.winfo_width())
                ch = max(1, self.canvas.winfo_height())
                # place centered; PhotoImage doesn't stretch — use subsample/zoom roughly
                self.canvas.create_image(cw // 2, ch // 2, image=self.photo, anchor=tk.CENTER)
            except tk.TclError:
                pass
        self.root.after(16, self._tick)

    def run(self) -> None:
        self.root.mainloop()


def main() -> int:
    ap = argparse.ArgumentParser(description="5D Random Walk fullscreen saver")
    ap.add_argument("--window", action="store_true", help="windowed preview instead of fullscreen")
    args = ap.parse_args()
    try:
        App(windowed=args.window).run()
    except tk.TclError as e:
        print("tkinter error:", e, file=sys.stderr)
        print("On Linux install: sudo apt install python3-tk", file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
