package com.gh.common.video

import android.content.Context
import android.net.Uri
import android.os.Looper
import android.view.Surface
import android.view.SurfaceHolder
import androidx.annotation.Size
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.DefaultRenderersFactory.ExtensionRendererMode
import com.google.android.exoplayer2.analytics.AnalyticsListener
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime
import com.google.android.exoplayer2.decoder.DecoderCounters
import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.MappingTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import tv.danmaku.ijk.media.exo2.ExoSourceManager
import tv.danmaku.ijk.media.exo2.demo.EventLogger
import tv.danmaku.ijk.media.player.AbstractMediaPlayer
import tv.danmaku.ijk.media.player.MediaInfo
import tv.danmaku.ijk.media.player.misc.IjkTrackInfo
import java.io.File
import java.io.FileDescriptor

/**
 * 基于IjkExo2MediaPlayer修改，实现列表视频播放的适配，主要做了如下改动：
 * 1. 复用SimpleExoPlayer的实例，避免在列表中使用时造成的卡顿
 * 2. 新增updateAppContext方法，提供切换上下文环境的入口
 * 3. 移除prepareAsync的Handler调用机制，避免频繁调用造成的队列拥堵问题
 */
class CustomIjkExo2MediaPlayer : AbstractMediaPlayer(), Player.EventListener, AnalyticsListener {

    private var _appContext: Context? = null
    private var internalPlayer: SimpleExoPlayer? = null
    private var eventLogger: EventLogger? = null
    private var rendererFactory: DefaultRenderersFactory? = null
    private var mediaSource: MediaSource? = null
    private var trackSelector: MappingTrackSelector? = null
    private var loadControl: LoadControl? = null
    private var dataSource: String? = null
    private var surface: Surface? = null
    private var headers: MutableMap<String, String> = HashMap()
    private var speedPlaybackParameters: PlaybackParameters? = null
    private var videoWidth = 0
    private var videoHeight = 0
    private var lastReportedPlaybackState = 0
    private var isLastReportedPlayWhenReady = false
    private var isPreparing = true
    private var isBuffering = false
    private var isLooping = false

    /**
     * 是否带上header
     */
    var isPreview = false

    /**
     * 是否开启缓存
     */
    var isCache: Boolean = false

    /**
     * dataSource等的帮组类
     */
    private var _exoHelper: ExoSourceManager? = null

    /**
     * 缓存目录，可以为空
     */
    var cacheDir: File? = null

    /**
     * 类型覆盖
     */
    var overrideExtension: String? = null

    private var audioSessionId = C.AUDIO_SESSION_ID_UNSET

    val appContext: Context? get() = _appContext

    val exoHelper: ExoSourceManager? get() = _exoHelper

    fun updateAppContext(context: Context) {
        val appContext = context.applicationContext.apply {
            _appContext = this
        }
        resetFlags()
        _exoHelper = ExoSourceManager.newInstance(context, headers)
        val trackSelector = DefaultTrackSelector(appContext).also {
            trackSelector = it
        }
        val eventLogger = EventLogger(trackSelector).also {
            eventLogger = it
        }
        val preferExtensionDecoders = true
        val useExtensionRenderers = true //是否开启扩展
        @ExtensionRendererMode val extensionRendererMode =
            if (useExtensionRenderers) (if (preferExtensionDecoders) DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER else DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) else DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
        val rendererFactory = DefaultRenderersFactory(appContext!!).also {
            it.setExtensionRendererMode(extensionRendererMode)
            rendererFactory = it
        }
        val loadControl = DefaultLoadControl().also {
            loadControl = it
        }
        internalPlayer = SimpleExoPlayer.Builder(appContext, rendererFactory)
            .setLooper(Looper.myLooper()!!)
            .setTrackSelector(trackSelector)
            .setLoadControl(loadControl)
            .build()
            .also {
                it.addListener(this)
                it.addAnalyticsListener(this)
                it.addListener(eventLogger)
            }
    }

    private fun getVideoRendererIndex(): Int {
        if (internalPlayer != null) {
            for (i in 0 until internalPlayer!!.rendererCount) {
                if (internalPlayer!!.getRendererType(i) == C.TRACK_TYPE_VIDEO) {
                    return i
                }
            }
        }
        return 0
    }

