package com.hmg.hmgDr.util.opentok

import android.content.Context
import android.content.res.Resources
import android.graphics.PixelFormat
import android.opengl.GLES20
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import android.view.View
import com.opentok.android.BaseVideoRenderer
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import java.nio.ShortBuffer
import java.util.concurrent.locks.ReentrantLock
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10


class ThumbnailCircleVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
    private val mView: GLSurfaceView = GLSurfaceView(mContext)
    private val mRenderer: MyRenderer

    interface ThumbnailCircleVideoRendererMetadataListener {
        fun onMetadataReady(metadata: ByteArray?)
    }

    fun setThumbnailCircleVideoRendererMetadataListener(metadataListener: ThumbnailCircleVideoRendererMetadataListener?) {
        mRenderer.metadataListener = metadataListener
    }

    internal class MyRenderer : GLSurfaceView.Renderer {
        var mTextureIds = IntArray(3)
        var mScaleMatrix = FloatArray(16)
        private val mVertexBuffer: FloatBuffer
        private val mTextureBuffer: FloatBuffer
        private val mDrawListBuffer: ShortBuffer
        var mVideoFitEnabled = true
        var mVideoDisabled = false
        private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw

        // vertices
        private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
attribute vec2 aTextureCoord;
varying vec2 vTextureCoord;
void main() {
  gl_Position = uMVPMatrix * aPosition;
  vTextureCoord = aTextureCoord;
}
"""
        private val fragmentShaderCode = """precision mediump float;
uniform sampler2D Ytex;
uniform sampler2D Utex,Vtex;
uniform vec2 radiusDp;
varying vec2 vTextureCoord;
void main(void) {
  float nx,ny,r,g,b,y,u,v;
  mediump vec4 txl,ux,vx;  nx=vTextureCoord[0];
  ny=vTextureCoord[1];
  y=texture2D(Ytex,vec2(nx,ny)).r;
  u=texture2D(Utex,vec2(nx,ny)).r;
  v=texture2D(Vtex,vec2(nx,ny)).r;
  y=1.1643*(y-0.0625);
  u=u-0.5;
  v=v-0.5;
  r=y+1.5958*v;
  g=y-0.39173*u-0.81290*v;
  b=y+2.017*u;
  float radius = 0.5;
  vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
  vec4 color1 = vec4(r, g, b, 1.0);
  vec2 st = (gl_FragCoord.xy/radiusDp.xy);  float dist = radius - distance(st,vec2(0.5));
  float t = 1.0;
  if (dist < 0.0) t = 0.0;
  gl_FragColor = mix(color0, color1, t);
}
"""
        var mFrameLock = ReentrantLock()
        var mCurrentFrame: Frame? = null
        private var mProgram = 0
        private var mTextureWidth = 0
        private var mTextureHeight = 0
        private var mViewportWidth = 0
        private var mViewportHeight = 0
        override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
            gl.glClearColor(0f, 0f, 0f, 1f)
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
            val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                    vertexShaderCode)
            val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
                    fragmentShaderCode)
            mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
            // Program
            GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
            // shader to program
            GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
            // shader to
            // program
            GLES20.glLinkProgram(mProgram)
            val positionHandle = GLES20.glGetAttribLocation(mProgram,
                    "aPosition")
            val textureHandle = GLES20.glGetAttribLocation(mProgram,
                    "aTextureCoord")
            GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                    GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
                    mVertexBuffer)
            GLES20.glEnableVertexAttribArray(positionHandle)
            GLES20.glVertexAttribPointer(textureHandle,
                    TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
                    TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
            GLES20.glEnableVertexAttribArray(textureHandle)
            GLES20.glUseProgram(mProgram)
            var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
            GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
            i = GLES20.glGetUniformLocation(mProgram, "Utex")
            GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
            i = GLES20.glGetUniformLocation(mProgram, "Vtex")
            GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
            val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
            val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
            GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
            mTextureWidth = 0
            mTextureHeight = 0
        }

        fun setupTextures(frame: Frame) {
            if (mTextureIds[0] != 0) {
                GLES20.glDeleteTextures(3, mTextureIds, 0)
            }
            GLES20.glGenTextures(3, mTextureIds, 0)
            val w = frame.width
            val h = frame.height
            val hw = w + 1 shr 1
            val hh = h + 1 shr 1
            initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
            initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
            initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
            mTextureWidth = frame.width
            mTextureHeight = frame.height
        }

        fun updateTextures(frame: Frame) {
            val width = frame.width
            val height = frame.height
            val half_width = width + 1 shr 1
            val half_height = height + 1 shr 1
            val y_size = width * height
            val uv_size = half_width * half_height
            val bb = frame.buffer
            // If we are reusing this frame, make sure we reset position and
            // limit
            bb.clear()
            if (bb.remaining() == y_size + uv_size * 2) {
                bb.position(0)
                GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
                GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
                GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
                GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
                        height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
                        bb)
                bb.position(y_size)
                GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
                GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
                        half_width, half_height, GLES20.GL_LUMINANCE,
                        GLES20.GL_UNSIGNED_BYTE, bb)
                bb.position(y_size + uv_size)
                GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
                GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
                        half_width, half_height, GLES20.GL_LUMINANCE,
                        GLES20.GL_UNSIGNED_BYTE, bb)
            } else {
                mTextureWidth = 0
                mTextureHeight = 0
            }
        }

        override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
            GLES20.glViewport(0, 0, width, height)
            mViewportWidth = width
            mViewportHeight = height
        }

        var metadataListener: ThumbnailCircleVideoRendererMetadataListener? = null
        override fun onDrawFrame(gl: GL10) {
            gl.glClearColor(0f, 0f, 0f, 0f)
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
            mFrameLock.lock()
            if (mCurrentFrame != null && !mVideoDisabled) {
                GLES20.glUseProgram(mProgram)
                if (mTextureWidth != mCurrentFrame!!.width
                        || mTextureHeight != mCurrentFrame!!.height) {
                    setupTextures(mCurrentFrame!!)
                }
                updateTextures(mCurrentFrame!!)
                Matrix.setIdentityM(mScaleMatrix, 0)
                var scaleX = 1.0f
                var scaleY = 1.0f
                val ratio = (mCurrentFrame!!.width.toFloat()
                        / mCurrentFrame!!.height)
                val vratio = mViewportWidth.toFloat() / mViewportHeight
                if (mVideoFitEnabled) {
                    if (ratio > vratio) {
                        scaleY = vratio / ratio
                    } else {
                        scaleX = ratio / vratio
                    }
                } else {
                    if (ratio < vratio) {
                        scaleY = vratio / ratio
                    } else {
                        scaleX = ratio / vratio
                    }
                }
                Matrix.scaleM(mScaleMatrix, 0,
                        scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
                        scaleY, 1f)
                metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
                val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
                        "uMVPMatrix")
                GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
                        mScaleMatrix, 0)
                GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
                        GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
            } else {
                //black frame when video is disabled
                gl.glClearColor(0f, 0f, 0f, 1f)
                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
            }
            mFrameLock.unlock()
        }

        fun displayFrame(frame: Frame?) {
            mFrameLock.lock()
            if (mCurrentFrame != null) {
                mCurrentFrame!!.recycle()
            }
            mCurrentFrame = frame
            mFrameLock.unlock()
        }

        fun disableVideo(b: Boolean) {
            mFrameLock.lock()
            mVideoDisabled = b
            if (mVideoDisabled) {
                if (mCurrentFrame != null) {
                    mCurrentFrame!!.recycle()
                }
                mCurrentFrame = null
            }
            mFrameLock.unlock()
        }

        fun enableVideoFit(enableVideoFit: Boolean) {
            mVideoFitEnabled = enableVideoFit
        }

        companion object {
            // number of coordinates per vertex in this array
            const val COORDS_PER_VERTEX = 3
            const val TEXTURECOORDS_PER_VERTEX = 2
            var mXYZCoords = floatArrayOf(
                    -1.0f, 1.0f, 0.0f,  // top left
                    -1.0f, -1.0f, 0.0f,  // bottom left
                    1.0f, -1.0f, 0.0f,  // bottom right
                    1.0f, 1.0f, 0.0f // top right
            )
            var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
            fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
                GLES20.glActiveTexture(name)
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
                GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
                GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
                GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
                GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
                GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
                        width, height, 0, GLES20.GL_LUMINANCE,
                        GLES20.GL_UNSIGNED_BYTE, null)
            }

            fun loadShader(type: Int, shaderCode: String?): Int {
                val shader = GLES20.glCreateShader(type)
                GLES20.glShaderSource(shader, shaderCode)
                GLES20.glCompileShader(shader)
                return shader
            }
        }

        init {
            val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
            bb.order(ByteOrder.nativeOrder())
            mVertexBuffer = bb.asFloatBuffer()
            mVertexBuffer.put(mXYZCoords)
            mVertexBuffer.position(0)
            val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
            tb.order(ByteOrder.nativeOrder())
            mTextureBuffer = tb.asFloatBuffer()
            mTextureBuffer.put(mUVCoords)
            mTextureBuffer.position(0)
            val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
            dlb.order(ByteOrder.nativeOrder())
            mDrawListBuffer = dlb.asShortBuffer()
            mDrawListBuffer.put(mVertexIndex)
            mDrawListBuffer.position(0)
        }
    }

    override fun onFrame(frame: Frame) {
        mRenderer.displayFrame(frame)
        mView.requestRender()
    }

    override fun setStyle(key: String, value: String) {
        if (STYLE_VIDEO_SCALE == key) {
            if (STYLE_VIDEO_FIT == value) {
                mRenderer.enableVideoFit(true)
            } else if (STYLE_VIDEO_FILL == value) {
                mRenderer.enableVideoFit(false)
            }
        }
    }

    override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
        mRenderer.disableVideo(!videoEnabled)
    }

    override fun getView(): View {
        return mView
    }

    override fun onPause() {
        mView.onPause()
    }

    override fun onResume() {
        mView.onResume()
    }

    companion object {
        private const val THUMBNAIL_SIZE = 90 //in dp
    }

    init {
        mView.setEGLContextClientVersion(2)
        mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
        mView.holder.setFormat(PixelFormat.TRANSLUCENT)
        mRenderer = MyRenderer()
        mView.setRenderer(mRenderer)
        mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
    }
}
