package de.duffy.screencast.miracast

/**
 * Die Bildformate, die der Wi-Fi-Display-Standard kennt, und die Auswahl
 * daraus. Der Fernseher meldet in einer Bitmaske, was er kann; wir suchen das
 * beste Format heraus, das beide Seiten beherrschen.
 */
object WfdFormats {

    data class VideoFormat(val ceaBit: Int, val width: Int, val height: Int, val fps: Int) {
        override fun toString() = "${width}x$height@${fps}"
    }

    /** CEA-Tabelle des Wi-Fi-Display-Standards, Reihenfolge = Bitposition. */
    val CEA = listOf(
        VideoFormat(0, 640, 480, 60),
        VideoFormat(1, 720, 480, 60),
        VideoFormat(2, 720, 480, 60),   // interlaced - meiden
        VideoFormat(3, 720, 576, 50),
        VideoFormat(4, 720, 576, 50),   // interlaced - meiden
        VideoFormat(5, 1280, 720, 30),
        VideoFormat(6, 1280, 720, 60),
        VideoFormat(7, 1920, 1080, 30),
        VideoFormat(8, 1920, 1080, 60),
        VideoFormat(9, 1920, 1080, 60), // interlaced - meiden
        VideoFormat(10, 1280, 720, 25),
        VideoFormat(11, 1280, 720, 50),
        VideoFormat(12, 1920, 1080, 25),
        VideoFormat(13, 1920, 1080, 50),
        VideoFormat(14, 1920, 1080, 50), // interlaced - meiden
        VideoFormat(15, 1280, 720, 24),
        VideoFormat(16, 1920, 1080, 24)
    )

    private val INTERLACED = setOf(2, 4, 9, 14)

    /** Wunschreihenfolge: fluessig und scharf, aber nicht uebertrieben. */
    private val PREFERENCE = listOf(5, 7, 6, 11, 13, 10, 12, 15, 16, 8, 0, 1, 3)

    /** Eine Codec-Beschreibung aus wfd_video_formats. */
    data class SinkCapability(
        val profile: Int,
        val level: Int,
        val ceaMask: Long,
        val vesaMask: Long,
        val hhMask: Long
    )

    /**
     * Zerlegt den Wert von wfd_video_formats.
     * Aufbau: native, preferred-mode, dann je Codec profile/level/cea/vesa/hh/...
     */
    fun parseVideoFormats(value: String): List<SinkCapability> {
        val parts = value.trim().split(Regex("\\s+"))
        if (parts.size < 3) return emptyList()
        val result = ArrayList<SinkCapability>()
        var i = 2 // native und preferred-display-mode ueberspringen
        while (i + 4 < parts.size) {
            val profile = parts[i].toIntOrNull(16) ?: break
            val level = parts[i + 1].toIntOrNull(16) ?: break
            val cea = parts[i + 2].toLongOrNull(16) ?: break
            val vesa = parts[i + 3].toLongOrNull(16) ?: 0
            val hh = parts[i + 4].toLongOrNull(16) ?: 0
            result.add(SinkCapability(profile, level, cea, vesa, hh))
            i += 11 // ein Codec-Block umfasst elf Felder
        }
        return result
    }

    /** Bestes gemeinsames Format. [maxHeight] begrenzt auf Wunsch die Groesse. */
    fun choose(capabilities: List<SinkCapability>, maxHeight: Int = 1080): VideoFormat? {
        val mask = capabilities.fold(0L) { acc, cap -> acc or cap.ceaMask }
        for (bit in PREFERENCE) {
            if (bit in INTERLACED) continue
            if (mask and (1L shl bit) == 0L) continue
            val format = CEA[bit]
            if (format.height > maxHeight) continue
            return format
        }
        return null
    }

    /**
     * Baut den wfd_video_formats-Wert fuer die Antwort: genau ein gesetztes Bit,
     * damit der Fernseher weiss, was wirklich kommt.
     */
    fun buildSelection(format: VideoFormat, profile: Int, level: Int): String {
        val cea = 1L shl format.ceaBit
        return String.format(
            "%02X %02X %02X %02X %08X %08X %08X %02X %04X %04X %02X none none",
            0, 0, profile, level, cea, 0, 0, 0, 0, 0, 0
        )
    }

    /** Liest aus wfd_client_rtp_ports den Port, auf dem der Fernseher lauscht. */
    fun parseRtpPort(value: String?): Int {
        if (value == null) return 0
        val parts = value.trim().split(Regex("\\s+"))
        for (part in parts) {
            val port = part.toIntOrNull()
            if (port != null && port in 1..65535) return port
        }
        return 0
    }
}
