/*
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.hardware.camera2.cts;

import static android.hardware.camera2.cts.CameraTestUtils.*;

import com.android.ex.camera2.blocking.BlockingSessionCallback;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.cts.helpers.CameraUtils;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.media.Image;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import android.util.Size;

import java.nio.ByteBuffer;

import android.hardware.camera2.cts.Camera2SurfaceViewCtsActivity;
import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;

import org.junit.runners.Parameterized;
import org.junit.runner.RunWith;
import org.junit.Test;

/**
 * Quick-running test for very basic camera operation for all cameras
 * and both camera APIs.
 *
 * May not take more than a few seconds to run, to be suitable for quick
 * testing.
 */

@RunWith(Parameterized.class)
public class FastBasicsTest extends Camera2SurfaceViewTestCase {
    private static final String TAG = "FastBasicsTest";
    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final int WAIT_FOR_FRAMES_TIMEOUT_MS = 3000;
    private static final int WAIT_FOR_PICTURE_TIMEOUT_MS = 8000;
    private static final int FRAMES_TO_WAIT_FOR_CAPTURE = 100;

    @Presubmit
    @Test
    public void testCamera2() throws Exception {
        String[] cameraIdsUnderTest = getCameraIdsUnderTest();
        for (int i = 0; i < cameraIdsUnderTest.length; i++) {
            try {
                Log.i(TAG, "Testing camera2 API for camera device " + cameraIdsUnderTest[i]);

                if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).isColorOutputSupported()) {
                    Log.i(TAG, "Camera " + cameraIdsUnderTest[i] +
                            " does not support color outputs, skipping");
                    continue;
                }

                openDevice(cameraIdsUnderTest[i]);
                camera2TestByCamera();
            } finally {
                closeDevice();
            }
        }
    }

    public void camera2TestByCamera() throws Exception {
        CaptureRequest.Builder previewRequest =
                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        CaptureRequest.Builder stillCaptureRequest =
                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        Size previewSize = mOrderedPreviewSizes.get(0);
        Size stillSize = mOrderedStillSizes.get(0);
        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
        SimpleImageReaderListener imageListener = new SimpleImageReaderListener();

        prepareStillCaptureAndStartPreview(previewRequest, stillCaptureRequest,
                previewSize, stillSize, resultListener, imageListener, false /*isHeic*/);

        CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_FRAMES_TIMEOUT_MS);

        Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
        assertNotNull("Can't read a capture result timestamp", timestamp);

        CaptureResult result2 = resultListener.getCaptureResult(WAIT_FOR_FRAMES_TIMEOUT_MS);

        Long timestamp2 = result2.get(CaptureResult.SENSOR_TIMESTAMP);
        assertNotNull("Can't read a capture result 2 timestamp", timestamp2);

        assertTrue("Bad timestamps", timestamp2 > timestamp);

        // If EnableZsl is supported, disable ZSL in order to compare preview and still timestamps.
        if (mStaticInfo.isEnableZslSupported()) {
            stillCaptureRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, false);
        }

        CaptureRequest capture = stillCaptureRequest.build();
        mSession.capture(capture, resultListener, mHandler);

        CaptureResult stillResult =
                resultListener.getTotalCaptureResultForRequest(capture, FRAMES_TO_WAIT_FOR_CAPTURE);

        Long timestamp3 = stillResult.get(CaptureResult.SENSOR_TIMESTAMP);
        assertNotNull("Can't read a still capture result timestamp", timestamp3);

        assertTrue("Bad timestamps", timestamp3 > timestamp2);

        Image img = imageListener.getImage(WAIT_FOR_PICTURE_TIMEOUT_MS);

        ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer();
        byte[] jpegData = new byte[jpegBuffer.remaining()];
        jpegBuffer.get(jpegData);

        Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);

        assertNotNull("Unable to decode still capture JPEG", b);

        closeImageReader();
    }

    private class Camera1Listener
            implements SurfaceTexture.OnFrameAvailableListener, Camera.PictureCallback {

        private Object mFrameSignal = new Object();
        private boolean mGotFrame = false;

        public boolean waitForFrame() {
            synchronized(mFrameSignal) {
                boolean waited = false;
                while (!waited) {
                    try {
                        mFrameSignal.wait(WAIT_FOR_FRAMES_TIMEOUT_MS);
                        waited = true;
                    } catch (InterruptedException e) {
                    }
                }
                return mGotFrame;
            }
        }

        public void onFrameAvailable(SurfaceTexture s) {
            synchronized(mFrameSignal) {
                mGotFrame = true;
                mFrameSignal.notifyAll();
            }
        }

        private Object mPictureSignal = new Object();
        private boolean mGotPicture = false;
        private byte[] mPictureData = null;

        public byte[] waitForPicture() {
            Log.i(TAG, "waitForPicture called");
            synchronized(mPictureSignal) {
                boolean waited = false;
                while (!waited) {
                    try {
                        mPictureSignal.wait(WAIT_FOR_PICTURE_TIMEOUT_MS);
                        waited = true;
                        Log.i(TAG, "waitForPicture returned with mGotPicture = " + mGotPicture);
                    } catch (InterruptedException e) {
                        Log.e(TAG, "waitForPicture gets interrupted exception!");
                    }
                }
                return mPictureData;
            }
        }

        public void onPictureTaken(byte[] data, Camera camera) {
            Log.i(TAG, "onPictureTaken called");
            synchronized(mPictureSignal) {
                mPictureData = data;
                mGotPicture = true;
                mPictureSignal.notifyAll();
            }
        }
    }

    @Presubmit
    @Test
    public void testCamera1() throws Exception {
        int[] cameraIds = CameraUtils.deriveCameraIdsUnderTest();
        for (int i : cameraIds) {
            Camera camera = null;
            try {
                Log.i(TAG, "Testing android.hardware.Camera API for camera device " + i);

                camera = Camera.open(i);

                Camera1Listener listener = new Camera1Listener();

                SurfaceTexture st = new SurfaceTexture(/*random int*/ 5);
                st.setOnFrameAvailableListener(listener);

                camera.setPreviewTexture(st);
                camera.startPreview();

                assertTrue("No preview received from camera", listener.waitForFrame());

                camera.takePicture(null, null, listener);

                byte[] picture = listener.waitForPicture();

                assertNotNull("No still picture received from camera", picture);

                Bitmap b = BitmapFactory.decodeByteArray(picture, 0, picture.length);

                assertNotNull("Still picture could not be decoded into Bitmap", b);

            } finally {
                if (camera != null) {
                    camera.release();
                }
            }
        }
    }
}
