/*
 * Copyright (C) 2024 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.media.metrics;

import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;

import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.hardware.DataSpace;
import android.media.MediaCodec;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Size;

import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/** Represents information about a piece of media (for example, an audio or video file). */
@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
public final class MediaItemInfo implements Parcelable {

    /** @hide */
    @IntDef(
            prefix = {"SOURCE_TYPE_"},
            value = {
                SOURCE_TYPE_UNSPECIFIED,
                SOURCE_TYPE_GALLERY,
                SOURCE_TYPE_CAMERA,
                SOURCE_TYPE_EDITING_SESSION,
                SOURCE_TYPE_LOCAL_FILE,
                SOURCE_TYPE_REMOTE_FILE,
                SOURCE_TYPE_REMOTE_LIVE_STREAM,
                SOURCE_TYPE_GENERATED,
            })
    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
    public @interface SourceType {}

    /** The media item's source is not known. */
    public static final int SOURCE_TYPE_UNSPECIFIED = 0;

    /** The media item came from the device gallery. */
    public static final int SOURCE_TYPE_GALLERY = 1;

    /** The media item came directly from camera capture. */
    public static final int SOURCE_TYPE_CAMERA = 2;

    /** The media item was output by a previous editing session. */
    public static final int SOURCE_TYPE_EDITING_SESSION = 3;

    /** The media item is stored on the local device's file system. */
    public static final int SOURCE_TYPE_LOCAL_FILE = 4;

    /** The media item is a remote file (for example, it's loaded from an HTTP server). */
    public static final int SOURCE_TYPE_REMOTE_FILE = 5;

    /** The media item is a remotely-served live stream. */
    public static final int SOURCE_TYPE_REMOTE_LIVE_STREAM = 6;

    /** The media item was generated by another system. */
    public static final int SOURCE_TYPE_GENERATED = 7;

    /** @hide */
    @LongDef(
            prefix = {"DATA_TYPE_"},
            flag = true,
            value = {
                DATA_TYPE_IMAGE,
                DATA_TYPE_VIDEO,
                DATA_TYPE_AUDIO,
                DATA_TYPE_METADATA,
                DATA_TYPE_DEPTH,
                DATA_TYPE_GAIN_MAP,
                DATA_TYPE_HIGH_FRAME_RATE,
                DATA_TYPE_SPEED_SETTING_CUE_POINTS,
                DATA_TYPE_GAPLESS,
                DATA_TYPE_SPATIAL_AUDIO,
                DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO,
            })
    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
    public @interface DataType {}

    /** The media item includes image data. */
    public static final long DATA_TYPE_IMAGE = 1L;

    /** The media item includes video data. */
    public static final long DATA_TYPE_VIDEO = 1L << 1;

    /** The media item includes audio data. */
    public static final long DATA_TYPE_AUDIO = 1L << 2;

    /**
     * The media item includes static media container metadata (for example, capture frame rate or
     * location information).
     */
    public static final long DATA_TYPE_METADATA = 1L << 3;

    /** The media item includes depth (z-distance) information. */
    public static final long DATA_TYPE_DEPTH = 1L << 4;

    /** The media item includes gain map information (for example, an Ultra HDR gain map). */
    public static final long DATA_TYPE_GAIN_MAP = 1L << 5;

    /** The media item includes high frame rate video data. */
    public static final long DATA_TYPE_HIGH_FRAME_RATE = 1L << 6;

    /**
     * The media item includes time-dependent speed information (for example, slow motion cue
     * points).
     */
    public static final long DATA_TYPE_SPEED_SETTING_CUE_POINTS = 1L << 7;

    /** The media item includes gapless audio metadata. */
    public static final long DATA_TYPE_GAPLESS = 1L << 8;

    /** The media item includes spatial audio data. */
    public static final long DATA_TYPE_SPATIAL_AUDIO = 1L << 9;

    /** The media item includes high dynamic range (HDR) video. */
    public static final long DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO = 1L << 10;

    /** Special value for numerical fields where the value was not specified. */
    public static final int VALUE_UNSPECIFIED = -1;

    private final @SourceType int mSourceType;
    private final @DataType long mDataTypes;
    private final long mDurationMillis;
    private final long mClipDurationMillis;
    @Nullable private final String mContainerMimeType;
    private final List<String> mSampleMimeTypes;
    private final List<String> mCodecNames;
    private final int mAudioSampleRateHz;
    private final int mAudioChannelCount;
    private final long mAudioSampleCount;
    private final Size mVideoSize;
    private final int mVideoDataSpace;
    private final float mVideoFrameRate;
    private final long mVideoSampleCount;

