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

import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.analytics.PlayerId;

/** Manages a DRM session. */
@UnstableApi
public interface DrmSessionManager {

  /**
   * Represents a single reference count of a {@link DrmSession}, while deliberately not giving
   * access to the underlying session.
   */
  interface DrmSessionReference {
    /** A reference that is never populated with an underlying {@link DrmSession}. */
    DrmSessionReference EMPTY = () -> {};

    /**
     * Releases the underlying session at most once.
     *
     * <p>Can be called from any thread. Calling this method more than once will only release the
     * underlying session once.
     */
    void release();
  }

  /** An instance that supports no DRM schemes. */
  DrmSessionManager DRM_UNSUPPORTED =
      new DrmSessionManager() {

        @Override
        public void setPlayer(Looper playbackLooper, PlayerId playerId) {}

        @Override
        @Nullable
        public DrmSession acquireSession(
            @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, Format format) {
          if (format.drmInitData == null) {
            return null;
          } else {
            return new ErrorStateDrmSession(
                new DrmSession.DrmSessionException(
                    new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME),
                    PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED));
          }
        }

        @Override
        public @C.CryptoType int getCryptoType(Format format) {
          return format.drmInitData != null ? C.CRYPTO_TYPE_UNSUPPORTED : C.CRYPTO_TYPE_NONE;
        }
      };

  /**
   * An instance that supports no DRM schemes.
   *
   * @deprecated Use {@link #DRM_UNSUPPORTED}.
   */
  @Deprecated DrmSessionManager DUMMY = DRM_UNSUPPORTED;

  /**
   * Returns {@link #DRM_UNSUPPORTED}.
   *
   * @deprecated Use {@link #DRM_UNSUPPORTED}.
   */
  @Deprecated
  static DrmSessionManager getDummyDrmSessionManager() {
    return DRM_UNSUPPORTED;
  }

  /**
   * Acquires any required resources.
   *
   * <p>{@link #release()} must be called to ensure the acquired resources are released. After
   * releasing, an instance may be re-prepared.
   */
  default void prepare() {
    // Do nothing.
  }

  /** Releases any acquired resources. */
  default void release() {
    // Do nothing.
  }

  /**
   * Sets information about the player using this DRM session manager.
   *
   * @param playbackLooper The {@link Looper} associated with the player's playback thread.
   * @param playerId The {@link PlayerId} of the player.
   */
  void setPlayer(Looper playbackLooper, PlayerId playerId);

  /**
   * Pre-acquires a DRM session for the specified {@link Format}.
   *
   * <p>This notifies the manager that a subsequent call to {@link #acquireSession(
   * DrmSessionEventListener.EventDispatcher, Format)} with the same {@link Format} is likely,
   * allowing a manager that supports pre-acquisition to get the required {@link DrmSession} ready
   * in the background.
   *
   * <p>The caller must call {@link DrmSessionReference#release()} on the returned instance when
   * they no longer require the pre-acquisition (i.e. they know they won't be making a matching call
   * to {@link #acquireSession(DrmSessionEventListener.EventDispatcher, Format)} in the near
   * future).
   *
   * <p>This manager may silently release the underlying session in order to allow another operation
   * to complete. This will result in a subsequent call to {@link #acquireSession(
   * DrmSessionEventListener.EventDispatcher, Format)} re-initializing a new session, including
   * repeating key loads and other async initialization steps.
   *
   * <p>The caller must separately call {@link #acquireSession(
   * DrmSessionEventListener.EventDispatcher, Format)} in order to obtain a session suitable for
   * playback. The pre-acquired {@link DrmSessionReference} and full {@link DrmSession} instances
   * are distinct. The caller must release both, and can release the {@link DrmSessionReference}
   * before the {@link DrmSession} without affecting playback.
   *
   * <p>This can be called from any thread.
   *
   * <p>Implementations that do not support pre-acquisition always return an empty {@link
   * DrmSessionReference} instance.
   *
   * @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute
   *     events, and passed on to {@link
   *     DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}.
   * @param format The {@link Format} for which to pre-acquire a {@link DrmSession}.
   * @return A releaser for the pre-acquired session. Guaranteed to be non-null even if the matching
   *     {@link #acquireSession(DrmSessionEventListener.EventDispatcher, Format)} would return null.
   */
  default DrmSessionReference preacquireSession(
      @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, Format format) {
    return DrmSessionReference.EMPTY;
  }

  /**
   * Returns a {@link DrmSession} for the specified {@link Format}, with an incremented reference
   * count. May return null if the {@link Format#drmInitData} is null and the DRM session manager is
   * not configured to attach a {@link DrmSession} to clear content. When the caller no longer needs
   * to use a returned {@link DrmSession}, it must call {@link
   * DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
   *
   * <p>If the provided {@link Format} contains a null {@link Format#drmInitData}, the returned
   * {@link DrmSession} (if not null) will be a placeholder session which does not execute key
   * requests, and cannot be used to handle encrypted content. However, a placeholder session may be
   * used to configure secure decoders for playback of clear content periods, which can reduce the
   * cost of transitioning between clear and encrypted content.
   *
   * @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute
   *     events, and passed on to {@link
   *     DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}.
   * @param format The {@link Format} for which to acquire a {@link DrmSession}.
   * @return The DRM session. May be null if the given {@link Format#drmInitData} is null.
   */
  @Nullable
  DrmSession acquireSession(
      @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher, Format format);

  /**
   * Returns the {@link C.CryptoType} that the DRM session manager will use for a given {@link
   * Format}. Returns {@link C#CRYPTO_TYPE_UNSUPPORTED} if the manager does not support any of the
   * DRM schemes defined in the {@link Format}. Returns {@link C#CRYPTO_TYPE_NONE} if {@link
   * Format#drmInitData} is null and {@link #acquireSession} will return {@code null} for the given
   * {@link Format}.
   *
   * @param format The {@link Format}.
   * @return The {@link C.CryptoType} that the manager will use, or @link C#CRYPTO_TYPE_UNSUPPORTED}
   *     if the manager does not support any of the DRM schemes defined in the {@link Format}. Will
   *     be {@link C#CRYPTO_TYPE_NONE} if {@link Format#drmInitData} is null and {@link
   *     #acquireSession} will return null for the given {@link Format}.
   */
  @C.CryptoType
  int getCryptoType(Format format);
}