    override fun setDisplay(sh: SurfaceHolder?) {
        if (sh == null) setSurface(null) else setSurface(sh.surface)
    }

    override fun setSurface(surface: Surface?) {
        internalPlayer?.setVideoSurface(surface)
            ?: let {
                this.surface = surface
            }
    }


    override fun setDataSource(context: Context?, uri: Uri, headers: Map<String, String>?) {
        if (headers != null) {
            this.headers.clear()
            this.headers.putAll(headers)
        }
        setDataSource(context, uri)
    }

    override fun setDataSource(path: String?) {
        setDataSource(appContext, Uri.parse(path))
    }

    override fun setDataSource(context: Context?, uri: Uri) {
        dataSource = uri.toString()
        mediaSource = exoHelper?.getMediaSource(dataSource, isPreview, isCache, isLooping, cacheDir, overrideExtension)
//        mediaSource = DefaultMediaSourceFactory(context!!).createMediaSource(MediaItem.fromUri(uri))
    }

    override fun setDataSource(fd: FileDescriptor?) {
        throw UnsupportedOperationException("no support")
    }

    override fun getDataSource(): String? {
        return dataSource
    }

    @Throws(IllegalStateException::class)
    override fun prepareAsync() {

        internalPlayer?.let {
            it.setPlaybackParameters(speedPlaybackParameters)
            if (surface != null) {
                it.setVideoSurface(surface)
                surface = null
            }
            mediaSource?.apply {
                it.setMediaSource(this)
            }
            it.prepare()
            it.playWhenReady = false
        }
    }

    @Throws(IllegalStateException::class)
    override fun start() {
        internalPlayer?.play()
    }

    @Throws(IllegalStateException::class)
    override fun stop() {
        resetFlags()
        internalPlayer?.clearMediaItems()
        internalPlayer?.stop()
    }

    private fun resetFlags() {
        isPreparing = true
        isBuffering = false
        lastReportedPlaybackState = Player.STATE_IDLE
        isLastReportedPlayWhenReady = false
    }

    @Throws(IllegalStateException::class)
    override fun pause() {
        internalPlayer?.pause()
    }

    @Deprecated("Deprecated in Java")
    override fun setWakeMode(context: Context?, mode: Int) {
        // FIXME: implement
    }

    override fun setScreenOnWhilePlaying(screenOn: Boolean) {
        // TODO: do nothing
    }

    override fun getTrackInfo(): Array<IjkTrackInfo?>? {
        // TODO: implement
        return null
    }

    override fun getVideoWidth(): Int {
        return videoWidth
    }

    override fun getVideoHeight(): Int {
        return videoHeight
    }

    override fun isPlaying(): Boolean {
        val state = internalPlayer?.playbackState ?: return false
        return when (state) {
            Player.STATE_BUFFERING, Player.STATE_READY -> internalPlayer!!.playWhenReady
            Player.STATE_IDLE, Player.STATE_ENDED -> false
            else -> false
        }
    }

    @Throws(IllegalStateException::class)
    override fun seekTo(msec: Long) {
        internalPlayer?.seekTo(msec)
    }

    override fun getCurrentPosition(): Long {
        return internalPlayer?.currentPosition ?: 0
    }

    override fun getDuration(): Long {
        return internalPlayer?.duration ?: 0
    }

    override fun getVideoSarNum(): Int {
        return 1
    }

    override fun getVideoSarDen(): Int {
        return 1
    }

    override fun reset() {
        internalPlayer?.release()
        internalPlayer = null

        // exoHelper?.release() 调用的是 ExoSourceManager 的 release 方法，会废弃掉全局唯一的 SimpleCache 实例
        // 导致其他地方使用 SimpleCache 时会出现异常，无法正常播放
//        exoHelper?.release()

        surface = null
        dataSource = null
        videoWidth = 0
        videoHeight = 0
    }

    override fun setLooping(looping: Boolean) {
        isLooping = looping
    }

    override fun isLooping(): Boolean {
        return isLooping
    }

    override fun setVolume(leftVolume: Float, rightVolume: Float) {
        internalPlayer?.volume = (leftVolume + rightVolume) / 2
    }

