package de.duffy.screencast

import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File

/**
 * Schleust einen echten H.264-Bitstream durch beide Muxer und schreibt das
 * Ergebnis raus, damit es anschliessend mit ffprobe geprueft werden kann.
 */
class MuxerTest {

    private fun sampleFile(): File {
        val path = System.getProperty("sampleH264")
            ?: error("System-Property sampleH264 fehlt")
        return File(path)
    }

    private fun outDir(): File =
        File(System.getProperty("muxOutDir") ?: "build/muxtest").apply { mkdirs() }

    /** Zerlegt den Bitstream in Zugriffseinheiten (ein Frame = eine Slice). */
    private fun accessUnits(data: ByteArray): Triple<ByteArray, ByteArray, List<Pair<List<ByteArray>, Boolean>>> {
        val nals = Fmp4Muxer.splitNalUnits(data, 0, data.size)
        var sps: ByteArray? = null
        var pps: ByteArray? = null
        val frames = ArrayList<Pair<List<ByteArray>, Boolean>>()
        var current = ArrayList<ByteArray>()
        var currentKey = false
        var hasSlice = false

        for (nal in nals) {
            when (val type = nal[0].toInt() and 0x1F) {
                7 -> sps = nal
                8 -> pps = nal
                else -> {
                    // Eine neue Zugriffseinheit beginnt, sobald nach einer Slice
                    // wieder Vorspann (AUD/SEI) oder die naechste Slice kommt.
                    if (hasSlice && (type == 9 || type == 6 || type == 1 || type == 5)) {
                        frames.add(Pair(current, currentKey))
                        current = ArrayList(); hasSlice = false; currentKey = false
                    }
                    if (type == 1 || type == 5) { hasSlice = true; currentKey = type == 5 }
                    current.add(nal)
                }
            }
        }
        if (hasSlice) frames.add(Pair(current, currentKey))
        return Triple(sps!!, pps!!, frames)
    }

    @Test
    fun fragmentedMp4IsPlayable() {
        val (sps, pps, frames) = accessUnits(sampleFile().readBytes())
        val muxer = Fmp4Muxer(sps, pps, 640, 480)
        val out = File(outDir(), "out.mp4")
        out.outputStream().use { stream ->
            stream.write(muxer.initSegment())
            var pts = 0L
            for ((nals, key) in frames) {
                stream.write(muxer.fragment(nals, pts, 33_333L, key))
                pts += 33_333L
            }
        }
        println("codec=" + muxer.codecString + " frames=" + frames.size)
        assertTrue("Keine Frames erkannt", frames.size > 30)
        assertTrue(out.length() > 10_000)
    }

    @Test
    fun transportStreamIsPlayable() {
        val (sps, pps, frames) = accessUnits(sampleFile().readBytes())
        val muxer = TsMuxer(sps, pps)
        val out = File(outDir(), "out.ts")
        out.outputStream().use { stream ->
            var pts = 0L
            for ((nals, key) in frames) {
                stream.write(muxer.frame(nals, pts, key))
                pts += 33_333L
            }
        }
        assertTrue("TS muss ein Vielfaches von 188 sein", out.length() % 188L == 0L)
        assertTrue(out.length() > 10_000)
    }

    @Test
    fun crcMatchesKnownMpegValue() {
        // Referenzwert fuer CRC-32/MPEG-2 ueber "123456789".
        val crc = TsMuxer.crc32("123456789".toByteArray())
        assertTrue("CRC falsch: " + java.lang.Long.toHexString(crc), crc == 0x0376E6E7L)
    }
}
