package jp.juggler.util.media

import android.content.Context
import android.media.MediaCodecList
import android.media.MediaFormat
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import jp.juggler.util.log.LogCategory
import java.io.File
import kotlin.math.max
import kotlin.math.min

/**
 * 動画の情報
 */
@Suppress("MemberVisibilityCanBePrivate")
class VideoInfo(
    // val file: File,
    mmr: MediaMetadataRetriever,
    val bytesLength: Long,
    val uri: Uri,
) {
    companion object {
        private val log = LogCategory("VideoInfo")

        val File.videoInfo: VideoInfo
            get() = MediaMetadataRetriever().use { mmr ->
                mmr.setDataSource(canonicalPath)
                VideoInfo(mmr, length(), Uri.fromFile(canonicalFile))
            }

        fun Uri.videoInfo(context: Context, length: Long): VideoInfo =
            MediaMetadataRetriever().use { mmr ->
                mmr.setDataSource(context, this)
                VideoInfo(mmr, length, this)
            }

        private fun MediaMetadataRetriever.string(key: Int) =
            extractMetadata(key)

        private fun MediaMetadataRetriever.int(key: Int) =
            string(key)?.toIntOrNull()

        private fun MediaMetadataRetriever.long(key: Int) =
            string(key)?.toLongOrNull()

        /**
         * 調査のためコーデックを列挙して情報をログに出す
         */
        fun dumpCodec() {
            val mcl = MediaCodecList(MediaCodecList.REGULAR_CODECS)
            for (info in mcl.codecInfos) {
                try {
                    if (!info.isEncoder) continue
                    val caps = try {
                        info.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_AVC) ?: continue
                    } catch (ex: Throwable) {
                        log.w(ex, "getCapabilitiesForType failed.")
                        continue
                    }

                    for (colorFormat in caps.colorFormats) {
                        log.i("${info.name} color 0x${colorFormat.toString(16)}")
                        // OMX.qcom.video.encoder.avc color 7fa30c04 不明
                        // OMX.qcom.video.encoder.avc color 7f000789 COLOR_FormatSurface
                        // OMX.qcom.video.encoder.avc color 7f420888 COLOR_FormatYUV420Flexible
                        // OMX.qcom.video.encoder.avc color 15 COLOR_Format32bitBGRA8888
                    }
                    caps.videoCapabilities.bitrateRange?.let { range ->
                        log.i("bitrateRange $range")
                    }
                    caps.videoCapabilities.supportedFrameRates?.let { range ->
                        log.i("supportedFrameRates $range")
                    }
                    if (Build.VERSION.SDK_INT >= 28) {
                        caps.encoderCapabilities.qualityRange?.let { range ->
                            log.i("qualityRange $range")
                        }
                    }
                } catch (ex: Throwable) {
                    log.w(ex, "dumpCodec failed.")
                    // type is not supported
                }
            }
        }
    }

    data class Size(var w: Int, var h: Int) {
        override fun toString() = "[$w,$h]"

        private val aspect: Float get() = w.toFloat() / h.toFloat()

        /**
         * アスペクト比を維持しつつ上限に合わせた解像度を提案する
         * - 拡大はしない
         */
        fun scaleTo(limitLonger: Int, limitShorter: Int): Size {
            val inSize = this
            // ゼロ除算対策
            if (inSize.w < 1 || inSize.h < 1) {
                return Size(limitLonger, limitShorter)
            }
            val inAspect = inSize.aspect
            // 入力の縦横に合わせて上限を決める
            val outSize = if (inAspect >= 1f) {
                Size(limitLonger, limitShorter)
            } else {
                Size(limitShorter, limitLonger)
            }
            // 縦横比を比較する
            return if (inAspect >= outSize.aspect) {
                // 入力のほうが横長なら横幅基準でスケーリングする
                // 拡大はしない
                val scale = outSize.w.toFloat() / inSize.w.toFloat()
                if (scale >= 1f) inSize else outSize.apply {
                    h = min(h, (scale * inSize.h + 0.5f).toInt())
                }
            } else {
                // 入力のほうが縦長なら縦幅基準でスケーリングする
                // 拡大はしない
                val scale = outSize.h.toFloat() / inSize.h.toFloat()
                if (scale >= 1f) inSize else outSize.apply {
                    w = min(w, (scale * inSize.w + 0.5f).toInt())
                }
            }
        }
    }

    val mimeType = mmr.string(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)

    val rotation = mmr.int(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) ?: 0

    val size = Size(
        mmr.int(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) ?: 0,
        mmr.int(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) ?: 0,
    )

    val squarePixels: Int get() = max(1, size.w) * max(1, size.h)

    val bitrate = mmr.int(MediaMetadataRetriever.METADATA_KEY_BITRATE)

    val duration = mmr.long(MediaMetadataRetriever.METADATA_KEY_DURATION)
        ?.toFloat()?.div(1000)?.takeIf { it > 0.1f }

    val frameCount = if (Build.VERSION.SDK_INT >= 28) {
        mmr.int(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT)?.takeIf { it > 0 }
    } else {
        null
    }

    val frameRatio = if (frameCount != null && duration != null) {
        frameCount.toFloat().div(duration)
    } else {
        null
    }

    val audioSampleRate = if (Build.VERSION.SDK_INT >= 31) {
        mmr.int(MediaMetadataRetriever.METADATA_KEY_SAMPLERATE)?.takeIf { it > 0 }
    } else {
        null
    }

    val hasAudio = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO)
        ?.let { it == "yes" }

    val hasVideo = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO)
        ?.let { it == "yes" }

    val actualBps by lazy {
        // ファイルサイズを取得できないならエラー
        if (bytesLength <= 0L) return@lazy null

        // 時間帳が短すぎるなら算出できない
        if (duration == null || duration < 0.1f) return@lazy null

        // bpsを計算
        bytesLength.toFloat().div(duration).times(8).toInt()
    }

    /**
     * 動画のファイルサイズが十分に小さいなら真
     */
    fun isSmallEnough(limitBps: Int): Boolean {
        // ファイルサイズを取得できないならエラー
        if (bytesLength <= 0L) error("too small file. $uri")
        // ファイルサイズが500KB以内ならビットレートを気にしない
        if (bytesLength < 500_000) return true

        // ファイルサイズからビットレートを計算できなかったなら再エンコード必要
        val actualBps = this.actualBps ?: return false

        // bpsを計算
        log.i("isSmallEnough duration=$duration, bps=$actualBps/$limitBps")
        return actualBps <= limitBps
    }

    override fun toString() =
        "rotation=$rotation, size=$size, frameRatio=$frameRatio, bitrate=${actualBps ?: bitrate}, audioSampleRate=$audioSampleRate, mimeType=$mimeType, uri=$uri"
}