    private MediaItemInfo(
            @SourceType int sourceType,
            @DataType long dataTypes,
            long durationMillis,
            long clipDurationMillis,
            @Nullable String containerMimeType,
            List<String> sampleMimeTypes,
            List<String> codecNames,
            int audioSampleRateHz,
            int audioChannelCount,
            long audioSampleCount,
            Size videoSize,
            int videoDataSpace,
            float videoFrameRate,
            long videoSampleCount) {
        mSourceType = sourceType;
        mDataTypes = dataTypes;
        mDurationMillis = durationMillis;
        mClipDurationMillis = clipDurationMillis;
        mContainerMimeType = containerMimeType;
        mSampleMimeTypes = sampleMimeTypes;
        mCodecNames = codecNames;
        mAudioSampleRateHz = audioSampleRateHz;
        mAudioChannelCount = audioChannelCount;
        mAudioSampleCount = audioSampleCount;
        mVideoSize = videoSize;
        mVideoDataSpace = videoDataSpace;
        mVideoFrameRate = videoFrameRate;
        mVideoSampleCount = videoSampleCount;
    }

    /**
     * Returns where the media item came from, or {@link #SOURCE_TYPE_UNSPECIFIED} if not specified.
     */
    public @SourceType int getSourceType() {
        return mSourceType;
    }

    /** Returns the data types that are present in the media item. */
    public @DataType long getDataTypes() {
        return mDataTypes;
    }

    /**
     * Returns the duration of the media item, in milliseconds, or {@link #VALUE_UNSPECIFIED} if not
     * specified.
     */
    public long getDurationMillis() {
        return mDurationMillis;
    }

    /**
     * Returns the duration of the clip taken from the media item, in milliseconds, or {@link
     * #VALUE_UNSPECIFIED} if not specified.
     */
    public long getClipDurationMillis() {
        return mClipDurationMillis;
    }

    /** Returns the MIME type of the media container, or {@code null} if unspecified. */
    @Nullable
    public String getContainerMimeType() {
        return mContainerMimeType;
    }

    /**
     * Returns the MIME types of samples stored in the media container, or an empty list if not
     * known.
     */
    @NonNull
    public List<String> getSampleMimeTypes() {
        return new ArrayList<>(mSampleMimeTypes);
    }

    /**
     * Returns the {@linkplain MediaCodec#getName() media codec names} for codecs that were used as
     * part of encoding/decoding this media item, or an empty list if not known or not applicable.
     */
    @NonNull
    public List<String> getCodecNames() {
        return new ArrayList<>(mCodecNames);
    }

    /**
     * Returns the sample rate of audio, in Hertz, or {@link #VALUE_UNSPECIFIED} if not specified.
     */
    public int getAudioSampleRateHz() {
        return mAudioSampleRateHz;
    }

    /** Returns the number of audio channels, or {@link #VALUE_UNSPECIFIED} if not specified. */
    public int getAudioChannelCount() {
        return mAudioChannelCount;
    }

    /**
     * Returns the number of audio frames in the item, after clipping (if applicable), or {@link
     * #VALUE_UNSPECIFIED} if not specified.
     */
    public long getAudioSampleCount() {
        return mAudioSampleCount;
    }

    /**
     * Returns the video size, in pixels, or a {@link Size} with width and height set to {@link
     * #VALUE_UNSPECIFIED} if not specified.
     */
    @NonNull
    public Size getVideoSize() {
        return mVideoSize;
    }

    /** Returns the {@linkplain DataSpace data space} for video, as a packed integer. */
    @SuppressLint("MethodNameUnits") // Packed integer for an android.hardware.DataSpace.
    public int getVideoDataSpace() {
        return mVideoDataSpace;
    }

    /**
     * Returns the average video frame rate, in frames per second, or {@link #VALUE_UNSPECIFIED} if
     * not specified.
     */
    public float getVideoFrameRate() {
        return mVideoFrameRate;
    }

    /**
     * Returns the number of video frames, aftrer clipping (if applicable), or {@link
     * #VALUE_UNSPECIFIED} if not specified.
     */
    public long getVideoSampleCount() {
        return mVideoSampleCount;
    }

    /** Builder for {@link MediaItemInfo}. */
    @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
    public static final class Builder {

