/*
 * Copyright 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 androidx.camera.core;

import android.content.ComponentName;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.google.auto.value.AutoValue;

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

/**
 * Represents the different states the camera can be in.
 *
 * <p>The following table displays the states the camera can be in, and the possible transitions
 * between them.
 *
 * <table>
 * <tr>
 *     <th>State</th>
 *     <th>Transition cause</th>
 *     <th>New State</th>
 * </tr>
 * <tr>
 *     <td rowspan="2">CLOSED</td>
 *     <td>Received signal to open camera, and camera unavailable</td>
 *     <td>PENDING_OPEN</td>
 * </tr>
 * <tr>
 *     <td>Received signal to open camera, and camera available</td>
 *     <td>OPENING</td>
 * </tr>
 * <tr>
 *     <td>PENDING_OPEN</td>
 *     <td>Received signal that camera is available</td>
 *     <td>OPENING</td>
 * </tr>
 * <tr>
 *     <td rowspan="5">OPENING</td>
 *     <td>Camera opened successfully</td>
 *     <td>OPEN</td>
 * </tr>
 * <tr>
 *     <td>Camera encountered recoverable error while opening</td>
 *     <td>OPENING(Error)</td>
 * </tr>
 * <tr>
 *     <td>Camera encountered critical error while opening</td>
 *     <td>CLOSING(Error)</td>
 * </tr>
 * <tr>
 *     <td>Camera opening failed prematurely</td>
 *     <td>CLOSED(Error)</td>
 * </tr>
 * <tr>
 *     <td>Reached max limit of camera (re)open attempts</td>
 *     <td>PENDING_OPEN</td>
 * </tr>
 * <tr>
 *     <td rowspan="3">OPEN</td>
 *     <td>Camera encountered recoverable error</td>
 *     <td>OPENING(Error)</td>
 * </tr>
 * <tr>
 *     <td>Camera encountered critical error</td>
 *     <td>CLOSING(Error)</td>
 * </tr>
 * <tr>
 *     <td>Received signal to close camera</td>
 *     <td>CLOSING</td>
 * </tr>
 * <tr>
 *     <td>CLOSING</td>
 *     <td>Camera closed</td>
 *     <td>CLOSED</td>
 * </tr>
 * </table>
 *
 * <p>Initially, a camera is in a {@link Type#CLOSED} state. When it receives a signal to open, for
 * example after one or multiple {@linkplain UseCase use cases} are attached to it, its state
 * moves to the {@link Type#OPENING} state. If it successfully opens the camera device, its state
 * moves to the {@link Type#OPEN} state, otherwise, it may move to a different state depending on
 * the error it encountered:
 *
 * <ul>
 * <li>If opening the camera device fails prematurely, for example, when "Do Not Disturb" mode is
 * enabled on a device that's affected by a bug in Android 9 (see
 * {@link #ERROR_DO_NOT_DISTURB_MODE_ENABLED}), the state moves to the {@link Type#CLOSED} state
 * .</li>
 * <li>If the error is recoverable, CameraX will attempt to reopen the camera device. If a recovery
 * attempt succeeds, the camera state moves to the {@link Type#OPEN} state, however, if all recovery
 * attempts are unsuccessful, the camera waits in a {@link Type#PENDING_OPEN} state to attempt
 * recovery again once the camera device's availability changes.</li>
 * <li>If the error is critical, and requires the intervention of the developer or user, the
 * camera's state moves to the {@link Type#CLOSING} state.</li>
 * </ul>
 *
 * <p>While in the {@link Type#PENDING_OPEN} state, the camera waits for a signal indicating the
 * camera device's availability. The signal can either be an external one from the camera service,
 * or an internal one from within CameraX. When received, the camera's state moves to the
 * {@link Type#OPENING} state, and an attempt to open the camera device is made.
 *
 * <p>While in the {@link Type#OPEN} state, the camera device may be disconnected due to an error.
 * In this case, depending on whether the error is critical or recoverable, CameraX may or may not
 * attempt to recover from it, thus the state will move to either a {@link Type#CLOSING} or
 * {@link Type#OPENING} state.
 *
 * <p>If the camera is in an {@link Type#OPEN} state and receives a signal to close the camera
 * device, for example when all its previously attached {@link UseCase use cases} are detached,
 * its state moves to the {@link Type#CLOSING} state. Once the camera device finishes closing,
 * the camera state moves to the {@link Type#CLOSED} state.
 *
 * <p>Whenever the camera encounters an error, it reports it through {@link #getError()}.
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@AutoValue
public abstract class CameraState {

    /**
     * An error indicating that the limit number of open cameras has been reached, and more
     * cameras cannot be opened until other instances are closed.
     */
    @SuppressWarnings("MinMaxConstant")
    public static final int ERROR_MAX_CAMERAS_IN_USE = 1;

    /**
     * An error indicating that the camera device is already in use.
     *
     * <p>This could be due to the camera device being used by a higher-priority camera client.
     */
    public static final int ERROR_CAMERA_IN_USE = 2;

    /**
     * An error indicating that the camera device has encountered a recoverable error.
     *
     * <p>CameraX will attempt to recover from the error, it if succeeds in doing so, the
     * camera will open, otherwise the camera will move to a {@link Type#PENDING_OPEN} state.
     *
     * When CameraX uses a {@link android.hardware.camera2} implementation, this error represents
     * a {@link android.hardware.camera2.CameraDevice.StateCallback#ERROR_CAMERA_DEVICE} error.
     */
    public static final int ERROR_OTHER_RECOVERABLE_ERROR = 3;

    /** An error indicating that configuring the camera has failed. */
    public static final int ERROR_STREAM_CONFIG = 4;

    /**
     * An error indicating that the camera device could not be opened due to a device policy.
     *
     * <p>The error may be encountered if a client from a background process attempts to open the
     * camera.
     *
     * @see android.app.admin.DevicePolicyManager#setCameraDisabled(ComponentName, boolean)
     */
    public static final int ERROR_CAMERA_DISABLED = 5;

    /**
     * An error indicating that the camera device was closed due to a fatal error.
     *
     * <p>The error may require the Android device to be shut down and restarted to restore camera
     * function. It may also indicate the existence of a persistent camera hardware problem.
     *
     * When CameraX uses a {@link android.hardware.camera2} implementation, this error represents
     * a {@link android.hardware.camera2.CameraDevice.StateCallback#ERROR_CAMERA_SERVICE} error.
     */
    public static final int ERROR_CAMERA_FATAL_ERROR = 6;

    /**
     * An error indicating that the camera could not be opened because "Do Not Disturb" mode is
     * enabled on devices affected by a bug in Android 9 (API level 28).
     *
     * <p>When "Do Not Disturb" mode is enabled, opening the camera device fails on certain
     * Android devices running on an early Android 9 release with a
     * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY}
     * camera hardware level.
     *
     * <p>CameraX will not attempt to reopen the camera device, instead, disable the "Do Not
     * Disturb" mode, then explicitly open the camera again.
     */
    public static final int ERROR_DO_NOT_DISTURB_MODE_ENABLED = 7;

    /**
     * Create a new {@link CameraState} instance from a {@link Type} and a {@code null}
     * {@link StateError}.
     *
     * <p>A {@link CameraState} is not expected to be instantiated in normal operation.
     */
    @NonNull
    public static CameraState create(@NonNull Type type) {
        return create(type, null);
    }

    /**
     * Create a new {@link CameraState} instance from a {@link Type} and a potential
     * {@link StateError}.
     *
     * <p>A {@link CameraState} is not expected to be instantiated in normal operation.
     */
    @NonNull
    public static CameraState create(@NonNull Type type, @Nullable StateError error) {
        return new AutoValue_CameraState(type, error);
    }

    /**
     * Returns the camera's state.
     *
     * @return The camera's state
     */
    @NonNull
    public abstract Type getType();

    /**
     * Potentially returns an error the camera encountered.
     *
     * @return An error the camera encountered, or {@code null} otherwise.
     */
    @Nullable
    public abstract StateError getError();

    @IntDef(value = {
            ERROR_CAMERA_IN_USE,
            ERROR_MAX_CAMERAS_IN_USE,
            ERROR_OTHER_RECOVERABLE_ERROR,
            ERROR_STREAM_CONFIG,
            ERROR_CAMERA_DISABLED,
            ERROR_CAMERA_FATAL_ERROR,
            ERROR_DO_NOT_DISTURB_MODE_ENABLED})
    @Retention(RetentionPolicy.SOURCE)
    @interface ErrorCode {
    }

    /**
     * Types of errors the camera can encounter.
     *
     * <p>CameraX tries to recover from recoverable errors, which include
     * {@link #ERROR_CAMERA_IN_USE}, {@link #ERROR_MAX_CAMERAS_IN_USE} and
     * {@link #ERROR_OTHER_RECOVERABLE_ERROR}. The rest of the errors are critical, and require
     * the intervention of the developer or user to restore camera function. These errors include
     * {@link #ERROR_STREAM_CONFIG}, {@link #ERROR_CAMERA_DISABLED},
     * {@link #ERROR_CAMERA_FATAL_ERROR} and {@link #ERROR_DO_NOT_DISTURB_MODE_ENABLED}.
     */
    public enum ErrorType {
        /**
         * An error the camera encountered that CameraX will attempt to recover from.
         *
         * <p>Recoverable errors include {@link #ERROR_CAMERA_IN_USE},
         * {@link #ERROR_MAX_CAMERAS_IN_USE} and {@link #ERROR_OTHER_RECOVERABLE_ERROR}.
         */
        RECOVERABLE,

        /**
         * An error the camera encountered that CameraX will not attempt to recover from.
         *
         * <p>A critical error is one that requires the intervention of the developer or user to
         * restore camera function, and includes {@link #ERROR_STREAM_CONFIG},
         * {@link #ERROR_CAMERA_DISABLED}, {@link #ERROR_CAMERA_FATAL_ERROR} and
         * {@link #ERROR_DO_NOT_DISTURB_MODE_ENABLED}.
         */
        CRITICAL
    }

    /** States the camera can be in. */
    public enum Type {
        /**
         * Represents a state where the camera is waiting for a signal to attempt to open the camera
         * device.
         *
         * <p>The camera can move to this state from a {@link Type#CLOSED} or {@link Type#OPENING}
         * state:
         * <ul>
         * <li>It moves to this state from a {@link Type#CLOSED} state if it attempts to open an
         * unavailable camera device. A camera device is unavailable for opening if (a) it's
         * already in use by another camera client, for example one with higher priority or (b)
         * the maximum number of cameras allowed to be open at the same time in CameraX has been
         * reached, this limit is currently set to 1.</li>
         * <li>It moves to this state from an {@link Type#OPENING} state if it reaches the
         * maximum number of camera reopen attempts while trying to recover from a camera opening
         * error.</li>
         * </ul>
         *
         * <p>While in this state, the camera waits for an external signal from the camera
         * service or an internal one from CameraX to attempt to reopen the camera device.
         *
         * <p>Developers may rely on this state to close any other open cameras in the app, or
         * request their user close an open camera in another app.
         */
        PENDING_OPEN,

        /**
         * Represents a state where the camera device is currently opening.
         *
         * <p>The camera can move to this state from a {@link Type#PENDING_OPEN} or
         * {@link Type#CLOSED} state: It moves to this state from a {@link Type#PENDING_OPEN}
         * state after it receives a signal that the camera is available to open, and from a
         * {@link Type#CLOSED} state after a request to open the camera is made, and the camera
         * is available to open.
         *
         * <p>While in this state, the camera is actively attempting to open the camera device.
         * This takes several hundred milliseconds on most devices. If it succeeds, the state
         * moves to the {@link Type#OPEN} state. If it fails however, the camera may attempt to
         * reopen the camera device a certain number of times. While this is happening, the
         * camera state remains the same, i.e. in an opening state, and it exposes the error it
         * encountered through {@link #getError()}.
         *
         * <p>Developers can rely on this state to be aware of when the camera is actively
         * attempting to open the camera device, this allows them to communicate it to their
         * users through the UI.
         */
        OPENING,

        /**
         * Represents a state where the camera device is open.
         *
         * <p>The camera can only move to this state from an {@link Type#OPENING} state.
         *
         * <p>Once in this state, active {@linkplain UseCase use cases} attached to this camera
         * could expect to shortly start receiving camera frames.
         *
         * <p>Developers can rely on this state to be notified of when the camera device is actually
         * ready for use, and can then set up camera dependent resources, especially if they're
         * heavyweight.
         */
        OPEN,

        /**
         * Represents a state where the camera device is currently closing.
         *
         * <p>The camera can move to this state from an {@link Type#OPEN} or {@link Type#OPENING}
         * state: It moves to this state from an {@link Type#OPEN} state after it receives a
         * signal to close the camera device, this can be after all its attached
         * {@linkplain UseCase use cases} are detached, and from an {@link Type#OPENING} state if
         * the camera encounters a fatal error it cannot recover from.
         *
         * <p>Developers can rely on this state to be aware of when the camera device is actually
         * in the process of closing. this allows them to communicate it to their users through
         * the UI.
         */
        CLOSING,

        /**
         * Represents a state where the camera device is closed.
         *
         * <p>The camera is initially in this state, and can move back to it from a
         * {@link Type#CLOSING} or {@link Type#OPENING} state: It moves to this state from a
         * {@link Type#CLOSING} state after the camera device successfully closes, and from an
         * {@link Type#OPENING} state when opening a camera device that is unavailable due to
         * {@link android.app.NotificationManager.Policy}, as some API level 28 devices cannot
         * access the camera when the device is in "Do Not Disturb" mode.
         *
         * <p>Developers can rely on this state to be notified of when the camera device is actually
         * closed, and then use this signal to free up camera resources, or start the camera device
         * with another camera client.
         */
        CLOSED
    }

    /**
     * Error that the camera has encountered.
     *
     * <p>The camera may report an error when it's in one of the following states:
     * {@link Type#OPENING}, {@link Type#OPEN}, {@link Type#CLOSING} and {@link Type#CLOSED}.
     *
     * <p>CameraX attempts to recover from certain errors it encounters when opening the camera
     * device, in these instances, the error is {@linkplain ErrorType#RECOVERABLE recoverable},
     * otherwise, the error is {@linkplain ErrorType#CRITICAL critical}.
     *
     * <p>When CameraX encounters a critical error, the developer and/or user must intervene to
     * restore camera function. When the error is recoverable, the developer and/or user can
     * still aid in the recovery process, as shown in the following table.
     * <table>
     * <tr>
     *     <th>State</th>
     *     <th>Error Code</th>
     *     <th>Recoverable</th>
     *     <th>How to handle it</th>
     * </tr>
     * <tr>
     *     <td>{@link Type#OPEN}</td>
     *     <td>{@linkplain #ERROR_STREAM_CONFIG ERROR_STREAM_CONFIG}</td>
     *     <td>No</td>
     *     <td>Make sure you set up your {@linkplain UseCase use cases} correctly.</td>
     * </tr>
     * <tr>
     *     <td>{@link Type#OPENING}</td>
     *     <td>{@linkplain #ERROR_CAMERA_IN_USE ERROR_CAMERA_IN_USE}</td>
     *     <td>Yes</td>
     *     <td>Close the camera, or ask the user to close another camera app that is using the
     *     camera.</td>
     * </tr>
     * <tr>
     *     <td>{@link Type#OPENING}</td>
     *     <td>{@linkplain #ERROR_MAX_CAMERAS_IN_USE ERROR_MAX_CAMERAS_IN_USE}</td>
     *     <td>Yes</td>
     *     <td>Close another open camera in the app, or ask the user to close another camera
     *     app that's using the camera.</td>
     * </tr>
     * <tr>
     *     <td>{@link Type#OPENING}</td>
     *     <td>{@linkplain #ERROR_OTHER_RECOVERABLE_ERROR ERROR_OTHER_RECOVERABLE_ERROR}</td>
     *     <td>Yes</td>
     *     <td>N/A</td>
     * </tr>
     * <tr>
     *     <td>{@link Type#CLOSING}</td>
     *     <td>{@linkplain #ERROR_CAMERA_DISABLED ERROR_CAMERA_DISABLED}</td>
     *     <td>No</td>
     *     <td>Ask the user to enable the device's cameras.</td>
     * </tr>
     * <tr>
     *     <td>{@link Type#CLOSING}</td>
     *     <td>{@linkplain #ERROR_CAMERA_FATAL_ERROR ERROR_CAMERA_FATAL_ERROR}</td>
     *     <td>No</td>
     *     <td>Ask the user to reboot the device to restore camera function.</td>
     * </tr>
     * <tr>
     *     <td>{@link Type#CLOSED}</td>
     *     <td>{@linkplain #ERROR_DO_NOT_DISTURB_MODE_ENABLED
     *     ERROR_DO_NOT_DISTURB_MODE_ENABLED}</td>
     *     <td>No</td>
     *     <td>Ask the user to disable "Do Not Disturb" mode, then open the camera again.</td>
     * </tr>
     * </table>
     */
    @AutoValue
    public abstract static class StateError {

        /**
         * Creates a {@link StateError} with an error code.
         *
         * <p>A {@link StateError} is not expected to be instantiated in normal operation.
         */
        @NonNull
        public static StateError create(@ErrorCode int error) {
            return create(error, null);
        }

        /**
         * Creates a {@link StateError} with an error code and a {@linkplain Throwable cause}.
         *
         * <p>A {@link StateError} is not expected to be instantiated in normal operation.
         */
        @NonNull
        public static StateError create(@ErrorCode int error, @Nullable Throwable cause) {
            return new AutoValue_CameraState_StateError(error, cause);
        }

        /**
         * Returns the code of this error.
         *
         * <p>The error's code is one of the following: {@link #ERROR_CAMERA_IN_USE},
         * {@link #ERROR_MAX_CAMERAS_IN_USE}, {@link #ERROR_OTHER_RECOVERABLE_ERROR},
         * {@link #ERROR_STREAM_CONFIG}, {@link #ERROR_CAMERA_DISABLED},
         * {@link #ERROR_CAMERA_FATAL_ERROR} and {@link #ERROR_DO_NOT_DISTURB_MODE_ENABLED}.
         *
         * @return The code of this error.
         */
        @ErrorCode
        public abstract int getCode();

        /**
         * Returns a potential cause of this error.
         *
         * @return The cause of this error, or {@code null} if the cause was not supplied.
         */
        @Nullable
        public abstract Throwable getCause();

        /**
         * Returns the type of this error.
         *
         * <p>An error can either be {@linkplain ErrorType#RECOVERABLE recoverable} or
         * {@linkplain ErrorType#CRITICAL critical}.
         *
         * @return The type of this error
         */
        @NonNull
        public ErrorType getType() {
            int code = getCode();
            if (code == ERROR_CAMERA_IN_USE || code == ERROR_MAX_CAMERAS_IN_USE
                    || code == ERROR_OTHER_RECOVERABLE_ERROR) {
                return ErrorType.RECOVERABLE;
            }
            return ErrorType.CRITICAL;
        }
    }
}
