package com.erlei.videorecorder.recorder;


import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.os.Environment;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;

import com.erlei.videorecorder.camera.Camera;
import com.erlei.videorecorder.camera.Size;
import com.erlei.videorecorder.encoder1.MediaAudioEncoder;
import com.erlei.videorecorder.encoder1.MediaMuxerWrapper;
import com.erlei.videorecorder.encoder1.MediaVideoEncoder;
import com.erlei.videorecorder.gles.EglCore;
import com.erlei.videorecorder.gles.EglSurfaceBase;
import com.erlei.videorecorder.gles.GLUtil;
import com.erlei.videorecorder.gles.WindowSurface;
import com.erlei.videorecorder.util.LogUtil;
import com.erlei.videorecorder.util.SaveFrameTask;

import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VideoRecorder implements RenderThread.RenderCallBack, IVideoRecorder {

    private static final String TAG = LogUtil.TAG;
    private final Object mSync = new Object();
    private final Config mConfig;
    private File mOutputFile;
    private ExecutorService mThreadExecutor;
    private RenderThread mRenderThread;
    private volatile boolean mRecordEnabled, mMuxerRunning, mRequestStart, mRequestStop, mPreviewState;
    private volatile WindowSurface mInputWindowSurface;
    private volatile MediaVideoEncoder mVideoEncoder;
    private volatile MediaMuxerWrapper mMuxer;
    private ByteBuffer mByteBuffer;
    private Size mSize;
    private volatile boolean mTakePicture;
    private TakePictureCallback mPictureCallback;

    private VideoRecorder(Config p) {
        mConfig = p;
        mOutputFile = getOutPut();
    }

    public Config getConfig() {
        return mConfig;
    }

    @Override
    public synchronized void startPreview() {
        if (mPreviewState) return;
        mThreadExecutor = Executors.newSingleThreadExecutor();
        mRenderThread = new RenderThread(mConfig);
        mRenderThread.setCallBack(this);
        mRenderThread.start();
        mPreviewState = true;
    }

    @Override
    public synchronized void startRecord() {
        setRecordEnabled(true);
    }

    @Override
    public synchronized void stopRecord() {
        setRecordEnabled(false);
    }

    @Override
    public CameraController getCameraController() {
        return mConfig.cameraController;
    }

    @Override
    public boolean isRecordEnable() {
        return mRecordEnabled;
    }

    /**
     * @return 混合器是否正在运行
     */
    @Override
    public boolean isMuxerRunning() {
        return mMuxerRunning;
    }

    @Override
    public void onSizeChanged(int width, int height) {
        mRenderThread.getHandler().onSizeChanged(width, height);

    }

    public SurfaceTexture getPreviewTexture() {
        return mRenderThread.getSurfaceTexture();
    }

    /**
     * 拍照
     */
    @Override
    public synchronized void takePicture(TakePictureCallback callback) {
        mTakePicture = true;
        mSize = mConfig.getCameraPreview().getSurfaceSize();
        mByteBuffer = ByteBuffer.allocateDirect(mSize.getHeight() * mSize.getWidth() * 4).order(ByteOrder.nativeOrder());
        mPictureCallback = callback;
    }

    public synchronized void setRecordEnabled(boolean enable) {
        if (isRecordEnable() == enable) {
            LogUtil.loge(TAG, "setRecordEnabled:mRecordEnabled == enable");
            return;
        }
        if (!mPreviewState) {
            LogUtil.loge(TAG, "setRecordEnabled:mPreviewState == true");
            return;
        }
        if (enable) {
            startEncoder();
        } else {
            stopEncoder();
        }
    }

    private synchronized void startEncoder() {
        mRecordEnabled = true;
        mRequestStart = true;
        LogUtil.loge(TAG, "startEncoder:begin");
        mOutputFile = getOutPut();
        mThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (!mPreviewState) return;
                LogUtil.loge(TAG, "startEncoder:begin");
                synchronized (mSync) {
                    try {
                        mMuxer = new MediaMuxerWrapper(mOutputFile.getAbsolutePath(), mConfig.viewHandler);
                        mVideoEncoder = new MediaVideoEncoder(mMuxer, mConfig);
                        new MediaAudioEncoder(mMuxer, mConfig);
                        mMuxer.prepare();
                        mMuxer.startRecording();

                        mInputWindowSurface = new WindowSurface(mRenderThread.getEglCore(), mVideoEncoder.getSurface(), true);
                    } catch (Exception e) {
                        e.printStackTrace();
                        LogUtil.loge(TAG, "startEncoder:" + e);
                    }
                    mMuxerRunning = true;
                    mRequestStart = false;
                }
                if (mConfig.viewHandler != null) {
                    mConfig.viewHandler.onCaptureStarted(mOutputFile.getAbsolutePath());
                }
            }
        });
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    @NonNull
    private File getOutPut() {
        if (mConfig.mOutputFile != null) {
            return mConfig.mOutputFile;
        } else {
            File path = new File(mConfig.outputPath);
            if (!path.exists()) {
                path.mkdirs();
            }
            if (path.isFile()) path = path.getParentFile();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.getDefault());
            return new File(path, format.format(new Date()) + ".mp4");
        }
    }

    private synchronized void stopEncoder() {
        mRecordEnabled = false;
        mRequestStop = true;
        LogUtil.loge(TAG, "stopEncoder:begin");
        mThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (!mPreviewState && !mMuxerRunning) return;
                synchronized (mSync) {
                    LogUtil.loge(TAG, "stopEncoder:begin");
                    mMuxerRunning = false;
                    try {
                        if (mMuxer != null) {
                            mMuxer.stopRecording();
                            mMuxer = null;
                        }
                        if (mInputWindowSurface != null) {
                            mInputWindowSurface.release();
                            mInputWindowSurface = null;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        LogUtil.loge(TAG, "stopEncoder:" + e);
                    }
                    mRequestStop = false;
                }
                if (mConfig.viewHandler != null) {
                    mConfig.viewHandler.onCaptureStopped(mOutputFile.getAbsolutePath());
                }
            }
        });
    }


    @Override
    public synchronized void stopPreview() {
        if (!mPreviewState) return;
        mPreviewState = false;
        mThreadExecutor.shutdownNow();
        mThreadExecutor = null;
        mMuxerRunning = false;
        mRecordEnabled = false;
        mRenderThread.getHandler().destroy();

        mConfig.cameraController.closeCamera();
    }

    @Override
    public void release() {

    }

    @Override
    public File getOutputFile() {
        return mOutputFile;
    }

    @Override
    public void onPrepared(EglCore eglCore) {
        mConfig.cameraController.openCamera(getPreviewTexture());
        LogUtil.logd(TAG, "GL_VERSION " + GLUtil.GL_VERSION + (GLUtil.GL_VERSION < 3 ? "draw twice" : "glBlitFramebuffer"));
    }

    /**
     * 渲染一帧
     *
     * @param renderer
     * @param windowSurface
     * @return swapBuffers
     */
    @Override
    public synchronized boolean onDrawFrame(CameraGLRenderer renderer, EglSurfaceBase windowSurface) {
        boolean swapBuffers;
        long startTime = System.currentTimeMillis();
        //使用mSync同步锁将导致录制开始的时候卡顿一下
//        && !mRequestStart && !mRequestStop
        if (mInputWindowSurface != null && mVideoEncoder != null && mRecordEnabled && mMuxerRunning && mPreviewState) {
            if (GLUtil.GL_VERSION >= 3) {

                windowSurface.makeCurrent();
                renderer.onDrawFrame();
                mInputWindowSurface.makeCurrentReadFrom(windowSurface);
                mVideoEncoder.frameAvailableSoon();
                GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
                GLUtil.checkGlError("before glBlitFramebuffer");
                GLES30.glBlitFramebuffer(
                        0, 0, windowSurface.getWidth(), windowSurface.getHeight(),
                        0, 0, windowSurface.getWidth(), windowSurface.getHeight(),
                        GLES30.GL_COLOR_BUFFER_BIT, GLES30.GL_NEAREST);

                int err;
                if ((err = GLES30.glGetError()) != GLES30.GL_NO_ERROR) {
                    LogUtil.logw("ERROR: glBlitFramebuffer failed: 0x" + Integer.toHexString(err));
                }
                mInputWindowSurface.swapBuffers();
                windowSurface.makeCurrent();
                swapBuffers = windowSurface.swapBuffers();
            } else {
                windowSurface.makeCurrent();
                renderer.onDrawFrame();
                swapBuffers = windowSurface.swapBuffers();

                mInputWindowSurface.makeCurrent();
                mVideoEncoder.frameAvailableSoon();
                renderer.onDrawFrame();
                mInputWindowSurface.swapBuffers();
            }
        } else {
            windowSurface.makeCurrent();
            renderer.onDrawFrame();
            swapBuffers = windowSurface.swapBuffers();
        }
        LogUtil.logv("onDrawFrame ----> " + (System.currentTimeMillis() - startTime) + "ms");

        if (mByteBuffer != null && mTakePicture) {
            mByteBuffer.rewind();
            GLES20.glReadPixels(0, 0, mSize.getWidth(), mSize.getHeight(), GLES30.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mByteBuffer);
            new SaveFrameTask(mSize.getWidth(), mSize.getHeight(), mPictureCallback).execute(mByteBuffer);
            mTakePicture = false;
            mPictureCallback = null;
            mByteBuffer = null;
        }
        return swapBuffers;
    }


    @Override
    public void onStopped() {

    }

    public static class Builder {

        private final Config mP;

        public Builder(ICameraPreview cameraPreview) {
            mP = new Config(cameraPreview.getContext(), cameraPreview);
        }

        public Config getConfig() {
            return mP;
        }

        public Builder setCameraController(CameraController controller) {
            mP.cameraController = controller;
            return this;
        }

        /**
         * 设置期望的帧率
         * 默认为25
         */
        public Builder setFrameRate(int frameRate) {
            mP.frameRate = frameRate;
            return this;
        }

        /**
         * 设置声道数
         */
        public Builder setChannelCount(@IntRange(from = 1, to = 2) int channelCount) {
            mP.audioChannelCount = channelCount;
            return this;
        }

        /**
         * 设置音频采样率
         * 默认为 44100
         */
        public Builder setAudioSampleRate(int sampleRate) {
            mP.audioSampleRate = sampleRate;
            return this;
        }

        /**
         * @param bitRate 设置视频比特率
         *                默认为 width * height *  3 * 4
         */
        public Builder setVideoBitRate(int bitRate) {
            mP.videoBitRate = bitRate;
            return this;
        }

        /**
         * @param bitRate 设置音频比特率
         *                默认为 64000
         */
        public Builder setAudioBitRate(int bitRate) {
            mP.audioBitRate = bitRate;
            return this;
        }

        /**
         * 设置关键帧间隔
         */
        public Builder setIFrameInterval(int interval) {
            mP.iFrameInterval = interval;
            return this;
        }

        /**
         * @param listener 纹理绘制监听
         */
        public Builder setDrawTextureListener(OnDrawTextureListener listener) {
            mP.mDrawTextureListener = listener;
            return this;
        }

        /**
         * @param file 设置输出文件 , 无论一个 VideoRecorder实例开启几次录制 , 之后最后一次的录制文件会保存
         */
        public Builder setOutPutFile(File file) {
            mP.mOutputFile = file;
            return this;
        }

        /**
         * @param outputPath 输出文件夹 , 只有沒 setOutPutFile ,这个属性才会起作用, 每一次startRecord都会生成一个新的文件
         */
        public Builder setOutPutPath(String outputPath) {
            mP.outputPath = outputPath;
            return this;
        }

        /**
         * @param enable 是否启用FPS日志输出
         */
        public Builder setLogFPSEnable(boolean enable) {
            mP.logFPS = enable;
            return this;
        }

        public VideoRecorder build() {
            if (mP.context == null)
                throw new IllegalArgumentException("context cannot be null");

            if (mP.cameraController == null) {
                if (mP.cameraPreview != null) {
                    mP.cameraController = new DefaultCameraController(mP.cameraPreview);
                }
            }

            if (mP.cameraController == null) {
                throw new IllegalArgumentException("TextureView or SurfaceView cannot be null");
            } else {
                mP.cameraController.setCameraBuilder(mP.cameraBuilder);
            }
            if (mP.mOutputFile == null && mP.outputPath == null) {
                File filesDir = mP.context.getExternalFilesDir(Environment.DIRECTORY_MOVIES);
                if (filesDir == null) filesDir = mP.context.getFilesDir();
                mP.outputPath = filesDir.getPath();
            }
            return new VideoRecorder(mP.clone());
        }

        public Builder setCallbackHandler(VideoRecorderHandler viewHandler) {
            mP.viewHandler = viewHandler;
            return this;
        }


        public Builder setCameraBuilder(Camera.CameraBuilder cameraBuilder) {
            mP.cameraBuilder = cameraBuilder;
            return this;
        }
    }

    public static class Config implements Cloneable {
        ICameraPreview cameraPreview;
        CameraController cameraController;
        OnDrawTextureListener mDrawTextureListener;
        Context context;
        VideoRecorderHandler viewHandler;
        boolean logFPS;
        File mOutputFile;
        int audioBitRate = 64000;
        int iFrameInterval = 5;
        int frameRate = 25;
        int audioSampleRate = 44100;
        int audioChannelCount = 1;
        int videoBitRate;
        String outputPath;
        Camera.CameraBuilder cameraBuilder;

        Config(Context context, ICameraPreview cameraPreview) {
            this.context = context;
            this.cameraPreview = cameraPreview;
        }

        public CameraController getCameraController() {
            return cameraController;
        }

        public Context getContext() {
            return context;
        }

        public VideoRecorderHandler getViewHandler() {
            return viewHandler;
        }

        public boolean isLogFPS() {
            return logFPS;
        }

        public File getOutputFile() {
            return mOutputFile;
        }

        public int getAudioBitRate() {
            return audioBitRate;
        }

        public int getIFrameInterval() {
            return iFrameInterval;
        }

        public int getFrameRate() {
            return frameRate;
        }

        public int getAudioSampleRate() {
            return audioSampleRate;
        }

        public int getAudioChannelCount() {
            return audioChannelCount;
        }

        public int getVideoBitRate() {
            return videoBitRate;
        }

        public String getOutputPath() {
            return outputPath;
        }

        public ICameraPreview getCameraPreview() {
            return cameraPreview;
        }

        public void setCameraPreview(ICameraPreview cameraPreview) {
            this.cameraPreview = cameraPreview;
        }

        public void setCameraController(CameraController cameraController) {
            this.cameraController = cameraController;
        }

        public void setContext(Context context) {
            this.context = context;
        }

        public void setViewHandler(VideoRecorderHandler viewHandler) {
            this.viewHandler = viewHandler;
        }

        public void setLogFPS(boolean logFPS) {
            this.logFPS = logFPS;
        }

        public void setOutputFile(File outputFile) {
            mOutputFile = outputFile;
        }

        public void setAudioBitRate(int audioBitRate) {
            this.audioBitRate = audioBitRate;
        }

        public void setIFrameInterval(int iFrameInterval) {
            this.iFrameInterval = iFrameInterval;
        }

        public void setFrameRate(int frameRate) {
            this.frameRate = frameRate;
        }

        public void setAudioSampleRate(int audioSampleRate) {
            this.audioSampleRate = audioSampleRate;
        }

        public void setAudioChannelCount(int audioChannelCount) {
            this.audioChannelCount = audioChannelCount;
        }

        public void setVideoBitRate(int videoBitRate) {
            this.videoBitRate = videoBitRate;
        }

        public void setOutputPath(String outputPath) {
            this.outputPath = outputPath;
        }

        public Camera.CameraBuilder getCameraBuilder() {
            return cameraBuilder;
        }

        public void setCameraBuilder(Camera.CameraBuilder cameraBuilder) {
            this.cameraBuilder = cameraBuilder;
        }

        public void setDrawTextureListener(OnDrawTextureListener drawTextureListener) {
            this.mDrawTextureListener = drawTextureListener;
        }

        @Override
        public Config clone() {
            try {
                return (Config) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }


}