        private @SourceType int mSourceType;
        private @DataType long mDataTypes;
        private long mDurationMillis;
        private long mClipDurationMillis;
        @Nullable private String mContainerMimeType;
        private final ArrayList<String> mSampleMimeTypes;
        private final ArrayList<String> mCodecNames;
        private int mAudioSampleRateHz;
        private int mAudioChannelCount;
        private long mAudioSampleCount;
        @Nullable private Size mVideoSize;
        private int mVideoDataSpace;
        private float mVideoFrameRate;
        private long mVideoSampleCount;

        /** Creates a new builder. */
        public Builder() {
            mSourceType = SOURCE_TYPE_UNSPECIFIED;
            mDurationMillis = VALUE_UNSPECIFIED;
            mClipDurationMillis = VALUE_UNSPECIFIED;
            mSampleMimeTypes = new ArrayList<>();
            mCodecNames = new ArrayList<>();
            mAudioSampleRateHz = VALUE_UNSPECIFIED;
            mAudioChannelCount = VALUE_UNSPECIFIED;
            mAudioSampleCount = VALUE_UNSPECIFIED;
            mVideoSize = new Size(VALUE_UNSPECIFIED, VALUE_UNSPECIFIED);
            mVideoFrameRate = VALUE_UNSPECIFIED;
            mVideoSampleCount = VALUE_UNSPECIFIED;
        }

        /** Sets where the media item came from. */
        public @NonNull Builder setSourceType(@SourceType int sourceType) {
            mSourceType = sourceType;
            return this;
        }

        /** Adds an additional data type represented as part of the media item. */
        public @NonNull Builder addDataType(@DataType long dataType) {
            mDataTypes |= dataType;
            return this;
        }

        /** Sets the duration of the media item, in milliseconds. */
        public @NonNull Builder setDurationMillis(long durationMillis) {
            mDurationMillis = durationMillis;
            return this;
        }

        /** Sets the duration of the clip taken from the media item, in milliseconds. */
        public @NonNull Builder setClipDurationMillis(long clipDurationMillis) {
            mClipDurationMillis = clipDurationMillis;
            return this;
        }

        /** Sets the MIME type of the media container. */
        public @NonNull Builder setContainerMimeType(@NonNull String containerMimeType) {
            mContainerMimeType = Objects.requireNonNull(containerMimeType);
            return this;
        }

        /** Adds a sample MIME type stored in the media container. */
        public @NonNull Builder addSampleMimeType(@NonNull String mimeType) {
            mSampleMimeTypes.add(Objects.requireNonNull(mimeType));
            return this;
        }

        /**
         * Adds an {@linkplain MediaCodec#getName() media codec name} that was used as part of
         * decoding/encoding this media item.
         */
        public @NonNull Builder addCodecName(@NonNull String codecName) {
            mCodecNames.add(Objects.requireNonNull(codecName));
            return this;
        }

        /** Sets the sample rate of audio, in Hertz. */
        public @NonNull Builder setAudioSampleRateHz(@IntRange(from = 0) int audioSampleRateHz) {
            mAudioSampleRateHz = audioSampleRateHz;
            return this;
        }

        /** Sets the number of audio channels. */
        public @NonNull Builder setAudioChannelCount(@IntRange(from = 0) int audioChannelCount) {
            mAudioChannelCount = audioChannelCount;
            return this;
        }

        /** Sets the number of audio frames in the item, after clipping (if applicable). */
        public @NonNull Builder setAudioSampleCount(@IntRange(from = 0) long audioSampleCount) {
            mAudioSampleCount = audioSampleCount;
            return this;
        }

        /** Sets the video size, in pixels. */
        public @NonNull Builder setVideoSize(@NonNull Size videoSize) {
            mVideoSize = Objects.requireNonNull(videoSize);
            return this;
        }

        /**
         * Sets the {@link DataSpace} of video frames.
         *
         * @param videoDataSpace The data space, returned by {@link DataSpace#pack(int, int, int)}.
         */
        public @NonNull Builder setVideoDataSpace(int videoDataSpace) {
            mVideoDataSpace = videoDataSpace;
            return this;
        }

        /** Sets the average video frame rate, in frames per second. */
        public @NonNull Builder setVideoFrameRate(@FloatRange(from = 0) float videoFrameRate) {
            mVideoFrameRate = videoFrameRate;
            return this;
        }

        /** Sets the number of video frames, after clipping (if applicable). */
        public @NonNull Builder setVideoSampleCount(@IntRange(from = 0) long videoSampleCount) {
            mVideoSampleCount = videoSampleCount;
            return this;
        }

