/*
 * Copyright 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 android.hardware.camera2.utils;

import static android.system.OsConstants.EINVAL;

import static com.android.internal.util.Preconditions.checkNotNull;

import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.hardware.HardwareBuffer;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.util.Range;
import android.util.Size;
import android.view.Surface;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * Various Surface utilities.
 */
public class SurfaceUtils {

    // Usage flags not yet included in HardwareBuffer
    private static final int USAGE_RENDERSCRIPT = 0x00100000;
    private static final int USAGE_HW_COMPOSER = 0x00000800;

    // Image formats not yet included in PixelFormat
    private static final int BGRA_8888 = 0x5;

    private static final int BAD_VALUE = -EINVAL;

    /**
     * Check if a surface is for preview consumer based on consumer end point Gralloc usage flags.
     *
     * @param surface The surface to be checked.
     * @return true if the surface is for preview consumer, false otherwise.
     */
    public static boolean isSurfaceForPreview(Surface surface) {
        checkNotNull(surface);
        long usageFlags = nativeDetectSurfaceUsageFlags(surface);
        long disallowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE | USAGE_RENDERSCRIPT
                | HardwareBuffer.USAGE_CPU_READ_OFTEN;
        long allowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | USAGE_HW_COMPOSER
                | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
        boolean previewConsumer = ((usageFlags & disallowedFlags) == 0
                && (usageFlags & allowedFlags) != 0);
        int surfaceFormat = getSurfaceFormat(surface);

        return previewConsumer;
    }

    /**
     * Check if the surface is for hardware video encoder consumer based on consumer end point
     * Gralloc usage flags.
     *
     * @param surface The surface to be checked.
     * @return true if the surface is for hardware video encoder consumer, false otherwise.
     */
    public static boolean isSurfaceForHwVideoEncoder(Surface surface) {
        checkNotNull(surface);
        long usageFlags = nativeDetectSurfaceUsageFlags(surface);
        long disallowedFlags = USAGE_HW_COMPOSER
                | USAGE_RENDERSCRIPT | HardwareBuffer.USAGE_CPU_READ_OFTEN;
        long allowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE;
        boolean videoEncoderConsumer = ((usageFlags & disallowedFlags) == 0
                && (usageFlags & allowedFlags) != 0);

        int surfaceFormat = getSurfaceFormat(surface);

        return videoEncoderConsumer;
    }

    /**
     * Get the native object id of a surface.
     *
     * @param surface The surface to be checked.
     * @return the native object id of the surface, 0 if surface is not backed by a native object.
     */
    public static long getSurfaceId(Surface surface) {
        checkNotNull(surface);
        try {
            return nativeGetSurfaceId(surface);
        } catch (IllegalArgumentException e) {
            return 0;
        }
    }

