package com.hisharp.gstreamer_player

import android.content.Context
import android.graphics.SurfaceTexture
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.TextureView
import org.freedesktop.gstreamer.GStreamer
import java.io.Closeable
import java.io.IOException

class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback, TextureView.SurfaceTextureListener {

    private val mAppContext: Context
    private var gstCallback: GstCallback? = null
    private var rtspUrl: String? = null
    private var tag = ""
    private var isInit = false

    private var surface: Surface? = null

    private external fun nativeInit() // Initialize native code, build pipeline, etc
    private external fun nativeFinalize() // Destroy pipeline and shutdown native code
    private external fun nativeSetUri(uri: String) // Set the URI of the media to play
    private external fun nativeSetTag(tag: String) // Set the URI of the media to play
    private external fun nativePlay() // Set pipeline to PLAYING
    private external fun nativePause() // Set pipeline to PAUSED
    private external fun nativeSurfaceInit(surface: Any) // A new surface is available
    private external fun nativeSurfaceFinalize() // Surface about to be destroyed

    private val native_custom_data : Long = 0 // Native code will use this to keep private data

    fun play() {
        if (!isInit) return
        nativePlay()
    }

    fun pause() {
        if (!isInit) return
        nativePause()
    }

    fun setRtspUrl(rtspUrl: String?) {
        this.rtspUrl = rtspUrl
    }

    fun setTag(tag: String) {
        this.tag = tag
        nativeSetTag(tag)
    }

    fun releaseSurface() {
        if (!isInit) return
        isInit = false
        nativeSurfaceFinalize()
    }

    fun setOnStatusChangeListener(callback: GstCallback?) {
        gstCallback = callback
    }

    fun setSurfaceHolder(holder: SurfaceHolder) {
        holder.addCallback(this)
    }

    fun setTextureView(textureView: TextureView) {
        textureView.surfaceTextureListener = this
    }

    // Called from native code. This sets the content of the TextView from the UI thread.
    private fun setMessage(message: String) {
        if (gstCallback == null) return
        if (message.contains("State changed to PAUSED")) {
            gstCallback!!.onStatus(GstStatus.PAUSE)
        } else if (message.contains("State changed to PLAYING") || message.contains("Buffering complete")) {
            gstCallback!!.onStatus(GstStatus.PLAYING)
        } else if (message.contains("Could not open resource for reading and writing")) {
            gstCallback!!.onStatus(GstStatus.ERROR_WHEN_OPENING)
        } else if (message.contains("GStreamer encountered a general supporting library error")) {
            gstCallback!!.onStatus(GstStatus.ERROR)
        } else if (message.contains("Unhandled error")) {
            gstCallback!!.onStatus(GstStatus.ERROR)
        } else if (message.contains("Buffering")) {
            gstCallback!!.onStatus(GstStatus.BUFFERING)
        }
        gstCallback!!.onMessage(message)
    }

    // Called from native code. Native code calls this once it has created its pipeline and
    // the main loop is running, so it is ready to accept commands.
    private fun onGStreamerInitialized() {
        Log.i("$TAG+$tag", "GStreamer Initialization complete")
        if (gstCallback != null) {
            gstCallback!!.onStatus(GstStatus.READY)
        }

        // Restore previous playing state
        if (rtspUrl != null) {
            nativeSetUri(rtspUrl!!)
        }
        isInit = true
    }

    // Called from native code when the size of the media changes or is first detected.
    // Inform the video surface about the new size and recalculate the layout.
    private fun onMediaSizeChanged(width: Int, height: Int) {
        Log.i("$TAG+$tag", String.format("Media size changed to %d x %d", width, height))
        /*mainHandler.post(() -> {
            surfaceView.media_width = width;
            surfaceView.media_height = height;
            surfaceView.requestLayout();
        });*/
    }

    init {
        mAppContext = context.applicationContext

        // Initialize GStreamer and warn if it fails
        try {
            GStreamer.init(mAppContext)
        } catch (e: Exception) {
            Exception("Unable initialize gstreamer.")
        }
        nativeInit()
    }

    @Throws(IOException::class)
    override fun close() {
        isInit = false
        try {
            nativeFinalize()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        Log.d("$TAG+$tag", "Surface created: " + holder.surface)
        this.surface = holder.surface
        this.surface?.let { nativeSurfaceInit(it) }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        Log.d("$TAG+$tag", String.format("Surface changed, format: %d, width: %d, height: %d", format, width, height))
        /*setSurfaceHolder(holder);*/
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        Log.d("$TAG+$tag", "Surface destroyed")
        this.isInit = false
        this.surface = null
        //pause()
        //releaseSurface()
    }

    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
        this.surface = Surface(surface)
        this.surface?.let { nativeSurfaceInit(it) }
        Log.d("$TAG+$tag", "Surface onSurfaceTextureAvailable: $surface")
    }

    override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
        Log.d("$TAG+$tag", "Surface onSurfaceTextureSizeChanged: $surface")
    }

    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
        Log.d("$TAG+$tag", "Surface onSurfaceTextureDestroyed: $surface")
        this.isInit = false
        this.surface = null
        return isInit
    }

    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
        Log.d("$TAG+$tag", "Surface onSurfaceTextureUpdated: $surface")
    }

    companion object {
        private val TAG = GstLibrary::class.java.simpleName

        @JvmStatic
        private external fun nativeClassInit(): Boolean // Initialize native class: cache Method IDs for callbacks

        init {
            System.loadLibrary("gstreamer_android")
            System.loadLibrary("gst_player")
            nativeClassInit()
        }
    }
}