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

import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;

/** Stores the information required to load and play a {@link MediaPeriod}. */
/* package */ final class MediaPeriodInfo {

  /** The media period's identifier. */
  public final MediaPeriodId id;
  /** The start position of the media to play within the media period, in microseconds. */
  public final long startPositionUs;
  /**
   * The requested next start position for the current timeline period, in microseconds, or {@link
   * C#TIME_UNSET} if the period was requested to start at its default position.
   *
   * <p>Note that if {@link #id} refers to an ad, this is the requested start position for the
   * suspended content.
   */
  public final long requestedContentPositionUs;
  /**
   * The end position to which the media period's content is clipped in order to play a following ad
   * group or to terminate a server side ad inserted stream before a played postroll, in
   * microseconds, or {@link C#TIME_UNSET} if the content is not clipped or if this media period is
   * an ad. The value {@link C#TIME_END_OF_SOURCE} indicates that a postroll ad follows at the end
   * of this content media period.
   */
  public final long endPositionUs;
  /**
   * The duration of the media period, like {@link #endPositionUs} but with {@link
   * C#TIME_END_OF_SOURCE} and {@link C#TIME_UNSET} resolved to the timeline period duration if
   * known.
   */
  public final long durationUs;
  /**
   * Whether this media period is followed by a transition to another media period of the same
   * server-side inserted ad stream. If true, {@link #isLastInTimelinePeriod}, {@link
   * #isLastInTimelineWindow} and {@link #isFinal} will all be false.
   */
  public final boolean isFollowedByTransitionToSameStream;
  /**
   * Whether this is the last media period in its timeline period (e.g., a postroll ad, or a media
   * period corresponding to a timeline period without ads).
   */
  public final boolean isLastInTimelinePeriod;
  /** Whether this is the last media period in its timeline window. */
  public final boolean isLastInTimelineWindow;
  /**
   * Whether this is the last media period in the entire timeline. If true, {@link
   * #isLastInTimelinePeriod} will also be true.
   */
  public final boolean isFinal;

  MediaPeriodInfo(
      MediaPeriodId id,
      long startPositionUs,
      long requestedContentPositionUs,
      long endPositionUs,
      long durationUs,
      boolean isFollowedByTransitionToSameStream,
      boolean isLastInTimelinePeriod,
      boolean isLastInTimelineWindow,
      boolean isFinal) {
    Assertions.checkArgument(!isFinal || isLastInTimelinePeriod);
    Assertions.checkArgument(!isLastInTimelineWindow || isLastInTimelinePeriod);
    Assertions.checkArgument(
        !isFollowedByTransitionToSameStream
            || (!isLastInTimelinePeriod && !isLastInTimelineWindow && !isFinal));
    this.id = id;
    this.startPositionUs = startPositionUs;
    this.requestedContentPositionUs = requestedContentPositionUs;
    this.endPositionUs = endPositionUs;
    this.durationUs = durationUs;
    this.isFollowedByTransitionToSameStream = isFollowedByTransitionToSameStream;
    this.isLastInTimelinePeriod = isLastInTimelinePeriod;
    this.isLastInTimelineWindow = isLastInTimelineWindow;
    this.isFinal = isFinal;
  }

  /**
   * Returns a copy of this instance with the start position set to the specified value. May return
   * the same instance if nothing changed.
   */
  public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) {
    return startPositionUs == this.startPositionUs
        ? this
        : new MediaPeriodInfo(
            id,
            startPositionUs,
            requestedContentPositionUs,
            endPositionUs,
            durationUs,
            isFollowedByTransitionToSameStream,
            isLastInTimelinePeriod,
            isLastInTimelineWindow,
            isFinal);
  }

  /**
   * Returns a copy of this instance with the requested content position set to the specified value.
   * May return the same instance if nothing changed.
   */
  public MediaPeriodInfo copyWithRequestedContentPositionUs(long requestedContentPositionUs) {
    return requestedContentPositionUs == this.requestedContentPositionUs
        ? this
        : new MediaPeriodInfo(
            id,
            startPositionUs,
            requestedContentPositionUs,
            endPositionUs,
            durationUs,
            isFollowedByTransitionToSameStream,
            isLastInTimelinePeriod,
            isLastInTimelineWindow,
            isFinal);
  }

  @Override
  public boolean equals(@Nullable Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    MediaPeriodInfo that = (MediaPeriodInfo) o;
    return startPositionUs == that.startPositionUs
        && requestedContentPositionUs == that.requestedContentPositionUs
        && endPositionUs == that.endPositionUs
        && durationUs == that.durationUs
        && isFollowedByTransitionToSameStream == that.isFollowedByTransitionToSameStream
        && isLastInTimelinePeriod == that.isLastInTimelinePeriod
        && isLastInTimelineWindow == that.isLastInTimelineWindow
        && isFinal == that.isFinal
        && Util.areEqual(id, that.id);
  }

  @Override
  public int hashCode() {
    int result = 17;
    result = 31 * result + id.hashCode();
    result = 31 * result + (int) startPositionUs;
    result = 31 * result + (int) requestedContentPositionUs;
    result = 31 * result + (int) endPositionUs;
    result = 31 * result + (int) durationUs;
    result = 31 * result + (isFollowedByTransitionToSameStream ? 1 : 0);
    result = 31 * result + (isLastInTimelinePeriod ? 1 : 0);
    result = 31 * result + (isLastInTimelineWindow ? 1 : 0);
    result = 31 * result + (isFinal ? 1 : 0);
    return result;
  }
}
