/*
 * Copyright (C) 2013 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 com.android.mediaframeworktest.functional.camera;

import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.FloatMath;
import android.util.Log;
import android.view.SurfaceHolder;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.List;

import com.android.mediaframeworktest.MediaFrameworkTest;
import com.android.mediaframeworktest.CameraStressTestRunner;
import com.android.mediaframeworktest.CameraTestHelper;
import junit.framework.Assert;

/**
 * Junit / Instrumentation test case for camera API pairwise testing
 * Settings tested against: flash mode, exposure compensation, white balance,
 *  scene mode, picture size, and geotagging
 *
 * adb shell am instrument
 *  - e class com.android.mediaframeworktest.stress.CameraPairwiseTest
 *  - w com.android.mediaframeworktest/.CameraStressTestRunner
 */
public class CameraPairwiseTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
    private CameraTestHelper mCameraTestHelper;
    private Handler mHandler;
    private Thread mLooperThread;
    private String TAG = "CameraPairwiseTest";

    private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds

    // coordinates of the Getty Museuem in Los Angeles
    private static final double MOCK_LATITUDE = 34.076621;
    private static final double MOCK_LONGITUDE = -118.473215;

    // camera setting enums
    public enum Flash { ON, OFF, AUTO };
    public enum Exposure { MIN, MAX, NONE };
    public enum WhiteBalance { DAYLIGHT, FLUORESCENT, CLOUDY, INCANDESCENT, AUTO };
    public enum SceneMode { SUNSET, ACTION, PARTY, NIGHT, AUTO };
    public enum PictureSize { SMALL, MEDIUM, LARGE };
    public enum Geotagging { ON, OFF };

    public CameraPairwiseTest() {
        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
    }

    protected void setUp() throws Exception {
        final Semaphore sem = new Semaphore(0);
        mLooperThread = new Thread() {
            @Override
            public void run() {
                Log.v(TAG, "starting looper");
                Looper.prepare();
                mHandler = new Handler();
                sem.release();
                Looper.loop();
                Log.v(TAG, "quit looper");
            }
        };
        mLooperThread.start();
        if (!sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
            fail("Failed to start the looper.");
        }
        getActivity();
        super.setUp();

        mCameraTestHelper = new CameraTestHelper();
    }

    @Override
    protected void tearDown() throws Exception {
        if (mHandler != null) {
            mHandler.getLooper().quit();
            mHandler = null;
        }
        if (mLooperThread != null) {
            mLooperThread.join(WAIT_TIMEOUT);
            if (mLooperThread.isAlive()) {
                fail("Failed to stop the looper.");
            }
            mLooperThread = null;
        }
        super.tearDown();
    }

    private void runOnLooper(final Runnable command) throws InterruptedException {
        final Semaphore sem = new Semaphore(0);
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    command.run();
                } finally {
                    sem.release();
                }
            }
        });
        if (!sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
            fail("Failed to run the command on the looper.");
        }
    }

    /**
     * Flash: Auto / Exposure: None / WB: Daylight
     * Scene: Sunset / Pic: Medium / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario01() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.NONE, WhiteBalance.DAYLIGHT, SceneMode.SUNSET,
                PictureSize.MEDIUM, Geotagging.OFF);
    }

    /**
     * Flash: On / Exposure: Min / WB: Fluorescent
     * Scene: Auto / Pic: Large / Geo: on
     */
    @LargeTest
    public void testCameraPairwiseScenario02() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.MIN, WhiteBalance.FLUORESCENT, SceneMode.AUTO,
                PictureSize.LARGE, Geotagging.ON);
    }

    /**
     * Flash: Off / Exposure: Max / WB: Auto
     * Scene: Night / Pic: Small / Geo: on
     */
    @LargeTest
    public void testCameraPairwiseScenario03() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.MAX, WhiteBalance.AUTO, SceneMode.NIGHT,
                PictureSize.SMALL, Geotagging.ON);
    }

    /**
     * Flash: Off / Exposure: Max / WB: Cloudy
     * Scene: Auto / Pic: Med / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario04() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.MAX, WhiteBalance.CLOUDY, SceneMode.AUTO,
                PictureSize.MEDIUM, Geotagging.OFF);
    }

    /**
     * Flash: Auto / Exposure: Max / WB: Incandescent
     * Scene: Auto / Pic: Large / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario05() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.MAX, WhiteBalance.INCANDESCENT,
                SceneMode.AUTO, PictureSize.LARGE, Geotagging.OFF);
    }

    /**
     * Flash: On / Exposure: None / WB: Cloudy
     * Scene: Auto / Pic: Small / Geo: on
     */
    @LargeTest
    public void testCameraPairwiseScenario06() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.NONE, WhiteBalance.CLOUDY, SceneMode.AUTO,
                PictureSize.SMALL, Geotagging.ON);
    }

    /**
     * Flash: Auto / Exposure: Min / WB: Auto
     * Scene: Action / Pic: Small / Geo: on
     */
    @LargeTest
    public void testCameraPairwiseScenario07() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.MIN, WhiteBalance.AUTO, SceneMode.ACTION,
                PictureSize.SMALL, Geotagging.ON);
    }

    /**
     * Flash: On / Exposure: Min / WB: Auto
     * Scene: Action / Pic: Medium / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario08() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.MIN, WhiteBalance.AUTO, SceneMode.ACTION,
                PictureSize.MEDIUM, Geotagging.OFF);
    }

    /**
     * Flash: Off / Exposure: Min / WB: Auto
     * Scene: Night / Pic: Large / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario09() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.MIN, WhiteBalance.AUTO, SceneMode.NIGHT,
                PictureSize.LARGE, Geotagging.OFF);
    }

    /**
     * Flash: Off / Exposure: Min / WB: Daylight
     * Scene: Sunset / Pic: Small / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario10() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.MIN, WhiteBalance.DAYLIGHT, SceneMode.SUNSET,
                PictureSize.SMALL, Geotagging.OFF);
    }

    /**
     * Flash: On / Exposure: Max / WB: Daylight
     * Scene: Sunset / Pic: Large / Geo: on
     */
    @LargeTest
    public void testCameraPairwiseScenario11() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.MAX, WhiteBalance.DAYLIGHT, SceneMode.SUNSET,
                PictureSize.LARGE, Geotagging.ON);
    }

    /**
     * Flash: Auto / Exposure: Min / WB: Cloudy
     * Scene: Auto / Pic: Large / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario12() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.MIN, WhiteBalance.CLOUDY, SceneMode.AUTO,
                PictureSize.LARGE, Geotagging.OFF);
    }

    /**
     * Flash: Off / Exposure: None / WB: Auto
     * Scene: Party / Pic: Medium / Geo: on
     */
    @LargeTest
    public void testCameraPairwiseScenario13() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.NONE, WhiteBalance.AUTO, SceneMode.PARTY,
                PictureSize.MEDIUM, Geotagging.ON);
    }

    /**
     * Flash: Auto / Exposure: None / WB: Auto
     * Scene: Night / Pic: Small / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario14() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.NONE, WhiteBalance.AUTO, SceneMode.NIGHT,
                PictureSize.SMALL, Geotagging.OFF);
    }

    /**
     * Flash: On / Exposure: None / WB: Incandescent
     * Scene: Auto / Pic: Medium / Geo: on
     */
    @LargeTest
    public void testCameraPairwiseScenario15() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.NONE, WhiteBalance.INCANDESCENT, SceneMode.AUTO,
                PictureSize.MEDIUM, Geotagging.ON);
    }

    /**
     * Flash: Auto / Exposure: Min / WB: Auto
     * Scene: Party / Pic: Small / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario16() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.MIN, WhiteBalance.AUTO, SceneMode.PARTY,
                PictureSize.SMALL, Geotagging.OFF);
    }

    /**
     * Flash: Off / Exposure: Min / WB: Incandescent
     * Scene: Auto / Pic: Small / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario17() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.MIN, WhiteBalance.INCANDESCENT, SceneMode.AUTO,
                PictureSize.SMALL, Geotagging.OFF);
    }

    /**
     * Flash: On / Exposure: None / WB: Auto
     * Scene: Party / Pic: Large / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario18() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.NONE, WhiteBalance.AUTO, SceneMode.PARTY,
                PictureSize.LARGE, Geotagging.OFF);
    }

    /**
     * Flash Off / Exposure: None / WB: Auto
     * Scene: Action / Pic: Large / Geo: off
     */
    @LargeTest
    public void testCameraPairwiseScenario19() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.NONE, WhiteBalance.AUTO, SceneMode.ACTION,
                PictureSize.LARGE, Geotagging.OFF);
    }

    /**
     * Flash: Off / Exposure: Max / WB: Fluorescent
     * Scene: Auto / Pic: Medium / Geo: Off
     */
    @LargeTest
    public void testCameraPairwiseScenario20() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.MAX, WhiteBalance.FLUORESCENT, SceneMode.AUTO,
                PictureSize.MEDIUM, Geotagging.OFF);
    }

    /**
     * Flash: Off / Exposure: Min / WB: Auto
     * Scene: Auto / Pic: Medium / Geo: off
     */
    public void testCameraPairwiseScenario21() throws Exception {
        genericPairwiseTestCase(Flash.OFF, Exposure.MIN, WhiteBalance.AUTO, SceneMode.AUTO,
                PictureSize.MEDIUM, Geotagging.OFF);
    }

    /**
     * Flash: On / Exposure: Max / WB: Auto
     * Scene: Action / Pic: Small / Geo: off
     */
    public void testCameraPairwiseScenario22() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.MAX, WhiteBalance.AUTO, SceneMode.ACTION,
                PictureSize.SMALL, Geotagging.OFF);
    }

    /**
     * Flash: On / Exposure: Max / WB: Auto
     * Scene: Night / Pic: Medium / Geo: on
     */
    public void testCameraPairwiseScenario23() throws Exception {
        genericPairwiseTestCase(Flash.ON, Exposure.MAX, WhiteBalance.AUTO, SceneMode.NIGHT,
                PictureSize.MEDIUM, Geotagging.ON);
    }

    /**
     * Flash: Auto / Exposure: None / WB: Fluorescent
     * Scene: Auto / Pic: Small / Geo: on
     */
    public void testCameraPairwiseScenario24() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.NONE, WhiteBalance.FLUORESCENT,
                SceneMode.AUTO, PictureSize.SMALL, Geotagging.ON);
    }

    /**
     * Flash: Auto / Exposure: Max / WB: Daylight
     * Scene: Auto / Pic: Medium / Geo: off
     */
    public void testCameraPairwiseScenario25() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.MAX, WhiteBalance.DAYLIGHT, SceneMode.AUTO,
                PictureSize.MEDIUM, Geotagging.OFF);
    }

    /**
     * Flash: Auto / Exposure: Max / WB: Auto
     * Scene: Party / Pic: Medium / Geo: on
     */
    public void testCameraPairwiseScenario26() throws Exception {
        genericPairwiseTestCase(Flash.AUTO, Exposure.MAX, WhiteBalance.AUTO, SceneMode.PARTY,
                PictureSize.MEDIUM, Geotagging.ON);
    }

    /**
     * Generic pairwise test method
     */
    private void genericPairwiseTestCase(Flash flash, Exposure exposure, WhiteBalance whitebalance,
            SceneMode scenemode, PictureSize picturesize, Geotagging geotagging) throws Exception {
        try {
            SurfaceHolder surfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
            Camera.Parameters params = mCameraTestHelper.getCameraParameters();

            runOnLooper(new Runnable() {
                @Override
                public void run() {
                    mCameraTestHelper.setupCameraTest();
                }
            });

            // Configure flash setting
            switch (flash) {
                case ON:
                    params.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
                    break;
                case OFF:
                    params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                    break;
                case AUTO:
                    params.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
                    break;
            }

            // Configure exposure setting
            switch (exposure) {
                case MIN:
                    params.setExposureCompensation(params.getMinExposureCompensation());
                    break;
                case MAX:
                    params.setExposureCompensation(params.getMaxExposureCompensation());
                    break;
                case NONE:
                    params.setExposureCompensation(0);
                    break;
            }

            // Configure white balance setting
            switch (whitebalance) {
                case DAYLIGHT:
                    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_DAYLIGHT);
                    break;
                case FLUORESCENT:
                    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_FLUORESCENT);
                    break;
                case INCANDESCENT:
                    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_INCANDESCENT);
                    break;
                case CLOUDY:
                    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT);
                    break;
                case AUTO:
                    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
                    break;
            }

            // Configure scene mode setting
            switch (scenemode) {
                case SUNSET:
                    params.setSceneMode(Camera.Parameters.SCENE_MODE_SUNSET);
                    break;
                case ACTION:
                    params.setSceneMode(Camera.Parameters.SCENE_MODE_ACTION);
                    break;
                case PARTY:
                    params.setSceneMode(Camera.Parameters.SCENE_MODE_PARTY);
                    break;
                case NIGHT:
                    params.setSceneMode(Camera.Parameters.SCENE_MODE_NIGHT);
                    break;
                case AUTO:
                    params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
                    break;
            }

            // Configure picture size setting
            List<Camera.Size> supportedPictureSizes = params.getSupportedPictureSizes();
            int mid = (int) Math.floor(supportedPictureSizes.size() / 2);
            int low = supportedPictureSizes.size() - 1;
            switch (picturesize) {
                case SMALL:
                    params.setPictureSize(supportedPictureSizes.get(low).width,
                            supportedPictureSizes.get(low).height);
                    break;
                case MEDIUM:
                    params.setPictureSize(supportedPictureSizes.get(mid).width,
                            supportedPictureSizes.get(mid).height);
                    break;
                case LARGE:
                    params.setPictureSize(supportedPictureSizes.get(0).width,
                            supportedPictureSizes.get(mid).height);
                    break;
            }

            // Configure geotagging setting
            switch (geotagging) {
                case ON:
                    params.setGpsLatitude(MOCK_LATITUDE);
                    params.setGpsLongitude(MOCK_LONGITUDE);
                    break;
                case OFF:
                    break;
            }

            mCameraTestHelper.setParameters(params);
            mCameraTestHelper.startCameraPreview(surfaceHolder);
            mCameraTestHelper.capturePhoto();
            mCameraTestHelper.cleanupTestImages();
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            fail("Test case failed");
        }
    }
}