    override fun getAudioSessionId(): Int {
        return audioSessionId
    }

    override fun getMediaInfo(): MediaInfo? {
        return null
    }

    @Deprecated("Deprecated in Java")
    override fun setLogEnabled(enable: Boolean) {
        // do nothing
    }

    @Deprecated("Deprecated in Java", ReplaceWith("true"))
    override fun isPlayable(): Boolean {
        return true
    }

    override fun setAudioStreamType(streamtype: Int) {
        // do nothing
    }

    @Deprecated("Deprecated in Java")
    override fun setKeepInBackground(keepInBackground: Boolean) {
        // do nothing
    }

    override fun release() {
        if (internalPlayer != null) {
            reset()
            eventLogger = null
        }
    }

    /**
     * 设置seek 的临近帧。
     */
    fun setSeekParameter(seekParameters: SeekParameters?) {
        internalPlayer?.setSeekParameters(seekParameters)
    }

    fun getMediaSource(): MediaSource? {
        return mediaSource
    }

    fun setMediaSource(mediaSource: MediaSource?) {
        this.mediaSource = mediaSource
    }

    /**
     * 倍速播放
     *
     * @param speed 倍速播放，默认为1
     * @param pitch 音量缩放，默认为1，修改会导致声音变调
     */
    fun setSpeed(@Size(min = 0) speed: Float, @Size(min = 0) pitch: Float) {
        val playbackParameters = PlaybackParameters(speed, pitch)
        speedPlaybackParameters = playbackParameters
        internalPlayer?.setPlaybackParameters(playbackParameters)
    }

    fun getSpeed(): Float {
        return internalPlayer?.playbackParameters?.speed ?: 0F
    }

    val bufferedPercentage: Int get() = internalPlayer?.bufferedPercentage ?: 0


    fun getTrackSelector(): MappingTrackSelector? {
        return trackSelector
    }

    fun setTrackSelector(trackSelector: MappingTrackSelector?) {
        this.trackSelector = trackSelector
    }

    fun getLoadControl(): LoadControl? {
        return loadControl
    }

    fun setLoadControl(loadControl: LoadControl?) {
        this.loadControl = loadControl
    }

    fun getRendererFactory(): DefaultRenderersFactory? {
        return rendererFactory
    }

    fun setRendererFactory(rendererFactory: DefaultRenderersFactory?) {
        this.rendererFactory = rendererFactory
    }

    @Deprecated("Deprecated in Java")
    override fun onTimelineChanged(timeline: Timeline, manifest: Any?, reason: Int) {
    }

