package io.iclue.backgroundvideo;

import android.app.Activity;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.text.TextUtils;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;

import java.io.IOException;

@SuppressWarnings("deprecation")
public class VideoOverlay extends ViewGroup implements TextureView.SurfaceTextureListener {
    private static final String TAG = "BACKGROUND_VID_OVERLAY";
    private RecordingState mRecordingState = RecordingState.INITIALIZING;

    private int mCameraId = CameraHelper.NO_CAMERA;
    private Camera mCamera = null;
    private final TextureView mPreview;
    private boolean mPreviewAttached = false;
    private MediaRecorder mRecorder = null;
    private boolean mStartWhenInitialized = false;

    private String mFilePath;
    private boolean mRecordAudio = true;
    private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
    private int mOrientation;

    public VideoOverlay(Context context) {
        super(context);

        this.setClickable(false);
        this.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        // Create surface to display the camera preview
        mPreview = new TextureView(context);
        mPreview.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        mPreview.setClickable(false);
        mPreview.setSurfaceTextureListener(this);
        attachView();
    }

    public void setCameraFacing(String cameraFace) {
        mCameraFacing = (cameraFace.equalsIgnoreCase("FRONT") ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK);
    }

    public void setRecordAudio(boolean recordAudio) {
        mRecordAudio = recordAudio;
    }

    public void Start(String filePath) throws Exception {
        if (this.mRecordingState == RecordingState.STARTED) {
            Log.w(TAG, "Already Recording");
            return;
        }

        if (!TextUtils.isEmpty(filePath)) {
            this.mFilePath = filePath;
        }

        attachView();

        if (this.mRecordingState == RecordingState.INITIALIZING) {
            this.mStartWhenInitialized = true;
            return;
        }

        if (TextUtils.isEmpty(mFilePath)) {
            throw new IllegalArgumentException("Filename for recording must be set");
        }

        initializeCamera();

        if (mCamera == null) {
            this.detachView();
            throw new NullPointerException("Cannot start recording, we don't have a camera!");
        }

        // Set camera parameters
        Camera.Parameters cameraParameters = mCamera.getParameters();
        mCamera.stopPreview(); //Apparently helps with freezing issue on some Samsung devices.
        mCamera.unlock();

        try {
            mRecorder = new MediaRecorder();
            mRecorder.setCamera(mCamera);

            CamcorderProfile profile;
            if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_LOW)) {
                profile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_LOW);
            } else {
                profile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH);
            }

            Camera.Size lowestRes = CameraHelper.getLowestResolution(cameraParameters);
            profile.videoFrameWidth = lowestRes.width;
            profile.videoFrameHeight = lowestRes.height;

            mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            if (mRecordAudio) {
                // With audio
                mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                mRecorder.setVideoFrameRate(profile.videoFrameRate);
                mRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
                mRecorder.setVideoEncodingBitRate(profile.videoBitRate);
                mRecorder.setAudioEncodingBitRate(profile.audioBitRate);
                mRecorder.setAudioChannels(profile.audioChannels);
                mRecorder.setAudioSamplingRate(profile.audioSampleRate);
                mRecorder.setVideoEncoder(profile.videoCodec);
                mRecorder.setAudioEncoder(profile.audioCodec);
            } else {
                // Without audio
                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                mRecorder.setVideoFrameRate(profile.videoFrameRate);
                mRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
                mRecorder.setVideoEncodingBitRate(profile.videoBitRate);
                mRecorder.setVideoEncoder(profile.videoCodec);
            }

            mRecorder.setOutputFile(filePath);
            mRecorder.setOrientationHint(mOrientation);
            mRecorder.prepare();
            Log.d(TAG, "Starting recording");
            mRecorder.start();
        } catch (Exception e) {
            this.releaseCamera();
            Log.e(TAG, "Could not start recording! MediaRecorder Error", e);
            throw e;
        }
    }

    public String Stop() throws IOException {
        Log.d(TAG, "stopRecording called");

        if (mRecorder != null) {
            MediaRecorder tempRecorder = mRecorder;
            mRecorder = null;
            try {
                tempRecorder.stop();
            } catch (Exception e) {
                //This can occur when the camera failed to start and then stop is called
                Log.e(TAG, "Could not stop recording.", e);
            }
        }

        this.releaseCamera();
        this.detachView();

        return this.mFilePath;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int numChildren = getChildCount();
        if (changed && numChildren > 0) {
            int itemWidth = (r - l) / numChildren;
            for (int i = 0; i < numChildren; i++) {
                View v = getChildAt(i);
                v.layout(itemWidth * i, 0, (i + 1) * itemWidth, b - t);
            }
        }
    }

    private void initializeCamera() {
        if (mCamera == null) {
            try {
                mCameraId = CameraHelper.getCameraId(mCameraFacing);
                if (mCameraId != CameraHelper.NO_CAMERA) {
                    mCamera = Camera.open(mCameraId);

                    // Set camera parameters
                    mOrientation = CameraHelper.calculateOrientation((Activity) this.getContext(), mCameraId);
                    Camera.Parameters cameraParameters = mCamera.getParameters();
                    Camera.Size previewSize = CameraHelper.getPreviewSize(cameraParameters);
                    cameraParameters.setPreviewSize(previewSize.width, previewSize.height);
                    cameraParameters.setRotation(mOrientation);
                    cameraParameters.setRecordingHint(true);

                    mCamera.setParameters(cameraParameters);
                    mCamera.setDisplayOrientation(mOrientation);
                }
            } catch (RuntimeException ex) {
                this.releaseCamera();
                Log.e(TAG, "Unable to open camera. Another application probably has a lock", ex);
            }
        }
    }

    private void releaseCamera() {
        if (mRecorder != null) {
            mRecorder.reset();
            mRecorder.release();
            mRecorder = null;
        }
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.lock();
            mCamera.release();
            mCamera = null;
            mCameraId = CameraHelper.NO_CAMERA;
        }
        this.mRecordingState = RecordingState.STOPPED;
    }

    private void attachView() {
        if (!mPreviewAttached && mPreview != null) {
            this.addView(mPreview);
            this.mPreviewAttached = true;
        }
    }

    private void detachView() {
        if (mPreviewAttached && mPreview != null) {
            this.removeView(mPreview);
            this.mPreviewAttached = false;
            this.mRecordingState = RecordingState.INITIALIZING;
        }
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "Creating Texture Created");

        this.mRecordingState = RecordingState.STOPPED;

        initializeCamera();

        if (mCamera != null) {
            try {
                mCamera.setPreviewTexture(surface);
            } catch (IOException e) {
                Log.e(TAG, "Unable to attach preview to camera!", e);
            }
            mCamera.startPreview();
        }

        if (mStartWhenInitialized) {
            try {
                Start(this.mFilePath);
            } catch (Exception ex) {
                Log.e(TAG, "Error start camera", ex);
            }
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    }


    private enum RecordingState {INITIALIZING, STARTED, STOPPED}
}