"""Windows-only parts of the macropad helper: built-in sensors, tray icon, autostart, dialogs.

Used by macropad.exe (one file, no console). The sensors come from LibreHardwareMonitorLib inside the
exe (HardwareMonitor package), so the LibreHardwareMonitor program and its web server are not needed.
CPU temperature also needs the PawnIO driver; the exe offers to install it once (winget).
"""
import ctypes
import os
import subprocess
import sys
import tempfile
import threading
from pathlib import Path
from xml.sax.saxutils import escape

APP_DIR = Path(os.environ.get('APPDATA', Path.home())) / 'Macropad'
TASK = 'Macropad'
NO_WINDOW = 0x08000000
MB_YESNO, MB_OK, MB_ICONQUESTION, MB_ICONINFO = 0x4, 0x0, 0x20, 0x40
MB_TOPMOST, MB_SETFOREGROUND = 0x40000, 0x10000
IDYES = 6
PAWNIO_WINGET = ['winget', 'install', '--id', 'namazso.PawnIO', '-e', '--silent',
                 '--accept-package-agreements', '--accept-source-agreements']


def message(text, question=False):
    flags = (MB_YESNO | MB_ICONQUESTION if question else MB_OK | MB_ICONINFO) | MB_TOPMOST | MB_SETFOREGROUND
    return ctypes.windll.user32.MessageBoxW(None, text, 'Макропад', flags) == IDYES


def single_instance():
    """True for the first copy; a second copy (autostart + double click) must quit."""
    single_instance.handle = ctypes.windll.kernel32.CreateMutexW(None, False, 'Local\\MacropadHelper')
    return ctypes.windll.kernel32.GetLastError() != 183   # ERROR_ALREADY_EXISTS


def is_admin():
    try:
        return bool(ctypes.windll.shell32.IsUserAnAdmin())
    except OSError:
        return False


def quiet(cmd):
    return subprocess.run(cmd, capture_output=True, text=True, creationflags=NO_WINDOW)


def once(flag):
    """True the first time a question with this name is asked on this computer."""
    marker = APP_DIR / f'.asked_{flag}'

    if marker.exists():
        return False

    APP_DIR.mkdir(parents=True, exist_ok=True)
    marker.write_text('1')
    return True


# --- autostart: a Task Scheduler task, because a program with admin rights cannot start from the Run key ---

def autostart_enabled():
    return quiet(['schtasks', '/Query', '/TN', TASK]).returncode == 0


def set_autostart(on):
    if not on:
        return quiet(['schtasks', '/Delete', '/TN', TASK, '/F']).returncode == 0

    user = escape(f"{os.environ.get('USERDOMAIN', '')}\\{os.environ.get('USERNAME', '')}")
    xml = f'''<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <Triggers><LogonTrigger><Enabled>true</Enabled><UserId>{user}</UserId><Delay>PT10S</Delay></LogonTrigger></Triggers>
  <Principals><Principal id="Author"><UserId>{user}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>HighestAvailable</RunLevel></Principal></Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author"><Exec><Command>"{escape(sys.executable)}"</Command></Exec></Actions>
</Task>
'''
    path = Path(tempfile.gettempdir()) / 'macropad_task.xml'
    path.write_text(xml, encoding='utf-16')

    try:
        return quiet(['schtasks', '/Create', '/TN', TASK, '/XML', str(path), '/F']).returncode == 0
    finally:
        path.unlink(missing_ok=True)


# --- sensors ---

def pawnio_installed():
    return quiet(['sc', 'query', 'PawnIO']).returncode == 0


def install_pawnio(log):
    try:
        result = quiet(PAWNIO_WINGET)
    except OSError as e:
        log(f'winget not available: {e}')
        return False

    log(f'PawnIO install: exit {result.returncode} {result.stdout.strip()[-200:]}')
    return pawnio_installed()


class LhmLib:
    """LibreHardwareMonitorLib through pythonnet; yields the same readings as the web server."""

    def __init__(self):
        from HardwareMonitor.Hardware import Computer

        self.computer = Computer()
        self.computer.IsCpuEnabled = True
        self.computer.IsGpuEnabled = True
        self.computer.IsMemoryEnabled = True
        self.computer.Open()

    def readings(self):
        out = []

        for hw in self.computer.Hardware:
            hw.Update()
            sensors = list(hw.Sensors)

            for sub in hw.SubHardware:
                sub.Update()
                sensors += list(sub.Sensors)

            for s in sensors:
                if s.Value is not None:
                    out.append((str(s.Identifier), str(s.SensorType), str(s.Name), float(s.Value)))

        return out

    def close(self):
        self.computer.Close()


# --- tray icon ---

def icon_image():
    from PIL import Image, ImageDraw

    img = Image.new('RGBA', (64, 64), (0, 0, 0, 0))
    d = ImageDraw.Draw(img)
    d.rounded_rectangle((2, 10, 62, 54), 8, fill=(146, 43, 54))
    d.rectangle((8, 16, 56, 30), fill=(20, 20, 20))
    d.rectangle((11, 19, 30, 27), fill=(120, 200, 255))

    for x in (8, 26, 44):
        d.rounded_rectangle((x, 35, x + 12, 48), 3, fill=(240, 240, 240))

    return img


def run_tray(state, worker, config_path, log_path, flash):
    """Tray icon in the main thread; the serial loop runs in `worker`. flash(hex name) asks and flashes."""
    import pystray

    def toggle_autostart(icon, item):
        if not set_autostart(not autostart_enabled()):
            message('Не вдалося змінити автозапуск. Запустіть macropad.exe ще раз і погодьтесь на права адміністратора.')

    def quit_app(icon, item):
        icon.stop()

    menu = pystray.Menu(
        pystray.MenuItem(lambda item: state['status'], None, enabled=False),
        pystray.MenuItem('Запускати разом з Windows', toggle_autostart, checked=lambda item: autostart_enabled()),
        pystray.MenuItem('Налаштування кнопок', lambda icon, item: os.startfile(config_path)),
        pystray.MenuItem('Журнал', lambda icon, item: os.startfile(log_path)),
        pystray.Menu.SEPARATOR,
        pystray.MenuItem('Записати ігри на плату', lambda icon, item: threading.Thread(target=flash, args=('macropad_games.hex',), daemon=True).start()),
        pystray.MenuItem('Записати прошивку для ПК', lambda icon, item: threading.Thread(target=flash, args=('macropad_nano.hex',), daemon=True).start()),
        pystray.Menu.SEPARATOR,
        pystray.MenuItem('Вийти', quit_app),
    )
    icon = pystray.Icon('macropad', icon_image(), 'Макропад', menu)

    def update_title():
        while True:
            icon.title = 'Макропад: ' + state['status']
            state['changed'].wait(5)
            state['changed'].clear()

    threading.Thread(target=worker, daemon=True).start()
    threading.Thread(target=update_title, daemon=True).start()
    icon.run()
