/*
 * Copyright (C) 2017 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 com.google.android.exoplayer2.ext.mediasession;

import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.util.Util;
import java.util.List;

/**
 * A {@link MediaSessionConnector.QueueEditor} implementation.
 *
 * <p>This class implements the {@link MediaSessionConnector.CommandReceiver} interface and handles
 * the {@link #COMMAND_MOVE_QUEUE_ITEM} to move a queue item instead of removing and inserting it.
 * This allows to move the currently playing window without interrupting playback.
 *
 * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which
 *     contains the same ExoPlayer code). See <a
 *     href="https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide">the
 *     migration guide</a> for more details, including a script to help with the migration.
 */
@Deprecated
public final class TimelineQueueEditor
    implements MediaSessionConnector.QueueEditor, MediaSessionConnector.CommandReceiver {

  public static final String COMMAND_MOVE_QUEUE_ITEM = "exo_move_window";
  public static final String EXTRA_FROM_INDEX = "from_index";
  public static final String EXTRA_TO_INDEX = "to_index";

  /** Converts a {@link MediaDescriptionCompat} to a {@link MediaItem}. */
  public interface MediaDescriptionConverter {
    /**
     * Returns a {@link MediaItem} for the given {@link MediaDescriptionCompat} or null if the
     * description can't be converted.
     *
     * <p>If not null, the media item that is returned will be used to call {@link
     * Player#addMediaItem(MediaItem)}.
     */
    @Nullable
    MediaItem convert(MediaDescriptionCompat description);
  }

  /**
   * Adapter to get {@link MediaDescriptionCompat} of items in the queue and to notify the
   * application about changes in the queue to sync the data structure backing the {@link
   * MediaSessionConnector}.
   */
  public interface QueueDataAdapter {
    /**
     * Adds a {@link MediaDescriptionCompat} at the given {@code position}.
     *
     * @param position The position at which to add.
     * @param description The {@link MediaDescriptionCompat} to be added.
     */
    void add(int position, MediaDescriptionCompat description);
    /**
     * Removes the item at the given {@code position}.
     *
     * @param position The position at which to remove the item.
     */
    void remove(int position);
    /**
     * Moves a queue item from position {@code from} to position {@code to}.
     *
     * @param from The position from which to remove the item.
     * @param to The target position to which to move the item.
     */
    void move(int from, int to);
  }

  /** Used to evaluate whether two {@link MediaDescriptionCompat} are considered equal. */
  interface MediaDescriptionEqualityChecker {
    /**
     * Returns {@code true} whether the descriptions are considered equal.
     *
     * @param d1 The first {@link MediaDescriptionCompat}.
     * @param d2 The second {@link MediaDescriptionCompat}.
     * @return {@code true} if considered equal.
     */
    boolean equals(MediaDescriptionCompat d1, MediaDescriptionCompat d2);
  }

  /**
   * Media description comparator comparing the media IDs. Media IDs are considered equals if both
   * are {@code null}.
   */
  public static final class MediaIdEqualityChecker implements MediaDescriptionEqualityChecker {

    @Override
    public boolean equals(MediaDescriptionCompat d1, MediaDescriptionCompat d2) {
      return Util.areEqual(d1.getMediaId(), d2.getMediaId());
    }
  }

  private final MediaControllerCompat mediaController;
  private final QueueDataAdapter queueDataAdapter;
  private final MediaDescriptionConverter mediaDescriptionConverter;
  private final MediaDescriptionEqualityChecker equalityChecker;

  /**
   * Creates a new {@link TimelineQueueEditor} with a given mediaSourceFactory.
   *
   * @param mediaController A {@link MediaControllerCompat} to read the current queue.
   * @param queueDataAdapter A {@link QueueDataAdapter} to change the backing data.
   * @param mediaDescriptionConverter The {@link MediaDescriptionConverter} for converting media
   *     descriptions to {@link MediaItem MediaItems}.
   */
  public TimelineQueueEditor(
      MediaControllerCompat mediaController,
      QueueDataAdapter queueDataAdapter,
      MediaDescriptionConverter mediaDescriptionConverter) {
    this(
        mediaController, queueDataAdapter, mediaDescriptionConverter, new MediaIdEqualityChecker());
  }

  /**
   * Creates a new {@link TimelineQueueEditor} with a given mediaSourceFactory.
   *
   * @param mediaController A {@link MediaControllerCompat} to read the current queue.
   * @param queueDataAdapter A {@link QueueDataAdapter} to change the backing data.
   * @param mediaDescriptionConverter The {@link MediaDescriptionConverter} for converting media
   *     descriptions to {@link MediaItem MediaItems}.
   * @param equalityChecker The {@link MediaDescriptionEqualityChecker} to match queue items.
   */
  public TimelineQueueEditor(
      MediaControllerCompat mediaController,
      QueueDataAdapter queueDataAdapter,
      MediaDescriptionConverter mediaDescriptionConverter,
      MediaDescriptionEqualityChecker equalityChecker) {
    this.mediaController = mediaController;
    this.queueDataAdapter = queueDataAdapter;
    this.mediaDescriptionConverter = mediaDescriptionConverter;
    this.equalityChecker = equalityChecker;
  }

  @Override
  public void onAddQueueItem(Player player, MediaDescriptionCompat description) {
    onAddQueueItem(player, description, player.getCurrentTimeline().getWindowCount());
  }

  @Override
  public void onAddQueueItem(Player player, MediaDescriptionCompat description, int index) {
    @Nullable MediaItem mediaItem = mediaDescriptionConverter.convert(description);
    if (mediaItem != null) {
      queueDataAdapter.add(index, description);
      player.addMediaItem(index, mediaItem);
    }
  }

  @Override
  public void onRemoveQueueItem(Player player, MediaDescriptionCompat description) {
    List<MediaSessionCompat.QueueItem> queue = mediaController.getQueue();
    for (int i = 0; i < queue.size(); i++) {
      if (equalityChecker.equals(queue.get(i).getDescription(), description)) {
        queueDataAdapter.remove(i);
        player.removeMediaItem(i);
        return;
      }
    }
  }

  // CommandReceiver implementation.

  @Override
  public boolean onCommand(
      Player player, String command, @Nullable Bundle extras, @Nullable ResultReceiver cb) {
    if (!COMMAND_MOVE_QUEUE_ITEM.equals(command) || extras == null) {
      return false;
    }
    int from = extras.getInt(EXTRA_FROM_INDEX, C.INDEX_UNSET);
    int to = extras.getInt(EXTRA_TO_INDEX, C.INDEX_UNSET);
    if (from != C.INDEX_UNSET && to != C.INDEX_UNSET) {
      queueDataAdapter.move(from, to);
      player.moveMediaItem(from, to);
    }
    return true;
  }
}
