package com.qiniu.pili.droid.streaming.demo.core;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;

import com.qiniu.pili.droid.streaming.av.common.PLFourCC;

import java.io.IOException;
import java.util.List;

public final class ExtVideoCapture implements SurfaceHolder.Callback, Camera.PreviewCallback {
    private static final String TAG = "ExtVideoCapture";

    public interface OnPreviewFrameCallback {
        void onPreviewFrameCaptured(byte[] data, int width, int height, int orientation, boolean mirror, int fmt, long tsInNanoTime);
    }

    public static final int MAX_CALLBACK_BUFFER_NUM = 2;

    private Camera mCamera;
    private int mCurrentFacingId = Camera.CameraInfo.CAMERA_FACING_BACK;

    private OnPreviewFrameCallback mOnPreviewFrameCallback;

    private int mPreviewWidth = 0;
    private int mPreviewHeight = 0;

    private Context mContext;
    private int mCameraPreviewDegree;

    public ExtVideoCapture(SurfaceView sv) {
        sv.getHolder().addCallback(this);
        sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mContext = sv.getContext();
    }

    public void setOnPreviewFrameCallback(OnPreviewFrameCallback callback) {
        mOnPreviewFrameCallback = callback;
    }

    public void switchCamera() {
        stopPreviewAndFreeCamera();

        if (mCurrentFacingId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            mCurrentFacingId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        } else {
            mCurrentFacingId = Camera.CameraInfo.CAMERA_FACING_BACK;
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (safeCameraOpen(mCurrentFacingId)) {
            try {
                mCamera.setPreviewDisplay(holder);

                int degree = getDeviceRotationDegree(mContext);
                Camera.CameraInfo camInfo = new Camera.CameraInfo();
                Camera.getCameraInfo(mCurrentFacingId, camInfo);
                int orientation;
                if (mCurrentFacingId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    orientation = (camInfo.orientation + degree) % 360;
                    mCameraPreviewDegree = orientation;
                    orientation = (360 - orientation) % 360;  // compensate the mirror
                } else {  // back-facing
                    orientation = (camInfo.orientation - degree + 360) % 360;
                    mCameraPreviewDegree = orientation;
                }
                mCamera.setDisplayOrientation(orientation);

                Camera.Parameters params = mCamera.getParameters();
                params.setPreviewFormat(ImageFormat.NV21);

                final Camera.Size previewSize = params.getPreviewSize();
                final int bitsPerPixel = ImageFormat.getBitsPerPixel(params.getPreviewFormat());
                final int previewBufferSize = (previewSize.width * previewSize.height * bitsPerPixel) / 8;
                for (int i = 0; i < MAX_CALLBACK_BUFFER_NUM; i++) {
                    mCamera.addCallbackBuffer(new byte[previewBufferSize]);
                }

                mPreviewWidth = previewSize.width;
                mPreviewHeight = previewSize.height;

                mCamera.setPreviewCallbackWithBuffer(this);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mCamera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        stopPreviewAndFreeCamera();
    }

    private boolean safeCameraOpen(int id) {
        boolean qOpened = false;

        try {
            releaseCameraAndPreview();
            mCamera = Camera.open(id);
            qOpened = (mCamera != null);
        } catch (Exception e) {
            Log.e(TAG, "failed to open Camera");
            e.printStackTrace();
        }

        if (qOpened) {
            mCurrentFacingId = id;
        }

        return qOpened;
    }

    private void releaseCameraAndPreview() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }

    /**
     * When this function returns, mCamera will be null.
     */
    private void stopPreviewAndFreeCamera() {
        if (mCamera != null) {
            // Call stopPreview() to stop updating the preview surface.
            mCamera.stopPreview();

            // Important: Call release() to release the camera for use by other
            // applications. Applications should release the camera immediately
            // during onPause() and re-open() it during onResume()).
            mCamera.release();

            mCamera = null;
        }
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (camera == null || data == null) {
            return;
        }

        if (mOnPreviewFrameCallback != null) {
            mOnPreviewFrameCallback.onPreviewFrameCaptured(data, mPreviewWidth, mPreviewHeight, mCameraPreviewDegree, false, PLFourCC.FOURCC_NV21, System.nanoTime());
        }

        if (mCamera != null) {
            mCamera.addCallbackBuffer(data);
        }
    }

    private static int getDisplayDefaultRotation(Context ctx) {
        WindowManager windowManager = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
        return windowManager.getDefaultDisplay().getRotation();
    }

    private static int getDeviceRotationDegree(Context ctx) {
        switch (getDisplayDefaultRotation(ctx)) {
            // normal portrait
            case Surface.ROTATION_0:
                return 0;
            // expected landscape
            case Surface.ROTATION_90:
                return 90;
            // upside down portrait
            case Surface.ROTATION_180:
                return 180;
            // "upside down" landscape
            case Surface.ROTATION_270:
                return 270;
        }
        return 0;
    }
}