/*
 * 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.hls.playlist;

import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.hls.HlsDataSourceFactory;
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import java.io.IOException;

/**
 * Tracks playlists associated to an HLS stream and provides snapshots.
 *
 * <p>The playlist tracker is responsible for exposing the seeking window, which is defined by the
 * segments that one of the playlists exposes. This playlist is called primary and needs to be
 * periodically refreshed in the case of live streams. Note that the primary playlist is one of the
 * media playlists while the multivariant playlist is an optional kind of playlist defined by the
 * HLS specification (RFC 8216).
 *
 * <p>Playlist loads might encounter errors. The tracker may choose to exclude them to ensure a
 * primary playlist is always available.
 */
@UnstableApi
public interface HlsPlaylistTracker {

  /** Factory for {@link HlsPlaylistTracker} instances. */
  interface Factory {

    /**
     * Creates a new tracker instance.
     *
     * @param dataSourceFactory The {@link HlsDataSourceFactory} to use for playlist loading.
     * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for playlist load errors.
     * @param playlistParserFactory The {@link HlsPlaylistParserFactory} for playlist parsing.
     */
    HlsPlaylistTracker createTracker(
        HlsDataSourceFactory dataSourceFactory,
        LoadErrorHandlingPolicy loadErrorHandlingPolicy,
        HlsPlaylistParserFactory playlistParserFactory);
  }

  /** Listener for primary playlist changes. */
  interface PrimaryPlaylistListener {

    /**
     * Called when the primary playlist changes.
     *
     * @param mediaPlaylist The primary playlist new snapshot.
     */
    void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist);
  }

  /** Called on playlist loading events. */
  interface PlaylistEventListener {

    /** Called a playlist changes. */
    void onPlaylistChanged();

    /**
     * Called if an error is encountered while loading a playlist.
     *
     * @param url The loaded url that caused the error.
     * @param loadErrorInfo The load error info.
     * @param forceRetry Whether retry should be forced without considering exclusion.
     * @return True if excluding did not encounter errors. False otherwise.
     */
    boolean onPlaylistError(
        Uri url, LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, boolean forceRetry);
  }

  /** Thrown when a playlist is considered to be stuck due to a server side error. */
  final class PlaylistStuckException extends IOException {

    /** The url of the stuck playlist. */
    public final Uri url;

    /**
     * Creates an instance.
     *
     * @param url See {@link #url}.
     */
    public PlaylistStuckException(Uri url) {
      this.url = url;
    }
  }

  /** Thrown when the media sequence of a new snapshot indicates the server has reset. */
  final class PlaylistResetException extends IOException {

    /** The url of the reset playlist. */
    public final Uri url;

    /**
     * Creates an instance.
     *
     * @param url See {@link #url}.
     */
    public PlaylistResetException(Uri url) {
      this.url = url;
    }
  }

  /**
   * Starts the playlist tracker.
   *
   * <p>Must be called from the playback thread. A tracker may be restarted after a {@link #stop()}
   * call.
   *
   * @param initialPlaylistUri Uri of the HLS stream. Can point to a media playlist or a
   *     multivariant playlist.
   * @param eventDispatcher A dispatcher to notify of events.
   * @param primaryPlaylistListener A callback for the primary playlist change events.
   */
  void start(
      Uri initialPlaylistUri,
      EventDispatcher eventDispatcher,
      PrimaryPlaylistListener primaryPlaylistListener);

  /**
   * Stops the playlist tracker and releases any acquired resources.
   *
   * <p>Must be called once per {@link #start} call.
   */
  void stop();

  /**
   * Registers a listener to receive events from the playlist tracker.
   *
   * @param listener The listener.
   */
  void addListener(PlaylistEventListener listener);

  /**
   * Unregisters a listener.
   *
   * @param listener The listener to unregister.
   */
  void removeListener(PlaylistEventListener listener);

  /**
   * Returns the multivariant playlist.
   *
   * <p>If the uri passed to {@link #start} points to a media playlist, an {@link
   * HlsMultivariantPlaylist} with a single variant for said media playlist is returned.
   *
   * @return The multivariant playlist. Null if the initial playlist has yet to be loaded.
   */
  @Nullable
  HlsMultivariantPlaylist getMultivariantPlaylist();

  /**
   * Returns the most recent snapshot available of the playlist referenced by the provided {@link
   * Uri}.
   *
   * @param url The {@link Uri} corresponding to the requested media playlist.
   * @param isForPlayback Whether the caller might use the snapshot to request media segments for
   *     playback. If true, the primary playlist may be updated to the one requested.
   * @return The most recent snapshot of the playlist referenced by the provided {@link Uri}. May be
   *     null if no snapshot has been loaded yet.
   */
  @Nullable
  HlsMediaPlaylist getPlaylistSnapshot(Uri url, boolean isForPlayback);

  /**
   * Returns the start time of the first loaded primary playlist, or {@link C#TIME_UNSET} if no
   * media playlist has been loaded.
   */
  long getInitialStartTimeUs();

  /**
   * Returns whether the snapshot of the playlist referenced by the provided {@link Uri} is valid,
   * meaning all the segments referenced by the playlist are expected to be available. If the
   * playlist is not valid then some of the segments may no longer be available.
   *
   * @param url The {@link Uri}.
   * @return Whether the snapshot of the playlist referenced by the provided {@link Uri} is valid.
   */
  boolean isSnapshotValid(Uri url);

  /**
   * If the tracker is having trouble refreshing the multivariant playlist or the primary playlist,
   * this method throws the underlying error. Otherwise, does nothing.
   *
   * @throws IOException The underlying error.
   */
  void maybeThrowPrimaryPlaylistRefreshError() throws IOException;

  /**
   * If the playlist is having trouble refreshing the playlist referenced by the given {@link Uri},
   * this method throws the underlying error.
   *
   * @param url The {@link Uri}.
   * @throws IOException The underyling error.
   */
  void maybeThrowPlaylistRefreshError(Uri url) throws IOException;

  /**
   * Excludes the given media playlist for the given duration, in milliseconds.
   *
   * @param playlistUrl The URL of the media playlist.
   * @param exclusionDurationMs The duration for which to exclude the playlist.
   * @return Whether exclusion was successful.
   */
  boolean excludeMediaPlaylist(Uri playlistUrl, long exclusionDurationMs);

  /**
   * Requests a playlist refresh and removes it from the exclusion list.
   *
   * <p>The playlist tracker may choose to delay the playlist refresh. The request is discarded if a
   * refresh was already pending.
   *
   * @param url The {@link Uri} of the playlist to be refreshed.
   */
  void refreshPlaylist(Uri url);

  /**
   * Returns whether the tracked playlists describe a live stream.
   *
   * @return True if the content is live. False otherwise.
   */
  boolean isLive();
}
