package com.daasuu.camerarecorder.egl;

import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.Handler;

import com.daasuu.camerarecorder.Resolution;
import com.daasuu.camerarecorder.capture.MediaVideoEncoder;
import com.daasuu.camerarecorder.egl.filter.GlFilter;

import javax.microedition.khronos.egl.EGLConfig;

import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_LINEAR;
import static android.opengl.GLES20.GL_MAX_TEXTURE_SIZE;
import static android.opengl.GLES20.GL_NEAREST;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.glClearColor;

/**
 * Created by sudamasayuki on 2018/03/14.
 */

public class GlPreviewRenderer extends GlFrameBufferObjectRenderer implements SurfaceTexture.OnFrameAvailableListener {

    private final Handler handler = new Handler();

    private GlSurfaceTexture previewTexture;

    // private final Camera camera;
    private int texName;

    private float[] MVPMatrix = new float[16];
    private float[] ProjMatrix = new float[16];
    private float[] MMatrix = new float[16];
    private float[] VMatrix = new float[16];
    private float[] STMatrix = new float[16];


    private final GLSurfaceView glView;

    private GLES20FramebufferObject filterFramebufferObject;
    private GlPreview previewShader;

    private GlFilter glFilter;
    private boolean isNewShader;

    private int angle = 0;
    private float aspectRatio = 1f;
    private float scaleRatio = 1f;
    private float drawScale = 1f;
    private float gestureScale = 1f;

    private Resolution cameraResolution;

    private int updateTexImageCounter = 0;
    private int updateTexImageCompare = 0;

    private SurfaceCreateListener surfaceCreateListener;
    private MediaVideoEncoder videoEncoder;


    public GlPreviewRenderer(GLSurfaceView glView) {
        this.glView = glView;
        this.glView.setEGLConfigChooser(new GLES20ConfigChooser(false));
        this.glView.setEGLContextFactory(new GLES20ContextFactory());
        this.glView.setRenderer(this);
        this.glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);


