import { LabelConfig, Label } from './Label';
import { UIInstanceManager } from '../../UIManager';
import LiveStreamDetectorEventArgs = PlayerUtils.LiveStreamDetectorEventArgs;
import { PlayerUtils } from '../../utils/PlayerUtils';
import { StringUtils } from '../../utils/StringUtils';
import { PlayerAPI } from 'bitmovin-player';
import { i18n } from '../../localization/i18n';

export enum PlaybackTimeLabelMode {
  /**
   * Displays the current time
   */
  CurrentTime,
  /**
   * Displays the duration of the content
   */
  TotalTime,
  /**
   * Displays the current time and the duration of the content
   * Format: ${currentTime} / ${totalTime}
   */
  CurrentAndTotalTime,
  /**
   * Displays the remaining time of the content
   */
  RemainingTime,
}

/**
 * @category Configs
 */
export interface PlaybackTimeLabelConfig extends LabelConfig {
  /**
   * The type of which time should be displayed in the label.
   * Default: PlaybackTimeLabelMode.CurrentAndTotalTime
   */
  timeLabelMode?: PlaybackTimeLabelMode;
  /**
   * Boolean if the label should be hidden in live playback
   */
  hideInLivePlayback?: boolean;
}

/**
 * A label that display the current playback time and the total time through {@link PlaybackTimeLabel#setTime setTime}
 * or any string through {@link PlaybackTimeLabel#setText setText}.
 *
 * @category Labels
 */
export class PlaybackTimeLabel extends Label<PlaybackTimeLabelConfig> {
  private timeFormat: string;

  constructor(config: PlaybackTimeLabelConfig = {}) {
    super(config);

    this.config = this.mergeConfig(
      config,
      <PlaybackTimeLabelConfig>{
        cssClass: 'ui-playbacktimelabel',
        timeLabelMode: PlaybackTimeLabelMode.CurrentAndTotalTime,
        hideInLivePlayback: false,
      },
      this.config,
    );
  }

  configure(player: PlayerAPI, uimanager: UIInstanceManager): void {
    super.configure(player, uimanager);

    const config = this.getConfig();
    let live = false;
    const liveCssClass = this.prefixCss('ui-playbacktimelabel-live');
    const liveEdgeCssClass = this.prefixCss('ui-playbacktimelabel-live-edge');
    let minWidth = 0;

    const liveClickHandler = () => {
      player.timeShift(0);
    };

    const updateLiveState = () => {
      // Player is playing a live stream when the duration is infinite
      live = player.isLive();

      // Attach/detach live marker class
      if (live) {
        this.getDomElement().addClass(liveCssClass);
        this.setText(i18n.getLocalizer('live'));
        if (config.hideInLivePlayback) {
          this.hide();
        }
        this.onClick.subscribe(liveClickHandler);
        updateLiveTimeshiftState();
      } else {
        this.getDomElement().removeClass(liveCssClass);
        this.getDomElement().removeClass(liveEdgeCssClass);
        this.show();
        this.onClick.unsubscribe(liveClickHandler);
      }
    };

    const updateLiveTimeshiftState = () => {
      if (!live) {
        return;
      }

      // The player is only at the live edge iff the stream is not shifted and it is actually playing or playback has
      // never been started (meaning it isn't paused). A player that is paused is always behind the live edge.
      // An exception is made for live streams without a timeshift window, because here we "stop" playback instead
      // of pausing it (from a UI perspective), so we keep the live edge indicator on because a play would always
      // resume at the live edge.
      const isTimeshifted = player.getTimeShift() < 0;
      const isTimeshiftAvailable = player.getMaxTimeShift() < 0;
      if (!isTimeshifted && (!player.isPaused() || !isTimeshiftAvailable)) {
        this.getDomElement().addClass(liveEdgeCssClass);
      } else {
        this.getDomElement().removeClass(liveEdgeCssClass);
      }
    };

    const playbackTimeHandler = () => {
      if (!live && player.getDuration() !== Infinity) {
        this.setTime(PlayerUtils.getCurrentTimeRelativeToSeekableRange(player), player.getDuration());
      }

      // To avoid 'jumping' in the UI by varying label sizes due to non-monospaced fonts,
      // we gradually increase the min-width with the content to reach a stable size.
      const width = this.getDomElement().width();
      if (width > minWidth) {
        minWidth = width;
        this.getDomElement().css({
          'min-width': minWidth + 'px',
        });
      }
    };

    const updateTimeFormatBasedOnDuration = () => {
      // Set time format depending on source duration
      this.timeFormat =
        Math.abs(player.isLive() ? player.getMaxTimeShift() : player.getDuration()) >= 3600
          ? StringUtils.FORMAT_HHMMSS
          : StringUtils.FORMAT_MMSS;
      playbackTimeHandler();
    };

    const liveStreamDetector = new PlayerUtils.LiveStreamDetector(player, uimanager);
    liveStreamDetector.onLiveChanged.subscribe((sender, args: LiveStreamDetectorEventArgs) => {
      live = args.live;
      playbackTimeHandler();
      updateTimeFormatBasedOnDuration();
      updateLiveState();
    });
    liveStreamDetector.detect(); // Initial detection

    const init = () => {
      // Reset min-width when a new source is ready (especially for switching VOD/Live modes where the label content
      // changes)
      minWidth = 0;
      this.getDomElement().css({
        'min-width': null,
      });

      updateTimeFormatBasedOnDuration();
    };

    player.on(player.exports.PlayerEvent.TimeChanged, playbackTimeHandler);
    player.on(player.exports.PlayerEvent.Ready, updateTimeFormatBasedOnDuration);
    player.on(player.exports.PlayerEvent.Seeked, playbackTimeHandler);

    player.on(player.exports.PlayerEvent.TimeShift, updateLiveTimeshiftState);
    player.on(player.exports.PlayerEvent.TimeShifted, updateLiveTimeshiftState);
    player.on(player.exports.PlayerEvent.Playing, updateLiveTimeshiftState);
    player.on(player.exports.PlayerEvent.Paused, updateLiveTimeshiftState);
    player.on(player.exports.PlayerEvent.StallStarted, updateLiveTimeshiftState);
    player.on(player.exports.PlayerEvent.StallEnded, updateLiveTimeshiftState);
    player.on(player.exports.PlayerEvent.DurationChanged, init);

    uimanager.getConfig().events.onUpdated.subscribe(init);

    init();
  }

  /**
   * Sets the current playback time and total duration.
   * @param playbackSeconds the current playback time in seconds
   * @param durationSeconds the total duration in seconds
   */
  setTime(playbackSeconds: number, durationSeconds: number) {
    const currentTime = StringUtils.secondsToTime(playbackSeconds, this.timeFormat);
    const totalTime = StringUtils.secondsToTime(durationSeconds, this.timeFormat);

    switch ((<PlaybackTimeLabelConfig>this.config).timeLabelMode) {
      case PlaybackTimeLabelMode.CurrentTime:
        this.setText(`${currentTime}`);
        break;
      case PlaybackTimeLabelMode.TotalTime:
        this.setText(`${totalTime}`);
        break;
      case PlaybackTimeLabelMode.CurrentAndTotalTime:
        this.setText(`${currentTime} / ${totalTime}`);
        break;
      case PlaybackTimeLabelMode.RemainingTime:
        this.setText(`${StringUtils.secondsToTime(durationSeconds - playbackSeconds, this.timeFormat)}`);
        break;
    }
  }

  /**
   * Sets the current time format
   * @param timeFormat the time format
   */
  protected setTimeFormat(timeFormat: string): void {
    this.timeFormat = timeFormat;
  }
}
