package com.castlabs.reactnative.player;

import android.view.Surface;
import androidx.annotation.NonNull;
import com.castlabs.android.player.DisplayInfo;
import com.castlabs.android.player.PlayerController;
import com.castlabs.android.player.PlayerListener;
import com.castlabs.android.player.VideoRendererListener;
import com.castlabs.android.player.exceptions.CastlabsPlayerException;
import com.castlabs.android.player.models.VideoTrackQuality;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Methods are synchronized to protect against
 * concurrent reads and writes inside listeners.
 */
public class PlaybackStatsMonitor implements PlayerListener, VideoRendererListener {
  private final @NonNull PlayerController playerController;

  private final @NonNull List<PlaybackStatsListener> listeners =
      new ArrayList<PlaybackStatsListener>();

  private final @NonNull ScheduledExecutorService scheduler =
      Executors.newSingleThreadScheduledExecutor();

  public PlaybackStatsMonitor(@NonNull PlayerController playerController) {
    this.playerController = playerController;

    this.playerController.addVideoRendererListener(this);
    this.playerController.addPlayerListener(this);

    startScheduler();
  }

  @Override
  synchronized public void onVideoSizeChanged(
      int width, int height, float pixelWidthHeightRatio
  ) {
    for (PlaybackStatsListener listener : listeners) {
      VideoTrackQuality videoQuality = playerController.getVideoQuality();
      if (videoQuality != null) {
        long newStreamBandwidth = videoQuality.getBitrate();
        if (newStreamBandwidth > 0) {
          listener.onStreamBandwidth(newStreamBandwidth);
        }
      }
    }
  }

  @Override
  synchronized public void onDroppedFrames(int count, long elapsedMs) {
    for (PlaybackStatsListener listener : listeners) {
      listener.onDroppedFrames(count);
    }
  }

  /**
   * Some value changes, such as the bitrate estimate, aren't emitted,
   * so they are pulled at regular intervals.
   */
  synchronized public void onScheduler() {
    for (PlaybackStatsListener listener : listeners) {
      long newEstimatedBandwidth = playerController.getBitrateEstimate();
      if (newEstimatedBandwidth > 0) {
        listener.onEstimatedBandwidth(newEstimatedBandwidth);
      }
      long bufferedUsec = playerController.getBufferSizeTime();
      listener.onBufferedTimeUpdate(bufferedUsec);
    }
  }

  /**
   * Adds the event listener.
   *
   * @param listener event listener
   */
  synchronized public void addListener(PlaybackStatsListener listener) {
    listeners.add(listener);
  }

  /**
   * Removes the event listener.
   *
   * @param listener event listener
   */
  synchronized public void removeListener(PlaybackStatsListener listener) {
    listeners.remove(listener);
  }

  synchronized public void dispose() {
    this.playerController.removeVideoRendererListener(this);
    this.playerController.removePlayerListener(this);

    listeners.clear();

    stopScheduler();
  }

  private void startScheduler() {
    // Schedule the task to run after an initial delay of 0, and then every 1 second.
    scheduler.scheduleWithFixedDelay(this::onScheduler, 0, 1, TimeUnit.SECONDS);
  }

  private void stopScheduler() {
    scheduler.shutdown();

    try {
      // Wait for the running task to finish for up to 1 seconds
      if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
        System.err.println("Scheduler did not terminate in the specified time.");
        scheduler.shutdownNow();
      }
    } catch (InterruptedException e) {
      scheduler.shutdownNow();
    }
  }

  @Override
  public void onVideoEnabled(DecoderCounters decoderCounters) {

  }

  @Override
  public void onVideoDecoderInitialized(String s, long l, long l1) {

  }

  @Override
  public void onVideoDecoderReleased(@NonNull String s, long l, long l1) {

  }

  @Override
  public void onVideoInputFormatChanged(Format format) {

  }

  @Override
  public void onRenderedFirstFrame(Surface surface) {

  }

  @Override
  public void onVideoDisabled(DecoderCounters decoderCounters) {

  }

  @Override
  public void onFatalErrorOccurred(@NonNull CastlabsPlayerException e) {

  }

  @Override
  public void onError(@NonNull CastlabsPlayerException e) {

  }

  @Override
  public void onStateChanged(@NonNull PlayerController.State state) {

  }

  @Override
  public void onSeekTo(long l) {

  }

  @Override
  public void onSeekCompleted() {

  }

  @Override
  public void onSeekRangeChanged(long l, long l1) {

  }

  @Override
  public void onPlaybackPositionChanged(long l) {

  }

  @Override
  public void onDisplayChanged(DisplayInfo displayInfo, boolean b) {

  }

  @Override
  public void onDurationChanged(long l) {

  }

  @Override
  public void onSpeedChanged(float v) {

  }

  @Override
  public void onPlayerModelChanged() {

  }

  @Override
  public void onVideoKeyStatusChanged(@NonNull List<VideoTrackQuality> list) {

  }

  @Override
  public void onFullyBuffered() {

  }
}