        Matrix.setIdentityM(STMatrix, 0);
    }

    public void onStartPreview(float cameraPreviewWidth, float cameraPreviewHeight, boolean isLandscapeDevice) {
        // 傾き、サイズ調整
        Matrix.setIdentityM(MMatrix, 0);
        Matrix.rotateM(MMatrix, 0, -angle, 0.0f, 0.0f, 1.0f);

//        Log.d("CameraRecorder ", "angle" + angle);
//        Log.d("CameraRecorder ", "getMeasuredHeight " + glView.getMeasuredHeight());
//        Log.d("CameraRecorder ", "getMeasuredWidth " + glView.getMeasuredWidth());
//        Log.d("CameraRecorder ", "cameraPreviewWidth " + cameraPreviewWidth);
//        Log.d("CameraRecorder ", "cameraPreviewHeight " + cameraPreviewHeight);


        if (isLandscapeDevice) {

            if (glView.getMeasuredWidth() == glView.getMeasuredHeight()) {

                float scale = Math.max(cameraPreviewWidth / cameraPreviewHeight,
                        cameraPreviewHeight / cameraPreviewWidth);
                Matrix.scaleM(MMatrix, 0, 1f * scale, 1f * scale, 1);

            } else {
                float scale = Math.max(
                        (float) glView.getMeasuredHeight() / cameraPreviewWidth,
                        (float) glView.getMeasuredWidth() / cameraPreviewHeight);
                Matrix.scaleM(MMatrix, 0, 1f * scale, 1f * scale, 1);
            }

        } else {
            // Portlate
            // View 1920 1080 Camera 1280 720 OK
            // View 1920 1080 Camera 800 600 OK
            // View 1440 1080 Camera 800 600 OK
            // View 1080 1080 Camera 1280 720 Need Scale
            // View 1080 1080 Camera 800 600 Need Scale


            float viewAspect = (float) glView.getMeasuredHeight() / glView.getMeasuredWidth();
            float cameraAspect = cameraPreviewWidth / cameraPreviewHeight;
            if (viewAspect >= cameraAspect) {
                Matrix.scaleM(MMatrix, 0, 1f, 1f, 1);
            } else {
                float adjust = cameraAspect / viewAspect;
                Matrix.scaleM(MMatrix, 0, 1f * adjust, 1f * adjust, 1);
            }
        }

    }

    public void setGlFilter(final GlFilter filter) {
        glView.queueEvent(new Runnable() {
            @Override
            public void run() {
                if (glFilter != null) {
                    glFilter.release();
                }
                glFilter = filter;
                isNewShader = true;
                glView.requestRender();
            }
        });
    }


    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        // increment every time a new frame is avail
        updateTexImageCounter++;
        glView.requestRender();
    }

    @Override
    public void onSurfaceCreated(EGLConfig config) {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        final int[] args = new int[1];

        GLES20.glGenTextures(args.length, args, 0);
        texName = args[0];

        // SurfaceTextureを生成
        previewTexture = new GlSurfaceTexture(texName);
        previewTexture.setOnFrameAvailableListener(this);

        GLES20.glBindTexture(previewTexture.getTextureTarget(), texName);
        // GL_TEXTURE_EXTERNAL_OES
        EglUtil.setupSampler(previewTexture.getTextureTarget(), GL_LINEAR, GL_NEAREST);
        GLES20.glBindTexture(GL_TEXTURE_2D, 0);

        filterFramebufferObject = new GLES20FramebufferObject();
        // GL_TEXTURE_EXTERNAL_OES
        previewShader = new GlPreview(previewTexture.getTextureTarget());
        previewShader.setup();


        Matrix.setLookAtM(VMatrix, 0,
                0.0f, 0.0f, 5.0f,
                0.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.0f
        );


        if (glFilter != null) {
            isNewShader = true;
        }

        GLES20.glGetIntegerv(GL_MAX_TEXTURE_SIZE, args, 0);

        handler.post(new Runnable() {
            @Override
            public void run() {
                if (surfaceCreateListener != null) {
                    // ここでカメラオープンを
                    surfaceCreateListener.onCreated(previewTexture.getSurfaceTexture());
                }
            }
        });
    }

    @Override
    public void onSurfaceChanged(int width, int height) {

        filterFramebufferObject.setup(width, height);
        previewShader.setFrameSize(width, height);
        if (glFilter != null) {
            glFilter.setFrameSize(width, height);
        }
        scaleRatio = (float) width / height;
        Matrix.frustumM(ProjMatrix, 0, -scaleRatio, scaleRatio, -1, 1, 5, 7);
    }

    @Override
    public void onDrawFrame(GLES20FramebufferObject fbo) {

        // ここのタイミングで以前指定したscaleを
        if (drawScale != gestureScale) {

            float tempScale = 1 / drawScale;
            Matrix.scaleM(MMatrix, 0, tempScale, tempScale, 1);
            drawScale = gestureScale;
            Matrix.scaleM(MMatrix, 0, drawScale, drawScale, 1);
        }

        synchronized (this) {
            if (updateTexImageCompare != updateTexImageCounter) {
                // loop and call updateTexImage() for each time the onFrameAvailable() method was called below.
                while (updateTexImageCompare != updateTexImageCounter) {

                    previewTexture.updateTexImage();
                    previewTexture.getTransformMatrix(STMatrix);
                    updateTexImageCompare++;  // increment the compare value until it's the same as _updateTexImageCounter
                }
            }

        }

        if (isNewShader) {
            if (glFilter != null) {
                glFilter.setup();
                glFilter.setFrameSize(fbo.getWidth(), fbo.getHeight());
            }
            isNewShader = false;
        }

        if (glFilter != null) {
            filterFramebufferObject.enable();
        }

        GLES20.glClear(GL_COLOR_BUFFER_BIT);

        Matrix.multiplyMM(MVPMatrix, 0, VMatrix, 0, MMatrix, 0);
        Matrix.multiplyMM(MVPMatrix, 0, ProjMatrix, 0, MVPMatrix, 0);

        previewShader.draw(texName, MVPMatrix, STMatrix, aspectRatio);


        if (glFilter != null) {
            fbo.enable();
            GLES20.glClear(GL_COLOR_BUFFER_BIT);
            glFilter.draw(filterFramebufferObject.getTexName(), fbo);
        }

        synchronized (this) {
            if (videoEncoder != null) {
                // notify to capturing thread that the camera frame is available.
                videoEncoder.frameAvailableSoon(texName, STMatrix, MVPMatrix, aspectRatio);
            }
        }

    }

    public void setCameraResolution(Resolution cameraResolution) {
        this.cameraResolution = cameraResolution;
    }

    public void setVideoEncoder(final MediaVideoEncoder encoder) {
        glView.queueEvent(new Runnable() {
            @Override
            public void run() {
                synchronized (GlPreviewRenderer.this) {
                    if (encoder != null) {
                        encoder.setEglContext(EGL14.eglGetCurrentContext(), texName);
                    }
                    videoEncoder = encoder;
                }
            }
        });

    }

    public GlSurfaceTexture getPreviewTexture() {
        return previewTexture;
    }

    public void setAngle(int angle) {
        this.angle = angle;
        if (angle == 90 || angle == 270) {
            aspectRatio = (float) cameraResolution.width() / cameraResolution.height();
        } else {
            aspectRatio = (float) cameraResolution.height() / cameraResolution.width();
        }
    }

    public void setGestureScale(float gestureScale) {
        this.gestureScale = gestureScale;
    }

    public GlFilter getFilter() {
        return glFilter;
    }

    public void release() {
        glView.queueEvent(new Runnable() {
            @Override
            public void run() {
                if (glFilter != null) {
                    glFilter.release();
                }
            }
        });
    }

    public interface SurfaceCreateListener {
        void onCreated(SurfaceTexture surface);
    }

    public void setSurfaceCreateListener(SurfaceCreateListener surfaceCreateListener) {
        this.surfaceCreateListener = surfaceCreateListener;
    }
}