"""DUXA PC Optimizer - Python edition (Windows 10/11, standard library only).
Run:  python optimizer.py        (GUI)        python optimizer.py --mode eco   (apply once)
Modes: balanced | performance | ups (60 Hz, effects off) | eco (super efficiency).
Shares %LOCALAPPDATA%/DuxaOptimizer/config.json with the exe / PowerShell versions."""
import ctypes, json, os, re, subprocess, sys
from ctypes import wintypes

CFG = os.path.join(os.environ.get("LOCALAPPDATA", "."), "DuxaOptimizer", "config.json")
os.makedirs(os.path.dirname(CFG), exist_ok=True)
GUIDS = {"balanced": "381b4222-f694-41f0-9685-ff5bb260df2e", "saver": "a1841308-3541-4fab-bc81-f71556f20b4a",
         "high": "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c", "ultimate": "e9a42b02-d5df-448d-aa00-03f14749eb61"}
RX = r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"
NAMES = {"balanced": "Сбалансированный / Balanced", "performance": "Максимум (игры) / Performance", "ups": "Экономия 60 Гц / Economy", "eco": "Супер-эффективность / Super eco"}
COLORS = {"balanced": "#5ef2ff", "performance": "#ff4fd8", "ups": "#ffc14d", "eco": "#b6ff5e"}


def load():
    cfg = {"mode": "balanced", "previous": "balanced", "ups_hz": 60, "original_hz": 0, "original_plan": "", "perf_plan_guid": "d93243ae-6a3d-4860-aaf0-cd4440ef5026"}
    try:
        cfg.update(json.load(open(CFG, encoding="utf-8-sig")))
    except Exception:
        pass
    return cfg


def save(cfg):
    json.dump(cfg, open(CFG, "w", encoding="utf-8"), ensure_ascii=False, indent=1)


def pc(*a):
    return subprocess.run(["powercfg", *a], capture_output=True, text=True, errors="ignore", creationflags=0x08000000).stdout


def plans():
    return [(m.group(1), m.group(2), bool(m.group(3))) for l in pc("/list").splitlines() for m in [re.search(RX + r"\s*\((.+?)\)(\s*\*)?", l)] if m]


def ensure(name, bases):
    for g, n, _ in plans():
        if n == name:
            return g
    for b in bases:
        m = re.search(RX, pc("-duplicatescheme", b))
        if m:
            pc("-changename", m.group(1), name)
            return m.group(1)


def setv(g, sub, st, v):
    pc("/setacvalueindex", g, sub, st, str(v)); pc("/setdcvalueindex", g, sub, st, str(v))


class DEVMODE(ctypes.Structure):
    _fields_ = [("n", ctypes.c_char * 32), ("sv", ctypes.c_short), ("dv", ctypes.c_short), ("size", ctypes.c_short), ("de", ctypes.c_short), ("fields", ctypes.c_int),
                ("px", ctypes.c_int), ("py", ctypes.c_int), ("ori", ctypes.c_int), ("fo", ctypes.c_int), ("col", ctypes.c_short), ("dup", ctypes.c_short), ("yr", ctypes.c_short),
                ("tt", ctypes.c_short), ("coll", ctypes.c_short), ("fn", ctypes.c_char * 32), ("ll", ctypes.c_short), ("bpp", ctypes.c_int), ("w", ctypes.c_int), ("h", ctypes.c_int),
                ("df", ctypes.c_int), ("hz", ctypes.c_int), ("a", ctypes.c_int * 8)]


def _dm(i=-1):
    d = DEVMODE(); d.size = ctypes.sizeof(DEVMODE)
    return d if ctypes.windll.user32.EnumDisplaySettingsA(None, i, ctypes.byref(d)) else None


def hz():
    d = _dm(); return d.hz if d else 0


def rates():
    c = _dm(); out = set(); i = 0
    while True:
        d = _dm(i); i += 1
        if not d: break
        if (d.w, d.h, d.bpp) == (c.w, c.h, c.bpp) and d.hz > 1: out.add(d.hz)
    return sorted(out)


def set_hz(target):
    r = rates()
    if not r: return
    target = min(r, key=lambda x: abs(x - target))
    d = _dm(); d.hz = target; d.fields = 0x400000
    ctypes.windll.user32.ChangeDisplaySettingsExA(None, ctypes.byref(d), None, 1, None)


FX = [(0x103F), (0x1043), (0x1003), (0x1017), (0x1015), (0x1013), (0x1005), (0x1007), (0x101B)]


def effects(on):
    for code in (FX if on else reversed(FX)):
        ctypes.windll.user32.SystemParametersInfoW(code, 0, ctypes.c_void_p(1 if on else 0), 3)


