#!/usr/bin/python3
"""hyper — talk to the Mood Hyper Engine (runtime/mood-hyperd).

  hyper                      status
  hyper top                  live per-core view (Hyper Cores highlighted), q / Ctrl+C quits
  hyper run [-n NAME] [-q] -- CMD [ARGS…]
                             run CMD on the Hyper Cores (shows in the Dynamic Island, notifies when done)
  hyper cores off|auto|1|2   how many physical cores to reserve
  hyper lane focus|jobs      focus = foreground app + shell may use the Hyper Cores; jobs = only `hyper run`
  hyper governor manual|adaptive
  hyper irq on|off           steer hardware interrupts off the Hyper Cores
  hyper specs                plain-text spec sheet
"""
import json
import os
import socket
import subprocess
import sys
import time
from pathlib import Path

sys.path.insert(0, "/usr/lib/mood/runtime")
STATE = Path("/run/mood/hyper.json")
SOCK = "/run/mood/hyper.sock"
C = {"h": "\033[38;5;213m", "a": "\033[38;5;117m", "d": "\033[2m", "b": "\033[1m", "r": "\033[0m", "g": "\033[38;5;120m", "y": "\033[38;5;221m"}
if not sys.stdout.isatty():
    C = {k: "" for k in C}


def state():
    try:
        s = json.loads(STATE.read_text())
        s["stale"] = time.time() - s.get("at", 0) > 10
        return s
    except Exception:
        return None


def ask(obj):
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    s.settimeout(5)
    s.connect(SOCK)
    s.sendall((json.dumps(obj) + "\n").encode())
    return json.loads(s.makefile().readline() or "{}")


def fmt(cpus):
    return ",".join(map(str, cpus)) or "none"


def status():
    s = state()
    if not s or s.get("stale"):
        print("Hyper Engine isn't running (sudo systemctl start mood-hyperd).")
        return 1
    cores = s["cores"]
    print(f"{C['b']}{C['h']}⚡ Hyper Engine{C['r']}  lane {C['b']}{s['lane']}{C['r']}  governor {s['config']['governor']}" + (f" ({s['profile']})" if s.get("profile") else ""))
    if cores:
        print(f"  Hyper Cores  {C['h']}{fmt(cores)}{C['r']}  (picked by {s['pickedBy']})   everything else on {fmt(s['rest']) if len(s['rest']) < 24 else str(len(s['rest'])) + ' cpus'}")
        print("  load         " + "  ".join(f"cpu{c} {s['coreLoad'].get(str(c)) or 0:>4.0f}%" for c in cores))
    else:
        print("  Hyper Cores  off")
    f = s.get("focus") or {}
    if f.get("pid"):
        print(f"  focus        {f.get('app') or '?'} (pid {f['pid']}) — {s['boosted']} boosted processes")
    if s.get("watts"):
        print("  power        " + "  ".join(f"{k} {v:.1f} W" for k, v in s["watts"].items()))
    print(f"  irqs moved   {s['irqMoved']}   engine cpu {s['engineLoad']:.1f}%")
    for j in s.get("jobs") or []:
        print(f"  job {j['pid']:>7}  {j['name']}  {int(time.time() - j['started'])} s")
    return 0


def admin(patch):
    r = subprocess.run(["sudo", "-n", "/usr/lib/mood/runtime/mood-admin", "hyper-set", json.dumps(patch)], capture_output=True, text=True)
    if r.returncode:
        print((r.stderr or r.stdout).strip() or "failed", file=sys.stderr)
        return 1
    time.sleep(1.5)
    return status()


