/*
 * 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;

import static java.lang.Math.max;
import static java.lang.Math.min;

/** Default {@link ControlDispatcher}. */
public class DefaultControlDispatcher implements ControlDispatcher {

  /** The default fast forward increment, in milliseconds. */
  public static final int DEFAULT_FAST_FORWARD_MS = 15_000;
  /** The default rewind increment, in milliseconds. */
  public static final int DEFAULT_REWIND_MS = 5000;

  private static final int MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;

  private final Timeline.Window window;
  private final long rewindIncrementMs;
  private final long fastForwardIncrementMs;

  /** Creates an instance. */
  public DefaultControlDispatcher() {
    this(DEFAULT_FAST_FORWARD_MS, DEFAULT_REWIND_MS);
  }

  /**
   * Creates an instance with the given increments.
   *
   * @param fastForwardIncrementMs The fast forward increment in milliseconds. A non-positive value
   *     disables the fast forward operation.
   * @param rewindIncrementMs The rewind increment in milliseconds. A non-positive value disables
   *     the rewind operation.
   */
  public DefaultControlDispatcher(long fastForwardIncrementMs, long rewindIncrementMs) {
    this.fastForwardIncrementMs = fastForwardIncrementMs;
    this.rewindIncrementMs = rewindIncrementMs;
    window = new Timeline.Window();
  }

  @Override
  public boolean dispatchPrepare(Player player) {
    player.prepare();
    return true;
  }

  @Override
  public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) {
    player.setPlayWhenReady(playWhenReady);
    return true;
  }

  @Override
  public boolean dispatchSeekTo(Player player, int windowIndex, long positionMs) {
    player.seekTo(windowIndex, positionMs);
    return true;
  }

  @Override
  public boolean dispatchPrevious(Player player) {
    Timeline timeline = player.getCurrentTimeline();
    if (timeline.isEmpty() || player.isPlayingAd()) {
      return true;
    }
    int windowIndex = player.getCurrentWindowIndex();
    timeline.getWindow(windowIndex, window);
    int previousWindowIndex = player.getPreviousWindowIndex();
    boolean isUnseekableLiveStream = window.isLive() && !window.isSeekable;
    if (previousWindowIndex != C.INDEX_UNSET
        && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
            || isUnseekableLiveStream)) {
      player.seekTo(previousWindowIndex, C.TIME_UNSET);
    } else if (!isUnseekableLiveStream) {
      player.seekTo(windowIndex, /* positionMs= */ 0);
    }
    return true;
  }

  @Override
  public boolean dispatchNext(Player player) {
    Timeline timeline = player.getCurrentTimeline();
    if (timeline.isEmpty() || player.isPlayingAd()) {
      return true;
    }
    int windowIndex = player.getCurrentWindowIndex();
    timeline.getWindow(windowIndex, window);
    int nextWindowIndex = player.getNextWindowIndex();
    if (nextWindowIndex != C.INDEX_UNSET) {
      player.seekTo(nextWindowIndex, C.TIME_UNSET);
    } else if (window.isLive() && window.isDynamic) {
      player.seekTo(windowIndex, C.TIME_UNSET);
    }
    return true;
  }

  @Override
  public boolean dispatchRewind(Player player) {
    if (isRewindEnabled() && player.isCurrentWindowSeekable()) {
      seekToOffset(player, -rewindIncrementMs);
    }
    return true;
  }

  @Override
  public boolean dispatchFastForward(Player player) {
    if (isFastForwardEnabled() && player.isCurrentWindowSeekable()) {
      seekToOffset(player, fastForwardIncrementMs);
    }
    return true;
  }

  @Override
  public boolean dispatchSetRepeatMode(Player player, @Player.RepeatMode int repeatMode) {
    player.setRepeatMode(repeatMode);
    return true;
  }

  @Override
  public boolean dispatchSetShuffleModeEnabled(Player player, boolean shuffleModeEnabled) {
    player.setShuffleModeEnabled(shuffleModeEnabled);
    return true;
  }

  @Override
  public boolean dispatchStop(Player player, boolean reset) {
    player.stop(reset);
    return true;
  }

  @Override
  public boolean dispatchSetPlaybackParameters(
      Player player, PlaybackParameters playbackParameters) {
    player.setPlaybackParameters(playbackParameters);
    return true;
  }

  @Override
  public boolean isRewindEnabled() {
    return rewindIncrementMs > 0;
  }

  @Override
  public boolean isFastForwardEnabled() {
    return fastForwardIncrementMs > 0;
  }

  /** Returns the rewind increment in milliseconds. */
  public long getRewindIncrementMs() {
    return rewindIncrementMs;
  }

  /** Returns the fast forward increment in milliseconds. */
  public long getFastForwardIncrementMs() {
    return fastForwardIncrementMs;
  }

  // Internal methods.

  private static void seekToOffset(Player player, long offsetMs) {
    long positionMs = player.getCurrentPosition() + offsetMs;
    long durationMs = player.getDuration();
    if (durationMs != C.TIME_UNSET) {
      positionMs = min(positionMs, durationMs);
    }
    positionMs = max(positionMs, 0);
    player.seekTo(player.getCurrentWindowIndex(), positionMs);
  }
}

@@ -1139,48 +1139,8 @@
 0;%0A%0A
-  private final Timeline.Window window;%0A
   pr
@@ -1891,44 +1891,8 @@
 Ms;%0A
-    window = new Timeline.Window();%0A
   %7D%0A
@@ -2522,212 +2522,71 @@
     
-int windowIndex = player.getCurrentWindowIndex();%0A    timeline.getWindow(windowIndex, window);%0A    int previousWindowIndex = player.getPreviousWindowIndex();%0A    boolean isUnseekableLiveStream = w
+boolean isUnseekableLiveStream =%0A        player.isCurrentW
 indow
-.is
 Live
@@ -2596,25 +2596,38 @@
 && !
-w
+player.isCurrentW
 indow
-.is
 Seekable
 ;%0A  
@@ -2622,16 +2622,18 @@
 Seekable
+()
 ;%0A    if
@@ -2639,43 +2639,27 @@
 f (p
-reviousWindowIndex != C.INDEX_UNSET
+layer.hasPrevious()
 %0A   
@@ -2791,23 +2791,16 @@
 yer.
-seekTo(
 previous
 Wind
@@ -2799,33 +2799,9 @@
 ious
-WindowIndex, C.TIME_UNSET
+(
 );%0A 
@@ -2861,29 +2861,16 @@
 .seekTo(
-windowIndex, 
 /* posit
@@ -3111,161 +3111,56 @@
    i
-nt windowIndex = player.getCurrentWindowInd
+f (player.hasN
 ex
+t
 ()
-;%0A    timeline.getWindow(windowIndex, window);%0A    int nextWindowIndex = player.getNextWindowInd
+) %7B%0A      player.n
 ex
+t
 ();%0A    
 if (
@@ -3159,150 +3159,82 @@
     
-if (nextWindowIndex != C.INDEX_UNSET) %7B%0A      player.seekTo(nextWindowIndex, C.TIME_UNSET);%0A    %7D else if (window.isLive() && w
+%7D else if (player.isCurrentWindowLive() && player.isCurrentW
 indow
-.is
 Dynamic
+()
 ) %7B%0A
@@ -3256,34 +3256,24 @@
 ekTo
-(windowIndex, C.TIME_UNSET
+DefaultPosition(
 );%0A 
@@ -5186,40 +5186,8 @@
 kTo(
-player.getCurrentWindowIndex(), 
 posi

Simplify DefaultControlDispatcher by using Player methods