def apply(mode, cfg):
    if mode == "balanced":
        pc("/setactive", cfg.get("original_plan") or GUIDS["balanced"]); set_hz(cfg.get("original_hz") or hz()); effects(True)
    elif mode in ("ups", "eco"):
        eco = mode == "eco"
        g = ensure("DUXA ECO" if eco else "DUXA UPS", [GUIDS["saver"], GUIDS["balanced"]])
        if g:
            for sub, st, v in (("SUB_PROCESSOR", "PROCTHROTTLEMAX", 25 if eco else 50), ("SUB_PROCESSOR", "PROCTHROTTLEMIN", 5), ("SUB_PROCESSOR", "PERFBOOSTMODE", 0),
                               ("SUB_VIDEO", "VIDEOIDLE", 30 if eco else 60), ("SUB_VIDEO", "VIDEODIM", 15 if eco else 30), ("SUB_DISK", "DISKIDLE", 60 if eco else 120), ("SUB_PCIEXPRESS", "ASPM", 2)):
                setv(g, sub, st, v)
            pc("/setactive", g)
        set_hz(cfg.get("ups_hz", 60)); effects(False)
    elif mode == "performance":
        mine = [g for g, _, _ in plans() if g.lower() == str(cfg.get("perf_plan_guid", "")).lower()]
        g = mine[0] if mine else ensure("DUXA Performance", [GUIDS["ultimate"], GUIDS["high"]])
        if g:
            if not mine:
                for sub, st, v in (("SUB_PROCESSOR", "PROCTHROTTLEMAX", 100), ("SUB_PROCESSOR", "PROCTHROTTLEMIN", 100), ("SUB_PROCESSOR", "PERFBOOSTMODE", 2), ("SUB_VIDEO", "VIDEOIDLE", 0), ("SUB_DISK", "DISKIDLE", 0)):
                    setv(g, sub, st, v)
            pc("/setactive", g)
        set_hz(max(rates() or [hz()])); effects(True)
    if cfg["mode"] != mode: cfg["previous"] = cfg["mode"]
    cfg["mode"] = mode; save(cfg)


def remember(cfg):
    if cfg["mode"] == "balanced":
        cfg["original_hz"] = cfg.get("original_hz") or hz()
        if not cfg.get("original_plan"):
            for g, n, act in plans():
                if act and not n.startswith("DUXA"): cfg["original_plan"] = g
        save(cfg)


def gui():
    import tkinter as tk
    cfg = load(); remember(cfg)
    root = tk.Tk(); root.title("DUXA PC Optimizer"); root.configure(bg="#0a0f22"); root.geometry("520x470"); root.resizable(False, False)
    tk.Label(root, text="DUXA PC Optimizer", font=("Segoe UI", 20, "bold"), fg="#e8eeff", bg="#0a0f22").pack(anchor="w", padx=22, pady=(16, 0))
    status = tk.Label(root, text="", font=("Segoe UI", 10), fg="#8a96c0", bg="#0a0f22"); status.pack(anchor="w", padx=24, pady=(0, 10))
    btns = {}

    def refresh():
        status.config(text="%d Hz  ·  now: %s  ·  previous: %s" % (hz(), NAMES.get(cfg["mode"], cfg["mode"]), NAMES.get(cfg.get("previous"), "-")))
        for m, b in btns.items():
            b.config(bg="#1a2342" if m == cfg["mode"] else "#131a33", highlightbackground=COLORS[m] if m == cfg["mode"] else "#25305c")

    def go(m):
        status.config(text="applying…"); root.update_idletasks(); apply(m, cfg); refresh()

    for m in ("balanced", "performance", "ups", "eco"):
        b = tk.Button(root, text=NAMES[m], font=("Segoe UI", 12, "bold"), fg=COLORS[m], bg="#131a33", activebackground="#1a2342", activeforeground=COLORS[m], relief="flat",
                      highlightthickness=2, highlightbackground="#25305c", cursor="hand2", height=2, command=lambda m=m: go(m)); b.pack(fill="x", padx=22, pady=6); btns[m] = b
    tk.Button(root, text="↩ Previous / Вернуть предыдущий", font=("Segoe UI", 11), fg="#e8eeff", bg="#131a33", relief="flat", cursor="hand2",
              command=lambda: go(cfg["previous"]) if cfg.get("previous") != cfg["mode"] else None).pack(fill="x", padx=22, pady=(14, 4))
    tk.Label(root, text="Economy modes: 60 Hz + visual effects off. Leaving them restores your settings.", font=("Segoe UI", 9), fg="#6f7aa6", bg="#0a0f22").pack(pady=6)
    refresh(); root.mainloop()


if __name__ == "__main__":
    if "--mode" in sys.argv:
        c = load(); apply(sys.argv[sys.argv.index("--mode") + 1], c); print("applied", c["mode"])
    else:
        gui()