def run(argv):
    name, quiet = None, False
    while argv and argv[0] != "--":
        a = argv.pop(0)
        if a in ("-n", "--name") and argv:
            name = argv.pop(0)
        elif a in ("-q", "--quiet"):
            quiet = True
        else:
            argv.insert(0, a)
            break
    if argv and argv[0] == "--":
        argv.pop(0)
    if not argv:
        print("usage: hyper run [-n NAME] [-q] -- CMD [ARGS…]", file=sys.stderr)
        return 2
    name = name or os.path.basename(argv[0])
    r, w = os.pipe()
    pid = os.fork()
    if pid == 0:
        os.close(r)
        cores = []
        try:
            rep = ask({"op": "job", "name": name})
            cores = rep.get("cores") or []
            if cores:
                os.sched_setaffinity(0, set(cores))
        except Exception:
            pass
        os.write(w, json.dumps(cores).encode())
        os.close(w)
        try:
            os.execvp(argv[0], argv)
        except OSError as e:
            print(f"hyper: {e}", file=sys.stderr)
            os._exit(127)
    os.close(w)
    cores = json.loads(os.read(r, 4096) or b"[]")
    os.close(r)
    if not quiet:
        print(f"{C['h']}⚡ {name} on Hyper Cores {fmt(cores)}{C['r']}" if cores else f"{C['d']}(Hyper Cores are off — running normally){C['r']}", file=sys.stderr)
    act = None
    try:
        import moodactivity
        act = f"hyper.{pid}"
        moodactivity.set(act, kind="live", icon="zap", title=name, sub=f"Hyper Cores {fmt(cores)}" if cores else "Running",
                         color="#e879f9", pid=os.getpid(), spinner=True, app="specs")
    except Exception:
        act = None
    t0 = time.time()
    try:
        _, status_ = os.waitpid(pid, 0)
    except KeyboardInterrupt:
        os.kill(pid, 2)
        _, status_ = os.waitpid(pid, 0)
    code = os.waitstatus_to_exitcode(status_)
    took = time.time() - t0
    nice = f"{took:.1f} s" if took < 90 else f"{int(took // 60)} min {int(took % 60)} s"
    if act:
        try:
            moodactivity.clear(act)
            moodactivity.flash(f"hyper.done.{pid}", f"{name} {'done' if code == 0 else 'failed'}", nice if code == 0 else f"exit {code} · {nice}",
                               icon="zap" if code == 0 else "x-circle", color="#e879f9" if code == 0 else "#ef4444", seconds=6)
        except Exception:
            pass
    if took > 20 and os.environ.get("WAYLAND_DISPLAY"):
        subprocess.Popen(["notify-send", "-a", "Hyper Engine", "-i", "mood-specs", f"{name} {'finished' if code == 0 else 'failed'}",
                          f"{'Took' if code == 0 else 'Exit ' + str(code) + ', took'} {nice} on the Hyper Cores"], stderr=subprocess.DEVNULL)
    return code


def top():
    sys.path.insert(0, "/usr/lib/mood/runtime")
    import moodspecs
    moodspecs.live()
    try:
        while True:
            time.sleep(1)
            L = moodspecs.live()
            s = L.get("hyper") or {}
            hyper = set(s.get("cores") or [])
            out = ["\033[H\033[2J" if sys.stdout.isatty() else "", f"{C['b']}{C['h']}⚡ Hyper top{C['r']}  total {L['total'] or 0:.0f}%   "
                   + ("  ".join(f"{k} {v:.1f} W" for k, v in (s.get("watts") or {}).items())) + "\n"]
            for c in L["cores"]:
                pct = c["load"] or 0
                bar = "█" * int(pct / 5) + "░" * (20 - int(pct / 5))
                col = C["h"] if c["cpu"] in hyper else C["a"]
                tag = " ⚡HYPER" if c["cpu"] in hyper else ""
                out.append(f"  cpu{c['cpu']:<3} {col}{bar}{C['r']} {pct:5.1f}%  {c['mhz'] or '':>5}{' MHz' if c['mhz'] else ''}{col}{tag}{C['r']}")
            for ch in L["sensors"][:3]:
                out.append(f"  {C['d']}{ch['chip']}{C['r']} " + "  ".join(f"{t['label']} {t['c']:.0f}°C" for t in ch["temps"][:4]))
            if s.get("focus", {}).get("app"):
                out.append(f"\n  focus: {s['focus']['app']}  ·  boosted {s.get('boosted', 0)} processes  ·  lane {s.get('lane')}")
            print("\n".join(out), flush=True)
    except KeyboardInterrupt:
        return 0


def main():
    a = sys.argv[1:]
    if not a or a[0] in ("status", "-s"):
        return status()
    cmd = a[0]
    if cmd in ("-h", "--help", "help"):
        print(__doc__)
        return 0
    if cmd == "run":
        return run(a[1:])
    if cmd == "top":
        return top()
    if cmd == "specs":
        import moodspecs
        print(moodspecs.sheet(), end="")
        return 0
    if cmd == "cores" and len(a) > 1 and a[1] in ("off", "auto", "0", "1", "2"):
        return admin({"cores": "auto" if a[1] == "auto" else 0 if a[1] == "off" else int(a[1])})
    if cmd == "lane" and len(a) > 1 and a[1] in ("focus", "jobs"):
        return admin({"lane": a[1]})
    if cmd == "governor" and len(a) > 1 and a[1] in ("manual", "adaptive"):
        return admin({"governor": a[1]})
    if cmd == "irq" and len(a) > 1 and a[1] in ("on", "off"):
        return admin({"irq": a[1] == "on"})
    print(__doc__, file=sys.stderr)
    return 2


if __name__ == "__main__":
    sys.exit(main())
