"""Bildschirmaufnahme und Versand als RTP-Strom.

Die eigentliche Arbeit macht ffmpeg: aufnehmen, in H.264 wandeln, in MPEG-TS
verpacken und als RTP verschicken - genau das Paketformat, das der
Wi-Fi-Display-Standard erwartet (Nutzlasttyp 33).
"""

import logging
import os
import shutil
import signal
import subprocess
from typing import List, Optional

from .formats import VideoFormat

log = logging.getLogger("wfd.streamer")


def screen_size(display: str) -> str:
    """Auflösung des Bildschirms - für die Aufnahme braucht ffmpeg sie."""
    for command, pattern in (
        (["xdpyinfo", "-display", display], "dimensions:"),
        (["xrandr", "--display", display, "--current"], "*"),
    ):
        if not shutil.which(command[0]):
            continue
        try:
            output = subprocess.run(command, capture_output=True, text=True,
                                    timeout=5).stdout
        except (subprocess.SubprocessError, OSError):
            continue
        for line in output.splitlines():
            if pattern not in line:
                continue
            for token in line.split():
                if "x" in token and token.replace("x", "").isdigit():
                    return token
    return "1920x1080"


class Streamer:
    """Hält den laufenden ffmpeg-Prozess."""

    def __init__(self, source: str = "auto", display: Optional[str] = None,
                 bitrate_mbit: int = 8):
        self.source = source
        self.display = display or os.environ.get("DISPLAY", ":0")
        self.bitrate_mbit = bitrate_mbit
        self.process: Optional[subprocess.Popen] = None
        self.command: List[str] = []

    def build_command(self, host: str, port: int, fmt: VideoFormat) -> List[str]:
        command = ["ffmpeg", "-hide_banner", "-loglevel", "warning", "-nostdin"]

        mode = self.source
        if mode == "auto":
            mode = "x11" if os.environ.get("DISPLAY") else "test"

        if mode == "x11":
            command += ["-f", "x11grab", "-framerate", str(fmt.fps),
                        "-video_size", screen_size(self.display),
                        "-draw_mouse", "1", "-i", self.display]
        elif mode == "kms":
            # Für Wayland-Sitzungen; benötigt erweiterte Rechte.
            command += ["-f", "kmsgrab", "-framerate", str(fmt.fps), "-i", "-",
                        "-vf", "hwdownload,format=bgr0"]
        elif mode == "test":
            command += ["-f", "lavfi", "-i",
                        f"testsrc=size={fmt.width}x{fmt.height}:rate={fmt.fps}"]
        else:
            raise ValueError(f"Unbekannte Bildquelle: {mode}")

        # Auf die ausgehandelte Größe bringen, Seitenverhältnis erhalten.
        scale = (f"scale={fmt.width}:{fmt.height}:force_original_aspect_ratio=decrease,"
                 f"pad={fmt.width}:{fmt.height}:(ow-iw)/2:(oh-ih)/2,format=yuv420p")
        rate = self.bitrate_mbit

        command += [
            "-vf", scale,
            "-c:v", "libx264",
            "-profile:v", "baseline",     # ohne B-Bilder, das versteht jeder Empfänger
            "-preset", "ultrafast",
            "-tune", "zerolatency",
            "-x264-params",
            f"keyint={fmt.fps * 2}:min-keyint={fmt.fps * 2}:scenecut=0:bframes=0:nal-hrd=cbr",
            "-b:v", f"{rate}M", "-minrate", f"{rate}M", "-maxrate", f"{rate}M",
            "-bufsize", f"{max(1, rate // 4)}M",
            "-r", str(fmt.fps),
            "-muxdelay", "0", "-muxpreload", "0",
            "-f", "rtp_mpegts",
            f"rtp://{host}:{port}?pkt_size=1316",
        ]
        return command

    def start(self, host: str, port: int, fmt: VideoFormat):
        if self.process:
            return
        self.command = self.build_command(host, port, fmt)
        log.info("Starte Bildstrom: %s", " ".join(self.command))
        self.process = subprocess.Popen(self.command)

    def is_running(self) -> bool:
        return self.process is not None and self.process.poll() is None

    def stop(self):
        if not self.process:
            return
        try:
            self.process.send_signal(signal.SIGINT)
            self.process.wait(timeout=4)
        except (subprocess.TimeoutExpired, OSError):
            try:
                self.process.kill()
            except OSError:
                pass
        self.process = None
