package com.joe.camera2recorddemo.OpenGL;

import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.Semaphore;


/**
 * MP4处理工具，暂时只用于处理图像。
 * 4.4的手机不支持video/mp4v-es格式的视频流，MediaMuxer混合无法stop，5.0以上可以
 */

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class Mp4Processor {

	private final int TIME_OUT = 1000;

	private String mInputPath;                  //输入路径
	private String mOutputPath;                 //输出路径

	private MediaCodec mVideoDecoder;           //视频解码器
	private MediaCodec mVideoEncoder;           //视频编码器
	//private MediaCodec mAudioDecoder;           //音频解码器
	//private MediaCodec mAudioEncoder;           //音频编码器
	private MediaExtractor mExtractor;          //音视频分离器
	private MediaMuxer mMuxer;                  //音视频混合器
	private EGLHelper mEGLHelper;               //GL环境创建的帮助类
	private MediaCodec.BufferInfo mVideoDecoderBufferInfo;  //用于存储当前帧的视频解码信息
	//private MediaCodec.BufferInfo mAudioDecoderBufferInfo;  //用于存储当前帧的音频解码信息
	private MediaCodec.BufferInfo mVideoEncoderBufferInfo;  //用于存储当前帧的视频编码信息
	private MediaCodec.BufferInfo mAudioEncoderBufferInfo;  //用于纯粹当前帧的音频编码信息

	private int mAudioEncoderTrack = -1;     //解码音轨
	private int mVideoEncoderTrack = -1;     //解码视轨
	private int mAudioDecoderTrack = -1;     //编码音轨
	private int mVideoDecoderTrack = -1;     //编码视轨

	//private String mAudioMime;
	//private String mVideoMime;

	private int mInputVideoWidth = 0;     //输入视频的宽度
	private int mInputVideoHeight = 0;    //输入视频的高度

	private int mOutputVideoWidth = 0;    //输出视频的宽度
	private int mOutputVideoHeight = 0;   //输出视频的高度
	private int mVideoTextureId;        //原始视频图像的纹理
	private SurfaceTexture mVideoSurfaceTexture;    //用于接收原始视频的解码的图像流

	private boolean isRenderToWindowSurface;        //是否渲染到用户设置的WindowBuffer上，用于测试
	private Surface mOutputSurface;                 //视频输出的Surface

	private Thread mDecodeThread;
	private Thread mGLThread;
	private boolean mCodecFlag = false;
	private boolean isVideoExtractorEnd = false;
	private boolean isAudioExtractorEnd = false;
	private boolean isStarted = false;
	private WrapRenderer mRenderer;
	private boolean mGLThreadFlag = false;
	private Semaphore mSem;
	private Semaphore mDecodeSem;

	private final Object Extractor_LOCK = new Object();
	private final Object MUX_LOCK = new Object();
	private final Object PROCESS_LOCK = new Object();

	private OnProgressListener mProgressListener;

	private boolean isUserWantToStop = false;
	private long mVideoStopTimeStamp = 0;     //视频停止时的时间戳，用于外部主动停止处理时，音频截取

	private long mTotalVideoTime = 0;     //视频的总时长

	private int filterRotation = 0;//滤镜的旋转角度

	public Mp4Processor() {
		mEGLHelper = new EGLHelper();
		mVideoDecoderBufferInfo = new MediaCodec.BufferInfo();
		//mAudioDecoderBufferInfo=new MediaCodec.BufferInfo();
		mVideoEncoderBufferInfo = new MediaCodec.BufferInfo();
		mAudioEncoderBufferInfo = new MediaCodec.BufferInfo();
	}


	/**
	 * 设置用于处理的MP4文件
	 *
	 * @param path 文件路径
	 */
	public void setInputPath(String path) {
		this.mInputPath = path;
	}

	/**
	 * 设置处理后的mp4存储的位置
	 *
	 * @param path 文件路径
	 */
	public void setOutputPath(String path) {
		this.mOutputPath = path;
	}

	/**
	 * 设置滤镜的旋转角度
	 *
	 * @param rotation
	 */
	public void setFilterRotation(int rotation) {
		this.filterRotation = rotation;
	}

	/**
	 * 设置直接渲染到指定的Surface上,测试用
	 *
	 * @param surface 渲染的位置
	 */
	public void setOutputSurface(Surface surface) {
		this.mOutputSurface = surface;
		this.isRenderToWindowSurface = surface != null;
	}

	/**
	 * 设置用户处理接口
	 *
	 * @param renderer 处理接口
	 */
	public void setRenderer(Renderer renderer) {
		mRenderer = new WrapRenderer(renderer);
	}

	public int getVideoSurfaceTextureId() {
		return mVideoTextureId;
	}

	public SurfaceTexture getVideoSurfaceTexture() {
		return mVideoSurfaceTexture;
	}

	/**
	 * 设置输出Mp4的图像大小，默认为输出大小
	 *
	 * @param width  视频图像宽度
	 * @param height 视频图像高度
	 */
	public void setOutputSize(int width, int height) {
		this.mOutputVideoWidth = width;
		this.mOutputVideoHeight = height;
	}

	public void setOnCompleteListener(OnProgressListener listener) {
		this.mProgressListener = listener;
	}

	private boolean prepare() throws IOException {
		//todo 获取视频旋转信息，并做出相应处理
		synchronized (PROCESS_LOCK) {
			int videoRotation = 0;
			MediaMetadataRetriever mMetRet = new MediaMetadataRetriever();
			mMetRet.setDataSource(mInputPath);
			mExtractor = new MediaExtractor();
			mExtractor.setDataSource(mInputPath);
			int count = mExtractor.getTrackCount();
			//解析Mp4
			for (int i = 0; i < count; i++) {
				MediaFormat format = mExtractor.getTrackFormat(i);
				String mime = format.getString(MediaFormat.KEY_MIME);
				Log.d("Mp4Processor", "extractor format-->" + mExtractor.getTrackFormat(i));
				if (mime.startsWith("audio")) {
					mAudioDecoderTrack = i;
					//todo 暂时不对音频处理，后续需要对音频处理时再修改这个
					/*mAudioDecoder=MediaCodec.createDecoderByType(mime);
					mAudioDecoder.configure(format,null,null,0);
                    if(!isRenderToWindowSurface){
                        Log.e("wuwang", format.toString());
                        MediaFormat audioFormat=MediaFormat.createAudioFormat(mime,
                                format.getInteger(MediaFormat.KEY_SAMPLE_RATE),
                                format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
                        audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
                                format.getInteger(MediaFormat.KEY_AAC_PROFILE));
                        audioFormat.setInteger(MediaFormat.KEY_BIT_RATE,
                                Integer.valueOf(mMetRet.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)));
                        mAudioEncoder=MediaCodec.createEncoderByType(mime);
                        mAudioEncoder.configure(audioFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
                    }*/
				} else if (mime.startsWith("video")) {
					//5.0以下，不能解析mp4v-es //todo 5.0以上也可能存在问题，目前还不知道原因
//                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP&&mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
//                        return false;
//                    }
					mVideoDecoderTrack = i;
					mTotalVideoTime = Long.valueOf(mMetRet.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
					String rotation = mMetRet.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
					if (rotation != null) {
						videoRotation = Integer.valueOf(rotation);
					}
					if (videoRotation == 90 || videoRotation == 270) {
						mInputVideoHeight = format.getInteger(MediaFormat.KEY_WIDTH);
						mInputVideoWidth = format.getInteger(MediaFormat.KEY_HEIGHT);
					} else {
						mInputVideoWidth = format.getInteger(MediaFormat.KEY_WIDTH);
						mInputVideoHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
					}
					Log.e("Mp4Processor", "createDecoder");
					mVideoDecoder = MediaCodec.createDecoderByType(mime);
					Log.e("Mp4Processor", "createDecoder end");
					mVideoTextureId = mEGLHelper.createTextureID();
					mVideoSurfaceTexture = new SurfaceTexture(mVideoTextureId);
					mVideoSurfaceTexture.setOnFrameAvailableListener(mFrameAvaListener);
					mVideoDecoder.configure(format, new Surface(mVideoSurfaceTexture), null, 0);
					if (!isRenderToWindowSurface) {
						if (mOutputVideoWidth == 0 || mOutputVideoHeight == 0) {
							mOutputVideoWidth = mInputVideoWidth;
							mOutputVideoHeight = mInputVideoHeight;
						}
						//判断滤镜旋转角度
						if(filterRotation == 90 || filterRotation == 270){
							int temp = mOutputVideoWidth;
							mOutputVideoWidth = mOutputVideoHeight;
							mOutputVideoHeight = temp;
						}
						Log.v("Mp4ProcessorHH","w:"+mOutputVideoWidth+";h:"+mOutputVideoHeight+";r:"+filterRotation);
						MediaFormat videoFormat = MediaFormat.createVideoFormat(/*mime*/"video/avc", mOutputVideoWidth, mOutputVideoHeight);
						videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
						videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, mOutputVideoHeight * mOutputVideoWidth * 5);
						videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 24);
						videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
//						videoFormat.setInteger(MediaFormat.KEY_ROTATION,0);
						mVideoEncoder = MediaCodec.createEncoderByType(/*mime*/"video/avc");
						mVideoEncoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
						mOutputSurface = mVideoEncoder.createInputSurface();
						Bundle bundle = new Bundle();
						if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
							bundle.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, mOutputVideoHeight * mOutputVideoWidth * 5);
							mVideoEncoder.setParameters(bundle);
						}
					}
				}
			}
			if (!isRenderToWindowSurface) {
				//如果用户没有设置渲染到指定Surface，就需要导出视频，暂时不对音频做处理
				mMuxer = new MediaMuxer(mOutputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
//                mMuxer.setOrientationHint(videoRotation);
				Log.d("Mp4Processor", "video rotation:" + videoRotation);
				//如果mp4中有音轨
				if (mAudioDecoderTrack >= 0) {
					MediaFormat format = mExtractor.getTrackFormat(mAudioDecoderTrack);
					Log.d("Mp4Processor", "audio track-->" + format.toString());

					mAudioEncoderTrack = mMuxer.addTrack(format);
				}
			}
		}
		return true;
	}

	public boolean start() throws IOException {
		synchronized (PROCESS_LOCK) {
			if (!isStarted) {
				if (!prepare()) {
					Log.e("Mp4Processor", "prepare failed");
					return false;
				}

				isUserWantToStop = false;

				isVideoExtractorEnd = false;
				isVideoExtractorEnd = false;
				mGLThreadFlag = true;
				mVideoDecoder.start();
				//mAudioDecoder.start();
				if (!isRenderToWindowSurface) {
					//mAudioEncoder.start();
					mVideoEncoder.start();
				}

				mGLThread = new Thread(new Runnable() {
					@Override
					public void run() {
						glRunnable();
					}
				});
				mGLThread.start();

				mCodecFlag = true;
				mDecodeThread = new Thread(new Runnable() {
					@Override
					public void run() {
						//视频处理
						if (mVideoDecoderTrack >= 0) {
							Log.d("Mp4Processor", "videoDecodeStep start");
							while (mCodecFlag && !videoDecodeStep()) ;
							Log.d("Mp4Processor", "videoDecodeStep end");
							mGLThreadFlag = false;
							try {
								mSem.release();
								mGLThread.join();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						//将原视频中的音频复制到新视频中
						if (mAudioDecoderTrack >= 0 && mVideoEncoderTrack >= 0) {
							ByteBuffer buffer = ByteBuffer.allocate(1024 * 32);
							while (mCodecFlag && !audioDecodeStep(buffer)) ;
							buffer.clear();
						}

						Log.d("Mp4Processor", "codec thread_finish");
						mCodecFlag = false;
						avStop();
						//todo 判断是用户取消了的情况
						if (mProgressListener != null) {
							mProgressListener.onComplete(mOutputPath);
						}
					}
				});
				mDecodeThread.start();
				isStarted = true;
			}
		}
		return true;
	}

	/**
	 * 等待解码线程执行完毕，异步线程同步等待
	 */
	public void waitProcessFinish() throws InterruptedException {
		if (mDecodeThread != null && mDecodeThread.isAlive()) {
			mDecodeThread.join();
		}
	}

	private boolean audioDecodeStep(ByteBuffer buffer) {
		boolean isTimeEnd = false;
		buffer.clear();
		synchronized (Extractor_LOCK) {
			mExtractor.selectTrack(mAudioDecoderTrack);
			int length = mExtractor.readSampleData(buffer, 0);
			if (length != -1) {
				int flags = mExtractor.getSampleFlags();
				mAudioEncoderBufferInfo.size = length;
				mAudioEncoderBufferInfo.flags = flags;
				mAudioEncoderBufferInfo.presentationTimeUs = mExtractor.getSampleTime();
				mAudioEncoderBufferInfo.offset = 0;
				isTimeEnd = mExtractor.getSampleTime() >= mVideoStopTimeStamp;
				mMuxer.writeSampleData(mAudioEncoderTrack, buffer, mAudioEncoderBufferInfo);
			}
			isAudioExtractorEnd = !mExtractor.advance();
		}
		return isAudioExtractorEnd || isTimeEnd;
	}

	//视频解码到SurfaceTexture上，以供后续处理。返回值为是否是最后一帧视频
	private boolean videoDecodeStep() {
		int mInputIndex = mVideoDecoder.dequeueInputBuffer(TIME_OUT);
		if (mInputIndex >= 0) {
			ByteBuffer buffer = getInputBuffer(mVideoDecoder, mInputIndex);
			buffer.clear();
			synchronized (Extractor_LOCK) {
				mExtractor.selectTrack(mVideoDecoderTrack);
				int ret = mExtractor.readSampleData(buffer, 0);
				if (ret != -1) {
					mVideoStopTimeStamp = mExtractor.getSampleTime();
					Log.d("Mp4Processor", "mVideoStopTimeStamp:" + mVideoStopTimeStamp);
					mVideoDecoder.queueInputBuffer(mInputIndex, 0, ret, mVideoStopTimeStamp, mExtractor.getSampleFlags());
				}
				isVideoExtractorEnd = !mExtractor.advance();
			}
		}
		while (true) {
			int mOutputIndex = mVideoDecoder.dequeueOutputBuffer(mVideoDecoderBufferInfo, TIME_OUT);
			if (mOutputIndex >= 0) {
				try {
					Log.d("Mp4Processor", " mDecodeSem.acquire ");
					mSem.release();
					if (!isUserWantToStop) {
						mDecodeSem.acquire();
					}
					Log.d("Mp4Processor", " mDecodeSem.acquire end ");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				mVideoDecoder.releaseOutputBuffer(mOutputIndex, true);
			} else if (mOutputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
				//MediaFormat format=mVideoDecoder.getOutputFormat();
			} else if (mOutputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
				break;
			}
		}
		return isVideoExtractorEnd || isUserWantToStop;
	}

	private boolean videoEncodeStep(boolean isEnd) {
		if (isEnd) {
			mVideoEncoder.signalEndOfInputStream();
		}
		while (true) {
			int mOutputIndex = mVideoEncoder.dequeueOutputBuffer(mVideoEncoderBufferInfo, TIME_OUT);
			Log.d("Mp4Processor", "videoEncodeStep-------------------mOutputIndex=" + mOutputIndex + "/" + mVideoEncoderBufferInfo.presentationTimeUs);
			if (mOutputIndex >= 0) {
				ByteBuffer buffer = getOutputBuffer(mVideoEncoder, mOutputIndex);
				if (mVideoEncoderBufferInfo.size > 0) {
					mMuxer.writeSampleData(mVideoEncoderTrack, buffer, mVideoEncoderBufferInfo);
				}
				mVideoEncoder.releaseOutputBuffer(mOutputIndex, false);
			} else if (mOutputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
				MediaFormat format = mVideoEncoder.getOutputFormat();
				Log.d("Mp4Processor", "video format -->" + format.toString());
				mVideoEncoderTrack = mMuxer.addTrack(format);
				mMuxer.start();
				synchronized (MUX_LOCK) {
					MUX_LOCK.notifyAll();
				}
			} else if (mOutputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
				break;
			}
		}
		return false;
	}

	private void glRunnable() {
		mSem = new Semaphore(0);
		mDecodeSem = new Semaphore(0);
		mEGLHelper.setSurface(mOutputSurface);
		boolean ret = mEGLHelper.createGLES(mOutputVideoWidth, mOutputVideoHeight);
		if (!ret) return;
		if (mRenderer == null) {
			mRenderer = new WrapRenderer(null);
		}
		mRenderer.setFlag(WrapRenderer.TYPE_MOVE);
		mRenderer.create();
		mRenderer.sizeChanged(mOutputVideoWidth, mOutputVideoHeight);
		while (mGLThreadFlag) {
			try {
				Log.d("Mp4Processor", " mSem.acquire ");
				mSem.acquire();
				Log.d("Mp4Processor", " mSem.acquire end");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (mGLThreadFlag) {
				mVideoSurfaceTexture.updateTexImage();
				//todo 带有rotation的视频，还需要处理
				mVideoSurfaceTexture.getTransformMatrix(mRenderer.getTextureMatrix());
				mRenderer.draw(mVideoTextureId);
				mEGLHelper.setPresentationTime(mVideoDecoderBufferInfo.presentationTimeUs * 1000);
				if (!isRenderToWindowSurface) {
					videoEncodeStep(false);
				}
				mEGLHelper.swapBuffers();
			}
			if (mProgressListener != null) {
				mProgressListener.onProgress(getTotalVideoTime() * 1000L, mVideoDecoderBufferInfo.presentationTimeUs);
			}
			mDecodeSem.release();
		}
		if (!isRenderToWindowSurface) {
			videoEncodeStep(true);
		}
		mRenderer.destroy();
		mEGLHelper.destroyGLES();
	}

	public long getPresentationTime() {
		return mVideoDecoderBufferInfo.presentationTimeUs * 1000;
	}

	public long getTotalVideoTime() {
		return mTotalVideoTime;
	}

	private SurfaceTexture.OnFrameAvailableListener mFrameAvaListener = new SurfaceTexture.OnFrameAvailableListener() {
		@Override
		public void onFrameAvailable(SurfaceTexture surfaceTexture) {
			Log.e("Mp4Processor", "mSem.release ");
//            mSem.release();
		}
	};

	private void avStop() {
		if (isStarted) {
			if (mVideoDecoder != null) {
				mVideoDecoder.stop();
				mVideoDecoder.release();
				mVideoDecoder = null;
			}
			if (!isRenderToWindowSurface && mVideoEncoder != null) {
				mVideoEncoder.stop();
				mVideoEncoder.release();
				mVideoEncoder = null;
			}
			if (!isRenderToWindowSurface) {
				if (mMuxer != null && mVideoEncoderTrack >= 0) {
					try {
						mMuxer.stop();
					} catch (IllegalStateException e) {
						e.printStackTrace();
					}
				}
				if (mMuxer != null) {
					try {
						mMuxer.release();
					} catch (IllegalStateException e) {
						e.printStackTrace();
					}
					mMuxer = null;
				}
			}
			if (mExtractor != null) {
				mExtractor.release();
			}
			isStarted = false;
			mVideoEncoderTrack = -1;
			mVideoDecoderTrack = -1;
			mAudioEncoderTrack = -1;
			mAudioDecoderTrack = -1;
		}
	}

	public boolean stop() throws InterruptedException {
		synchronized (PROCESS_LOCK) {
			if (isStarted) {
				if (mCodecFlag) {
					mDecodeSem.release();
					isUserWantToStop = true;
					if (mDecodeThread != null && mDecodeThread.isAlive()) {
						Log.d("Mp4Processor", "try to stop decode thread");
						mDecodeThread.join();
						Log.d("Mp4Processor", "decode thread stoped");
					}
					isUserWantToStop = false;
				}
			}
		}
		return true;
	}

	public boolean release() throws InterruptedException {
		synchronized (PROCESS_LOCK) {
			if (mCodecFlag) {
				stop();
			}
		}
		return true;
	}

	private ByteBuffer getInputBuffer(MediaCodec codec, int index) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			return codec.getInputBuffer(index);
		} else {
			return codec.getInputBuffers()[index];
		}
	}

	private ByteBuffer getOutputBuffer(MediaCodec codec, int index) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			return codec.getOutputBuffer(index);
		} else {
			return codec.getOutputBuffers()[index];
		}
	}

	public interface OnProgressListener {
		void onProgress(long max, long current);

		void onComplete(String path);
	}

}
