/*
 * Copyright (C) 2021 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.params;

import android.annotation.LongDef;
import android.annotation.NonNull;

import android.hardware.camera2.CameraMetadata;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * Immutable class with information about supported 10-bit dynamic range profiles.
 *
 * <p>An instance of this class can be queried by retrieving the value of
 * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES}.
 * </p>
 *
 * <p>All camera devices supporting the
 * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT}
 * capability must advertise the supported 10-bit dynamic range profiles in
 * {@link #getSupportedProfiles}</p>
 *
 * <p>Some devices may not be able to support 8-bit and/or 10-bit output with different dynamic
 * range profiles within the same capture request. Such device specific constraints can be queried
 * by calling {@link #getProfileCaptureRequestConstraints}. Do note that unsupported
 * combinations will result in {@link IllegalArgumentException} when trying to submit a capture
 * request. Capture requests that only reference outputs configured using the same dynamic range
 * profile value will never fail due to such constraints.</p>
 *
 * @see OutputConfiguration#setDynamicRangeProfile
 */
public final class DynamicRangeProfiles {
    /**
     * This the default 8-bit standard profile that will be used in case where camera clients do not
     * explicitly configure a supported dynamic range profile by calling
     * {@link OutputConfiguration#setDynamicRangeProfile}.
     */
    public static final long STANDARD =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD;

    /**
     * 10-bit pixel samples encoded using the Hybrid log-gamma transfer function
     *
     * <p>All 10-bit output capable devices are required to support this profile.</p>
     */
    public static final long HLG10  =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10;

    /**
     * 10-bit pixel samples encoded using the SMPTE ST 2084 transfer function.
     *
     * <p>This profile utilizes internal static metadata to increase the quality
     * of the capture.</p>
     */
    public static final long HDR10  =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10;

    /**
     * 10-bit pixel samples encoded using the SMPTE ST 2084 transfer function.
     *
     * <p>In contrast to HDR10, this profile uses internal per-frame metadata
     * to further enhance the quality of the capture.</p>
     */
    public static final long HDR10_PLUS =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS;

    /**
     * <p>This is a camera mode for Dolby Vision capture optimized for a more scene
     * accurate capture. This would typically differ from what a specific device
     * might want to tune for a consumer optimized Dolby Vision general capture.</p>
     */
    public static final long DOLBY_VISION_10B_HDR_REF =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF;

    /**
     * <p>This is the power optimized mode for 10-bit Dolby Vision HDR Reference Mode.</p>
     */
    public static final long DOLBY_VISION_10B_HDR_REF_PO =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO;

    /**
     * <p>This is the camera mode for the default Dolby Vision capture mode for the
     * specific device. This would be tuned by each specific device for consumer
     * pleasing results that resonate with their particular audience. We expect
     * that each specific device would have a different look for their default
     * Dolby Vision capture.</p>
     */
    public static final long DOLBY_VISION_10B_HDR_OEM =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM;

    /**
     * <p>This is the power optimized mode for 10-bit Dolby Vision HDR device specific capture
     * Mode.</p>
     */
    public static final long DOLBY_VISION_10B_HDR_OEM_PO =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO;

    /**
     * <p>This is the 8-bit version of the Dolby Vision reference capture mode optimized
     * for scene accuracy.</p>
     */
    public static final long DOLBY_VISION_8B_HDR_REF =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF;

    /**
     * <p>This is the power optimized mode for 8-bit Dolby Vision HDR Reference Mode.</p>
     */
    public static final long DOLBY_VISION_8B_HDR_REF_PO =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO;

    /**
     * <p>This is the 8-bit version of device specific tuned and optimized Dolby Vision
     * capture mode.</p>
     */
    public static final long DOLBY_VISION_8B_HDR_OEM =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM;

    /**
     * <p>This is the power optimized mode for 8-bit Dolby Vision HDR device specific
     * capture Mode.</p>
     */
    public static final long DOLBY_VISION_8B_HDR_OEM_PO =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO;

    /*
     * @hide
     */
    public static final long PUBLIC_MAX =
            CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX;

     /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @LongDef(prefix = {"PROFILE_"}, value =
            {STANDARD,
             HLG10,
             HDR10,
             HDR10_PLUS,
             DOLBY_VISION_10B_HDR_REF,
             DOLBY_VISION_10B_HDR_REF_PO,
             DOLBY_VISION_10B_HDR_OEM,
             DOLBY_VISION_10B_HDR_OEM_PO,
             DOLBY_VISION_8B_HDR_REF,
             DOLBY_VISION_8B_HDR_REF_PO,
             DOLBY_VISION_8B_HDR_OEM,
             DOLBY_VISION_8B_HDR_OEM_PO})
    public @interface Profile {
    }

    private final HashMap<Long, Set<Long>> mProfileMap = new HashMap<>();
    private final HashMap<Long, Boolean> mLookahedLatencyMap = new HashMap<>();