        /** Builds an instance. */
        @NonNull
        public MediaItemInfo build() {
            return new MediaItemInfo(
                    mSourceType,
                    mDataTypes,
                    mDurationMillis,
                    mClipDurationMillis,
                    mContainerMimeType,
                    mSampleMimeTypes,
                    mCodecNames,
                    mAudioSampleRateHz,
                    mAudioChannelCount,
                    mAudioSampleCount,
                    mVideoSize,
                    mVideoDataSpace,
                    mVideoFrameRate,
                    mVideoSampleCount);
        }
    }

    @Override
    @NonNull
    public String toString() {
        return "MediaItemInfo { "
                + "sourceType = "
                + mSourceType
                + ", "
                + "dataTypes = "
                + mDataTypes
                + ", "
                + "durationMillis = "
                + mDurationMillis
                + ", "
                + "clipDurationMillis = "
                + mClipDurationMillis
                + ", "
                + "containerMimeType = "
                + mContainerMimeType
                + ", "
                + "sampleMimeTypes = "
                + mSampleMimeTypes
                + ", "
                + "codecNames = "
                + mCodecNames
                + ", "
                + "audioSampleRateHz = "
                + mAudioSampleRateHz
                + ", "
                + "audioChannelCount = "
                + mAudioChannelCount
                + ", "
                + "audioSampleCount = "
                + mAudioSampleCount
                + ", "
                + "videoSize = "
                + mVideoSize
                + ", "
                + "videoDataSpace = "
                + mVideoDataSpace
                + ", "
                + "videoFrameRate = "
                + mVideoFrameRate
                + ", "
                + "videoSampleCount = "
                + mVideoSampleCount
                + " }";
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MediaItemInfo that = (MediaItemInfo) o;
        return mSourceType == that.mSourceType
                && mDataTypes == that.mDataTypes
                && mDurationMillis == that.mDurationMillis
                && mClipDurationMillis == that.mClipDurationMillis
                && Objects.equals(mContainerMimeType, that.mContainerMimeType)
                && mSampleMimeTypes.equals(that.mSampleMimeTypes)
                && mCodecNames.equals(that.mCodecNames)
                && mAudioSampleRateHz == that.mAudioSampleRateHz
                && mAudioChannelCount == that.mAudioChannelCount
                && mAudioSampleCount == that.mAudioSampleCount
                && Objects.equals(mVideoSize, that.mVideoSize)
                && Objects.equals(mVideoDataSpace, that.mVideoDataSpace)
                && mVideoFrameRate == that.mVideoFrameRate
                && mVideoSampleCount == that.mVideoSampleCount;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mSourceType, mDataTypes);
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mSourceType);
        dest.writeLong(mDataTypes);
        dest.writeLong(mDurationMillis);
        dest.writeLong(mClipDurationMillis);
        dest.writeString(mContainerMimeType);
        dest.writeStringList(mSampleMimeTypes);
        dest.writeStringList(mCodecNames);
        dest.writeInt(mAudioSampleRateHz);
        dest.writeInt(mAudioChannelCount);
        dest.writeLong(mAudioSampleCount);
        dest.writeInt(mVideoSize.getWidth());
        dest.writeInt(mVideoSize.getHeight());
        dest.writeInt(mVideoDataSpace);
        dest.writeFloat(mVideoFrameRate);
        dest.writeLong(mVideoSampleCount);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    private MediaItemInfo(@NonNull Parcel in) {
        mSourceType = in.readInt();
        mDataTypes = in.readLong();
        mDurationMillis = in.readLong();
        mClipDurationMillis = in.readLong();
        mContainerMimeType = in.readString();
        mSampleMimeTypes = new ArrayList<>();
        in.readStringList(mSampleMimeTypes);
        mCodecNames = new ArrayList<>();
        in.readStringList(mCodecNames);
        mAudioSampleRateHz = in.readInt();
        mAudioChannelCount = in.readInt();
        mAudioSampleCount = in.readLong();
        int videoSizeWidth = in.readInt();
        int videoSizeHeight = in.readInt();
        mVideoSize = new Size(videoSizeWidth, videoSizeHeight);
        mVideoDataSpace = in.readInt();
        mVideoFrameRate = in.readFloat();
        mVideoSampleCount = in.readLong();
    }

    public static final @NonNull Creator<MediaItemInfo> CREATOR =
            new Creator<>() {
                @Override
                public MediaItemInfo[] newArray(int size) {
                    return new MediaItemInfo[size];
                }

                @Override
                public MediaItemInfo createFromParcel(@NonNull Parcel in) {
                    return new MediaItemInfo(in);
                }
            };
}
