/*
 * Copyright 2020 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.media3.common;

import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;

/**
 * Identifies a specific playback of a {@link Timeline.Period}.
 *
 * <p>A {@link Timeline.Period} can be played multiple times, for example if it is repeated. Each
 * instances of this class identifies a specific playback of a {@link Timeline.Period}.
 *
 * <p>In ExoPlayer's implementation, {@link MediaPeriodId} identifies a {@code MediaPeriod}.
 */
// TODO(b/172315872) Should be final, but subclassed in MediaSource for backward-compatibility.
@UnstableApi
public class MediaPeriodId {

  /** The unique id of the timeline period. */
  public final Object periodUid;

  /**
   * If the media period is in an ad group, the index of the ad group in the period. {@link
   * C#INDEX_UNSET} otherwise.
   */
  public final int adGroupIndex;

  /**
   * If the media period is in an ad group, the index of the ad in its ad group in the period.
   * {@link C#INDEX_UNSET} otherwise.
   */
  public final int adIndexInAdGroup;

  /**
   * The sequence number of the window in the buffered sequence of windows this media period is part
   * of. {@link C#INDEX_UNSET} if the media period id is not part of a buffered sequence of windows.
   */
  public final long windowSequenceNumber;

  /**
   * The index of the next ad group to which the media period's content is clipped, or {@link
   * C#INDEX_UNSET} if there is no following ad group or if this media period is an ad.
   */
  public final int nextAdGroupIndex;

  /**
   * Creates a media period identifier for a period which is not part of a buffered sequence of
   * windows.
   *
   * @param periodUid The unique id of the timeline period.
   */
  public MediaPeriodId(Object periodUid) {
    this(periodUid, /* windowSequenceNumber= */ C.INDEX_UNSET);
  }

  /**
   * Creates a media period identifier for the specified period in the timeline.
   *
   * @param periodUid The unique id of the timeline period.
   * @param windowSequenceNumber The sequence number of the window in the buffered sequence of
   *     windows this media period is part of.
   */
  public MediaPeriodId(Object periodUid, long windowSequenceNumber) {
    this(
        periodUid,
        /* adGroupIndex= */ C.INDEX_UNSET,
        /* adIndexInAdGroup= */ C.INDEX_UNSET,
        windowSequenceNumber,
        /* nextAdGroupIndex= */ C.INDEX_UNSET);
  }

  /**
   * Creates a media period identifier for the specified clipped period in the timeline.
   *
   * @param periodUid The unique id of the timeline period.
   * @param windowSequenceNumber The sequence number of the window in the buffered sequence of
   *     windows this media period is part of.
   * @param nextAdGroupIndex The index of the next ad group to which the media period's content is
   *     clipped.
   */
  public MediaPeriodId(Object periodUid, long windowSequenceNumber, int nextAdGroupIndex) {
    this(
        periodUid,
        /* adGroupIndex= */ C.INDEX_UNSET,
        /* adIndexInAdGroup= */ C.INDEX_UNSET,
        windowSequenceNumber,
        nextAdGroupIndex);
  }

  /**
   * Creates a media period identifier that identifies an ad within an ad group at the specified
   * timeline period.
   *
   * @param periodUid The unique id of the timeline period that contains the ad group.
   * @param adGroupIndex The index of the ad group.
   * @param adIndexInAdGroup The index of the ad in the ad group.
   * @param windowSequenceNumber The sequence number of the window in the buffered sequence of
   *     windows this media period is part of.
   */
  public MediaPeriodId(
      Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) {
    this(
        periodUid,
        adGroupIndex,
        adIndexInAdGroup,
        windowSequenceNumber,
        /* nextAdGroupIndex= */ C.INDEX_UNSET);
  }

  /** Copy constructor for inheritance. */
  // TODO(b/172315872) Delete when client have migrated from MediaSource.MediaPeriodId
  protected MediaPeriodId(MediaPeriodId mediaPeriodId) {
    this.periodUid = mediaPeriodId.periodUid;
    this.adGroupIndex = mediaPeriodId.adGroupIndex;
    this.adIndexInAdGroup = mediaPeriodId.adIndexInAdGroup;
    this.windowSequenceNumber = mediaPeriodId.windowSequenceNumber;
    this.nextAdGroupIndex = mediaPeriodId.nextAdGroupIndex;
  }

  private MediaPeriodId(
      Object periodUid,
      int adGroupIndex,
      int adIndexInAdGroup,
      long windowSequenceNumber,
      int nextAdGroupIndex) {
    this.periodUid = periodUid;
    this.adGroupIndex = adGroupIndex;
    this.adIndexInAdGroup = adIndexInAdGroup;
    this.windowSequenceNumber = windowSequenceNumber;
    this.nextAdGroupIndex = nextAdGroupIndex;
  }

  /** Returns a copy of this period identifier but with {@code newPeriodUid} as its period uid. */
  public MediaPeriodId copyWithPeriodUid(Object newPeriodUid) {
    return periodUid.equals(newPeriodUid)
        ? this
        : new MediaPeriodId(
            newPeriodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, nextAdGroupIndex);
  }

  /** Returns a copy of this period identifier with a new {@code windowSequenceNumber}. */
  public MediaPeriodId copyWithWindowSequenceNumber(long windowSequenceNumber) {
    return this.windowSequenceNumber == windowSequenceNumber
        ? this
        : new MediaPeriodId(
            periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, nextAdGroupIndex);
  }

  /** Returns whether this period identifier identifies an ad in an ad group in a period. */
  public boolean isAd() {
    return adGroupIndex != C.INDEX_UNSET;
  }

  @Override
  public boolean equals(@Nullable Object obj) {
    if (this == obj) {
      return true;
    }
    if (!(obj instanceof MediaPeriodId)) {
      return false;
    }

    MediaPeriodId periodId = (MediaPeriodId) obj;
    return periodUid.equals(periodId.periodUid)
        && adGroupIndex == periodId.adGroupIndex
        && adIndexInAdGroup == periodId.adIndexInAdGroup
        && windowSequenceNumber == periodId.windowSequenceNumber
        && nextAdGroupIndex == periodId.nextAdGroupIndex;
  }

  @Override
  public int hashCode() {
    int result = 17;
    result = 31 * result + periodUid.hashCode();
    result = 31 * result + adGroupIndex;
    result = 31 * result + adIndexInAdGroup;
    result = 31 * result + (int) windowSequenceNumber;
    result = 31 * result + nextAdGroupIndex;
    return result;
  }
}