    /**
     * Create a new immutable DynamicRangeProfiles instance.
     *
     * <p>This constructor takes over the array; do not write to the array afterwards.</p>
     *
     * <p>Do note that the constructor is available for testing purposes only!
     * Camera clients must always retrieve the value of
     * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES}.
     * for a given camera id in order to retrieve the device capabilities.</p>
     *
     * @param elements
     *          An array of elements describing the map. It contains three elements per entry. The
     *          first element describes the supported dynamic range profile value. The
     *          second element contains a bitmap of concurrently supported dynamic range profiles
     *          within the same capture request. The third element contains a hint about
     *          extra latency associated with the corresponding dynamic range. Bitmap values of 0
     *          indicate that there are no constraints.
     *
     * @throws IllegalArgumentException
     *            if the {@code elements} array length is invalid, not divisible by 3 or contains
     *            invalid element values
     * @throws NullPointerException
     *            if {@code elements} is {@code null}
     *
     */
    public DynamicRangeProfiles(@NonNull final long[] elements) {
        if ((elements.length % 3) != 0) {
            throw new IllegalArgumentException("Dynamic range profile map length " +
                    elements.length + " is not even!");
        }

        for (int i = 0; i < elements.length; i += 3) {
            checkProfileValue(elements[i]);
            // STANDARD is not expected to be included
            if (elements[i] == STANDARD) {
                throw new IllegalArgumentException("Dynamic range profile map must not include a"
                        + " STANDARD profile entry!");
            }
            HashSet<Long> profiles = new HashSet<>();

            if (elements[i+1] != 0) {
                for (long profile = STANDARD; profile < PUBLIC_MAX; profile <<= 1) {
                    if ((elements[i+1] & profile) != 0) {
                        profiles.add(profile);
                    }
                }
            }

            mProfileMap.put(elements[i], profiles);
            mLookahedLatencyMap.put(elements[i], elements[i+2] != 0L);
        }

        // Build the STANDARD constraints depending on the advertised 10-bit limitations
        HashSet<Long> standardConstraints = new HashSet<>();
        standardConstraints.add(STANDARD);
        for(Long profile : mProfileMap.keySet()) {
            if (mProfileMap.get(profile).isEmpty() || mProfileMap.get(profile).contains(STANDARD)) {
                standardConstraints.add(profile);
            }
        }

        mProfileMap.put(STANDARD, standardConstraints);
        mLookahedLatencyMap.put(STANDARD, false);
    }


    /**
     * @hide
     */
    public static void checkProfileValue(long profile) {
        if (profile == STANDARD || profile == HLG10 || profile == HDR10 || profile == HDR10_PLUS
                || profile == DOLBY_VISION_10B_HDR_REF || profile == DOLBY_VISION_10B_HDR_REF_PO
                || profile == DOLBY_VISION_10B_HDR_OEM || profile == DOLBY_VISION_10B_HDR_OEM_PO
                || profile == DOLBY_VISION_8B_HDR_REF || profile == DOLBY_VISION_8B_HDR_REF_PO
                || profile == DOLBY_VISION_8B_HDR_OEM
                || profile == DOLBY_VISION_8B_HDR_OEM_PO) {//No-op
        } else {
            throw new IllegalArgumentException("Unknown profile " + profile);
        }
    }

    /**
     * Return a set of supported dynamic range profiles.
     *
     * @return non-modifiable set of dynamic range profiles
     */
     public @NonNull Set<Long> getSupportedProfiles() {
         return Collections.unmodifiableSet(mProfileMap.keySet());
     }

    /**
     * Return a list of supported dynamic range profiles that
     * can be referenced in a single capture request along with a given
     * profile.
     *
     * <p>For example if assume that a particular 10-bit output capable device
     * returns ({@link #STANDARD}, {@link #HLG10}, {@link #HDR10}) as result from calling
     * {@link #getSupportedProfiles()} and {@link #getProfileCaptureRequestConstraints}
     * returns ({@link #STANDARD}, {@link #HLG10}) when given an argument of {@link #STANDARD}.
     * This means that the corresponding camera device will only accept and process capture requests
     * that reference outputs configured using {@link #HDR10} dynamic profile or alternatively
     * some combination of {@link #STANDARD} and {@link #HLG10}. However trying to
     * queue capture requests to outputs that reference both {@link #HDR10} and
     * {@link #STANDARD}/{@link #HLG10} will result in {@link IllegalArgumentException}.</p>
     *
     * <p>The list will be empty in case there are no constraints for the given
     * profile.</p>
     *
     * @return non-modifiable set of dynamic range profiles
     * @throws IllegalArgumentException   If the profile argument is not
     *                                    within the list returned by
     *                                    getSupportedProfiles()
     *
     * @see OutputConfiguration#setDynamicRangeProfile
     */
     public @NonNull Set<Long> getProfileCaptureRequestConstraints(@Profile long profile) {
         Set<Long> ret = mProfileMap.get(profile);
         if (ret == null) {
             throw new IllegalArgumentException("Unsupported profile!");
         }

         return Collections.unmodifiableSet(ret);
     }

    /**
     * Check whether a given dynamic range profile is suitable for latency sensitive use cases.
     *
     * <p>Due to internal lookahead logic, camera outputs configured with some dynamic range
     * profiles may experience additional latency greater than 3 buffers. Using camera outputs
     * with such profiles for latency sensitive use cases such as camera preview is not
     * recommended. Profiles that have such extra streaming delay are typically utilized for
     * scenarios such as offscreen video recording.</p>
     *
     * @return true if the given profile is not suitable for latency sensitive use cases, false
     *         otherwise
     * @throws IllegalArgumentException   If the profile argument is not
     *                                    within the list returned by
     *                                    getSupportedProfiles()
     *
     * @see OutputConfiguration#setDynamicRangeProfile
     */
    public boolean isExtraLatencyPresent(@Profile long profile) {
        Boolean ret = mLookahedLatencyMap.get(profile);
        if (ret == null) {
            throw new IllegalArgumentException("Unsupported profile!");
        }

        return ret;
    }
}