    @Deprecated("Deprecated in Java")
    override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
        //重新播放状态顺序为：STATE_IDLE -》STATE_BUFFERING -》STATE_READY
        //缓冲时顺序为：STATE_BUFFERING -》STATE_READY
//        Log.e("CustomIjkExo2Player",
//            "onPlayerStateChanged: playWhenReady = $playWhenReady, playbackState = $playbackState"
//        )
        if (isLastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) {
            var buffer = 0
            if (internalPlayer != null) {
                buffer = internalPlayer!!.bufferedPercentage
            }
            if (isBuffering) {
                when (playbackState) {
                    Player.STATE_ENDED, Player.STATE_READY -> {
                        notifyOnInfo(MEDIA_INFO_BUFFERING_END, buffer)
                        isBuffering = false
                    }

                    else -> {}
                }
            }
            if (isPreparing) {
                when (playbackState) {
                    Player.STATE_READY -> {
                        notifyOnPrepared()
                        isPreparing = false
                    }

                    else -> {}
                }
            }
            when (playbackState) {
                Player.STATE_BUFFERING -> {
                    notifyOnInfo(MEDIA_INFO_BUFFERING_START, buffer)
                    isBuffering = true
                }

                Player.STATE_READY -> {}
                Player.STATE_ENDED -> notifyOnCompletion()
                else -> {}
            }
        }
        isLastReportedPlayWhenReady = playWhenReady
        lastReportedPlaybackState = playbackState
    }

    override fun onRepeatModeChanged(repeatMode: Int) {}

    override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}

    override fun onPlayerError(error: ExoPlaybackException) {
        notifyOnError(MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNKNOWN)
    }

    override fun onPositionDiscontinuity(reason: Int) {}

    override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {}

    @Deprecated("Deprecated in Java")
    override fun onPlayerStateChanged(eventTime: EventTime, playWhenReady: Boolean, playbackState: Int) {
    }

    override fun onTimelineChanged(eventTime: EventTime, reason: Int) {}

    override fun onPositionDiscontinuity(eventTime: EventTime, reason: Int) {
        notifyOnInfo(ON_POSITION_DISCOUNTINUITY, reason)
    }

    override fun onSeekStarted(eventTime: EventTime) {}

    @Deprecated("Deprecated in Java")
    override fun onSeekProcessed(eventTime: EventTime) {
    }

    @Deprecated("Deprecated in Java", ReplaceWith("notifyOnSeekComplete()"))
    override fun onSeekProcessed() {
        notifyOnSeekComplete()
    }

    override fun onPlaybackParametersChanged(eventTime: EventTime, playbackParameters: PlaybackParameters) {}

    override fun onRepeatModeChanged(eventTime: EventTime, repeatMode: Int) {}

    override fun onShuffleModeChanged(eventTime: EventTime, shuffleModeEnabled: Boolean) {}

    @Deprecated("Deprecated in Java")
    override fun onLoadingChanged(eventTime: EventTime, isLoading: Boolean) {
    }

    @Deprecated("Deprecated in Java")
    override fun onLoadingChanged(isLoading: Boolean) {
    }

    override fun onPlayerError(eventTime: EventTime, error: ExoPlaybackException) {}

    override fun onTracksChanged(
        eventTime: EventTime,
        trackGroups: TrackGroupArray,
        trackSelections: TrackSelectionArray
    ) {
    }

    override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) {}

    override fun onBandwidthEstimate(
        eventTime: EventTime,
        totalLoadTimeMs: Int,
        totalBytesLoaded: Long,
        bitrateEstimate: Long
    ) {
    }

    override fun onMetadata(eventTime: EventTime, metadata: Metadata) {}

    @Deprecated("Deprecated in Java")
    override fun onDecoderEnabled(eventTime: EventTime, trackType: Int, decoderCounters: DecoderCounters) {
    }

    @Deprecated("Deprecated in Java")
    override fun onDecoderInitialized(
        eventTime: EventTime,
        trackType: Int,
        decoderName: String,
        initializationDurationMs: Long
    ) {
    }

    @Deprecated("Deprecated in Java")
    override fun onDecoderInputFormatChanged(eventTime: EventTime, trackType: Int, format: Format) {
    }

    @Deprecated("Deprecated in Java")
    override fun onDecoderDisabled(eventTime: EventTime, trackType: Int, decoderCounters: DecoderCounters) {
        audioSessionId = C.AUDIO_SESSION_ID_UNSET
    }

    override fun onAudioUnderrun(
        eventTime: EventTime,
        bufferSize: Int,
        bufferSizeMs: Long,
        elapsedSinceLastFeedMs: Long
    ) {
    }

    override fun onDroppedVideoFrames(eventTime: EventTime, droppedFrames: Int, elapsedMs: Long) {}

    override fun onVideoSizeChanged(
        eventTime: EventTime,
        width: Int,
        height: Int,
        unappliedRotationDegrees: Int,
        pixelWidthHeightRatio: Float
    ) {
        videoWidth = (width * pixelWidthHeightRatio).toInt()
        videoHeight = height
        notifyOnVideoSizeChanged((width * pixelWidthHeightRatio).toInt(), height, 1, 1)
        if (unappliedRotationDegrees > 0) notifyOnInfo(MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees)
    }

    override fun onRenderedFirstFrame(eventTime: EventTime, surface: Surface?) {}

    override fun onDrmKeysLoaded(eventTime: EventTime) {}

    override fun onDrmSessionManagerError(eventTime: EventTime, error: Exception) {}

    override fun onDrmKeysRestored(eventTime: EventTime) {}

    override fun onDrmKeysRemoved(eventTime: EventTime) {}

    companion object {

        const val ON_POSITION_DISCOUNTINUITY = 2702
    }
}