/*
 * Copyright (C) 2015 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.camera.one.config;

import android.content.ContentResolver;
import android.hardware.camera2.CameraCharacteristics;

import com.android.camera.app.MemoryManager;
import com.android.camera.debug.Log;
import com.android.camera.one.config.OneCameraFeatureConfig.CaptureSupportLevel;
import com.android.camera.one.config.OneCameraFeatureConfig.HdrPlusSupportLevel;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.GcamHelper;
import com.android.camera.util.GservicesHelper;
import com.google.common.base.Function;
import com.google.common.base.Optional;

/**
 * Creates the OneCamera feature configurations for the GoogleCamera app.
 */
public class OneCameraFeatureConfigCreator {
    private static final Log.Tag TAG = new Log.Tag("OneCamFtrCnfgCrtr");

    /**
     * Create the default camera feature config.
     */
    public static OneCameraFeatureConfig createDefault(ContentResolver contentResolver,
            MemoryManager memoryManager) {
        // Enable CaptureModule on all M devices.
        boolean useCaptureModule = true;
        Log.i(TAG, "CaptureModule? " + useCaptureModule);

        // HDR+ has multiple levels of support.
        HdrPlusSupportLevel hdrPlusSupportLevel =
                GcamHelper.determineHdrPlusSupportLevel(contentResolver, useCaptureModule);
        return new OneCameraFeatureConfig(useCaptureModule,
                buildCaptureModuleDetector(contentResolver),
                hdrPlusSupportLevel,
                memoryManager.getMaxAllowedNativeMemoryAllocation(),
                GservicesHelper.getMaxAllowedImageReaderCount(contentResolver));
    }

    private static Function<CameraCharacteristics, CaptureSupportLevel> buildCaptureModuleDetector(
            final ContentResolver contentResolver) {
        return new Function<CameraCharacteristics, CaptureSupportLevel>() {
            @Override
            public CaptureSupportLevel apply(CameraCharacteristics characteristics) {
                // If a capture support level override exists, use it. Otherwise
                // dynamically check the capabilities of the current device.
                Optional<CaptureSupportLevel> override =
                        getCaptureSupportLevelOverride(characteristics, contentResolver);
                if (override.isPresent()) {
                    Log.i(TAG, "Camera support level override: " + override.get().name());
                    return override.get();
                }

                Integer supportedLevel = characteristics
                        .get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);

                // A hardware level should always be supported, so we should
                // never have to return here. If no hardware level is supported
                // on a LEGACY device, the LIMITED_JPEG fallback will not work.
                if (supportedLevel == null) {
                    Log.e(TAG, "Device does not report supported hardware level.");
                    return CaptureSupportLevel.LIMITED_JPEG;
                }

                // LEGACY_JPEG is the ONLY mode that is supported on LEGACY
                // devices.
                if (supportedLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    return CaptureSupportLevel.LEGACY_JPEG;
                }

                // No matter if L or L MR1, the N5 does not currently support
                // ZSL due to HAL bugs. The latest one causes random preview
                // freezes even on MR1, see b/19565931.
                if (ApiHelper.IS_NEXUS_5) {
                    return CaptureSupportLevel.LIMITED_JPEG;
                }

                if (ApiHelper.IS_NEXUS_6) {
                    if (ApiHelper.isLMr1OrHigher()) {
                        // Although front-facing cameras on the N6 (and N5) are not advertised as
                        // FULL, they can do ZSL. We might want to change the check for ZSL
                        // according to b/19625916.
                        return CaptureSupportLevel.ZSL;
                    } else {
                        // On a non-LEGACY N6 (or N5) prior to Lollipop MR1 we fall back to
                        // LIMITED_JPEG due to HAL bugs.
                        return CaptureSupportLevel.LIMITED_JPEG;
                    }
                }

                // On FULL devices starting with L-MR1 we can run ZSL if private reprocessing
                // or YUV reprocessing is supported.
                if (supportedLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL ||
                        supportedLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
                    if (supportsReprocessing(characteristics)) {
                        return CaptureSupportLevel.ZSL;
                    } else {
                        return CaptureSupportLevel.LIMITED_YUV;
                    }
                }

                // On LIMITED devices starting with L-MR1 we run a simple YUV
                // capture mode.
                if (supportedLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED) {
                    return CaptureSupportLevel.LIMITED_YUV;
                }

                // We should never get here. If we do, let's fall back to a mode
                // that should work on all non-LEGACY devices.
                Log.e(TAG, "Unknown support level: " + supportedLevel);
                return CaptureSupportLevel.LIMITED_JPEG;
            }
        };
    }

    private static boolean supportsReprocessing(CameraCharacteristics characteristics) {
        Integer maxNumInputStreams = characteristics.get(
                CameraCharacteristics.REQUEST_MAX_NUM_INPUT_STREAMS);
        if (maxNumInputStreams == null) {
            Log.e(TAG, "Camera does not have maximum number of input streams.");
            return false;
        }
        if (maxNumInputStreams == 0) {
            return false;
        }

        int[] capabilities = characteristics.get(
                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
        for (int cap : capabilities) {
            if (cap == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING ||
                    cap == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return If an override exits, this returns the capture support hardware
     *         level that should be used on this device.
     */
    private static Optional<CaptureSupportLevel> getCaptureSupportLevelOverride(
            CameraCharacteristics cameraCharacteristics, ContentResolver contentResolver) {
        Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
        if (facing == null) {
            Log.e(TAG, "Camera not facing anywhere.");
            return Optional.absent();
        }

        switch (facing) {
            case CameraCharacteristics.LENS_FACING_BACK: {
                int override = GservicesHelper.getCaptureSupportLevelOverrideBack(contentResolver);
                return CaptureSupportLevel.fromFlag(override);
            }
            case CameraCharacteristics.LENS_FACING_FRONT: {
                int override = GservicesHelper.getCaptureSupportLevelOverrideFront(contentResolver);
                return CaptureSupportLevel.fromFlag(override);
            }
            default:
                Log.e(TAG, "Not sure where camera is facing to.");
                return Optional.absent();
        }
    }
}
