"""Telling you a clip was saved, while you are looking at a game.

The app window is not on screen when the clip hotkey matters — it is
behind a fullscreen game, or hidden in the tray. So an in-page toast is
the one thing that cannot do this job. What can: the tray balloon, which
Windows draws over a game, and a sound, which needs no screen at all.

Both are best effort. A machine with no tray and no audio device still
records clips; it just does it quietly.
"""
import os
import struct
import threading
import wave

from clipshelf import config

SOUND = config.DATA / "clip-saved.wav"

# A short two-tone blip. Generated rather than shipped: it is 40 ms of
# arithmetic, and a generated file cannot be the wrong sample rate for
# somebody's device or go missing from a release.
RATE = 44100
TONES = ((880.0, 0.045), (1318.5, 0.075))     # A5 -> E6, a small rising chime


def _write_sound(path) -> bool:
    import math
    try:
        frames = bytearray()
        for freq, dur in TONES:
            n = int(RATE * dur)
            for i in range(n):
                # a raised-cosine envelope, so it starts and ends at zero —
                # a square-edged tone clicks on cheap speakers
                env = 0.5 - 0.5 * math.cos(2 * math.pi * i / max(1, n - 1))
                v = int(9000 * env * math.sin(2 * math.pi * freq * i / RATE))
                frames += struct.pack("<h", v)
        path.parent.mkdir(parents=True, exist_ok=True)
        tmp = path.with_suffix(".wav.tmp")
        with wave.open(str(tmp), "wb") as w:
            w.setnchannels(1)
            w.setsampwidth(2)
            w.setframerate(RATE)
            w.writeframes(bytes(frames))
        tmp.replace(path)
        return True
    except Exception:
        return False


def _ensure_sound():
    if not SOUND.exists():
        _write_sound(SOUND)
    return SOUND.exists()


def play():
    """The blip, off the calling thread — a hotkey must never wait on audio."""
    if not config.CFG.get("clip_sound", True):
        return

    def go():
        try:
            if not _ensure_sound():
                return
            if os.name == "nt":
                import winsound          # stdlib on Windows: no dependency
                winsound.PlaySound(str(SOUND),
                                   winsound.SND_FILENAME | winsound.SND_ASYNC)
            else:
                import shutil
                import subprocess
                for player in ("paplay", "aplay", "afplay"):
                    if shutil.which(player):
                        subprocess.run([player, str(SOUND)],
                                       capture_output=True, timeout=5)
                        break
        except Exception:
            pass                        # a missing sound card is not an error

    threading.Thread(target=go, daemon=True).start()


def balloon(title: str, message: str):
    """A tray notification, which Windows will draw over a fullscreen game."""
    if not config.CFG.get("clip_notify", True):
        return
    try:
        from clipshelf import desktop
        tray = getattr(desktop, "_tray", None)
        if tray is not None and getattr(tray, "HAS_NOTIFICATION", True):
            tray.notify(message, title)
    except Exception:
        pass                            # no tray, or a backend without balloons


def clip_saved(label: str = ""):
    """Both halves of 'that worked', for one saved clip."""
    balloon("Clipshelf", f"Saved {label}".strip() if label else "Clip saved")
    play()