    /**
     * Get the surface usage bits.
     *
     * @param surface The surface to be queried for usage.
     * @return the native object id of the surface, 0 if surface is not backed by a native object.
     */
    public static long getSurfaceUsage(Surface surface) {
        checkNotNull(surface);
        try {
            return nativeDetectSurfaceUsageFlags(surface);
        } catch (IllegalArgumentException e) {
            return 0;
        }
    }
    /**
     * Get the Surface size.
     *
     * @param surface The surface to be queried for size.
     * @return Size of the surface.
     *
     * @throws IllegalArgumentException if the surface is already abandoned.
     */
    @UnsupportedAppUsage
    public static Size getSurfaceSize(Surface surface) {
        checkNotNull(surface);

        int[] dimens = new int[2];
        int errorFlag =  nativeDetectSurfaceDimens(surface, /*out*/dimens);
        if (errorFlag == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");

        return new Size(dimens[0], dimens[1]);
    }

    /**
     * Get the Surface format.
     *
     * @param surface The surface to be queried for format.
     * @return format of the surface.
     *
     * @throws IllegalArgumentException if the surface is already abandoned.
     */
    public static int getSurfaceFormat(Surface surface) {
        checkNotNull(surface);
        int surfaceType = nativeDetectSurfaceType(surface);
        if (surfaceType == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");

        // TODO: remove this override since the default format should be
        // ImageFormat.PRIVATE. b/9487482
        if ((surfaceType >= PixelFormat.RGBA_8888
                && surfaceType <= BGRA_8888)) {
            surfaceType = ImageFormat.PRIVATE;
        }
        return surfaceType;
    }

    /**
     * Detect and retrieve the Surface format without any
     * additional overrides.
     *
     * @param surface The surface to be queried for format.
     * @return format of the surface.
     *
     * @throws IllegalArgumentException if the surface is already abandoned.
     */
    public static int detectSurfaceFormat(Surface surface) {
        checkNotNull(surface);
        int surfaceType = nativeDetectSurfaceType(surface);
        if (surfaceType == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");

        return surfaceType;
    }

    /**
     * Get the Surface dataspace.
     *
     * @param surface The surface to be queried for dataspace.
     * @return dataspace of the surface.
     *
     * @throws IllegalArgumentException if the surface is already abandoned.
     */
    public static int getSurfaceDataspace(Surface surface) {
        checkNotNull(surface);
        int dataSpace = nativeDetectSurfaceDataspace(surface);
        if (dataSpace == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
        return dataSpace;
    }

    /**
     * Return true is the consumer is one of the consumers that can accept
     * producer overrides of the default dimensions and format.
     *
     */
    public static boolean isFlexibleConsumer(Surface output) {
        checkNotNull(output);
        long usageFlags = nativeDetectSurfaceUsageFlags(output);

        // Keep up to date with allowed consumer types in
        // frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
        long disallowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE | USAGE_RENDERSCRIPT;
        long allowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
                | HardwareBuffer.USAGE_CPU_READ_OFTEN
                | USAGE_HW_COMPOSER;
        boolean flexibleConsumer = ((usageFlags & disallowedFlags) == 0
                && (usageFlags & allowedFlags) != 0);
        return flexibleConsumer;
    }


    /**
     * A high speed output surface can only be preview or hardware encoder surface.
     *
     * @param surface The high speed output surface to be checked.
     */
    private static void checkHighSpeedSurfaceFormat(Surface surface) {
        int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface);

        if (surfaceFormat != ImageFormat.PRIVATE) {
            throw new IllegalArgumentException("Surface format(" + surfaceFormat + ") is not"
                    + " for preview or hardware video encoding!");
        }
    }

    /**
     * Verify that that the surfaces are valid for high-speed recording mode,
     * and that the FPS range is supported
     *
     * @param surfaces the surfaces to verify as valid in terms of size and format
     * @param fpsRange the target high-speed FPS range to validate
     * @param config The stream configuration map for the device in question
     */
    public static void checkConstrainedHighSpeedSurfaces(Collection<Surface> surfaces,
            Range<Integer> fpsRange, StreamConfigurationMap config) {
        if (surfaces == null || surfaces.size() == 0 || surfaces.size() > 2) {
            throw new IllegalArgumentException("Output target surface list must not be null and"
                    + " the size must be 1 or 2");
        }

        List<Size> highSpeedSizes = null;
        if (fpsRange == null) {
            highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizes());
        } else {
            // Check the FPS range first if provided
            Range<Integer>[] highSpeedFpsRanges = config.getHighSpeedVideoFpsRanges();
            if(!Arrays.asList(highSpeedFpsRanges).contains(fpsRange)) {
                throw new IllegalArgumentException("Fps range " + fpsRange.toString() + " in the"
                        + " request is not a supported high speed fps range " +
                        Arrays.toString(highSpeedFpsRanges));
            }
            highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizesFor(fpsRange));
        }

        for (Surface surface : surfaces) {
            checkHighSpeedSurfaceFormat(surface);

            // Surface size must be supported high speed sizes.
            Size surfaceSize = SurfaceUtils.getSurfaceSize(surface);
            if (!highSpeedSizes.contains(surfaceSize)) {
                throw new IllegalArgumentException("Surface size " + surfaceSize.toString() + " is"
                        + " not part of the high speed supported size list " +
                        Arrays.toString(highSpeedSizes.toArray()));
            }
            // Each output surface must be either preview surface or recording surface.
            if (!SurfaceUtils.isSurfaceForPreview(surface) &&
                    !SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) {
                throw new IllegalArgumentException("This output surface is neither preview nor "
                        + "hardware video encoding surface");
            }
            if (SurfaceUtils.isSurfaceForPreview(surface) &&
                    SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) {
                throw new IllegalArgumentException("This output surface can not be both preview"
                        + " and hardware video encoding surface");
            }
        }

        // For 2 output surface case, they shouldn't be same type.
        if (surfaces.size() == 2) {
            // Up to here, each surface can only be either preview or recording.
            Iterator<Surface> iterator = surfaces.iterator();
            boolean isFirstSurfacePreview =
                    SurfaceUtils.isSurfaceForPreview(iterator.next());
            boolean isSecondSurfacePreview =
                    SurfaceUtils.isSurfaceForPreview(iterator.next());
            if (isFirstSurfacePreview == isSecondSurfacePreview) {
                throw new IllegalArgumentException("The 2 output surfaces must have different"
                        + " type");
            }
        }
    }

    private static native int nativeDetectSurfaceType(Surface surface);

    private static native int nativeDetectSurfaceDataspace(Surface surface);

    private static native long nativeDetectSurfaceUsageFlags(Surface surface);

    private static native int nativeDetectSurfaceDimens(Surface surface,
            /*out*/int[/*2*/] dimens);

    private static native long nativeGetSurfaceId(Surface surface);
}
