/**
 * fichier seek-bar.js
 */
import Slider from '../../slider/slider.js' ;
import Component from '../../component.js' ;
import {IS_IOS, IS_ANDROID} from '../../utils/browser.js' ;
import * as Dom from '../../utils/dom.js' ;
import * as Fn from '../../utils/fn.js' ;
import formatTime from '../../utils/format-time.js' ;
import {silencePromesse} de '../../utils/promesse' ;
import keycode from 'keycode' ;
import document from 'global/document' ;

import './load-progress-bar.js' ;
import './play-progress-bar.js' ;
import './mouse-time-display.js' ;

// Le nombre de secondes pendant lesquelles les fonctions `step*` déplacent la ligne de temps.
const STEP_SECONDS = 5 ;

// Le multiplicateur de STEP_SECONDS que PgUp/PgDown déplace sur la ligne de temps.
const PAGE_KEY_MULTIPLIER = 12 ;

/**
 * Barre de recherche et conteneur pour les barres de progression. Utilise {@link PlayProgressBar}
 * comme sa "barre".
 *
 * slider @extends
 */
class SeekBar extends Slider {

  /**
   * Crée une instance de cette classe.
   *
   * @param {Player} player
   *        Le `Player` auquel cette classe doit être attachée.
   *
   * @param {Objet} [options]
   *        La mémoire clé/valeur des options du lecteur.
   */
  constructor(player, options) {
    super(player, options) ;
    this.setEventHandlers_() ;
  }

  /**
   * Définit les gestionnaires d'événements
   *
   * @private
   */
  setEventHandlers_() {
    this.update_ = Fn.bind(this, this.update) ;
    this.update = Fn.throttle(this.update_, Fn.UPDATE_REFRESH_INTERVAL) ;

    this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update) ;
    if (this.player_.liveTracker) {
      this.on(this.player_.liveTracker, 'liveedgechange', this.update) ;
    }

    // lors de la lecture, il faut s'assurer que la barre de progression de la lecture est mise à jour en douceur
    // via un intervalle
    this.updateInterval = null ;

    this.enableIntervalHandler_ = (e) => this.enableInterval_(e) ;
    this.disableIntervalHandler_ = (e) => this.disableInterval_(e) ;

    this.on(this.player_, ['playing'], this.enableIntervalHandler_) ;

    this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_) ;

    // il n'est pas nécessaire de mettre à jour la progression de la lecture si le document est caché,
    // également, cela provoque un pic du CPU et éventuellement un plantage de la page sur IE11.
    if ("hidden" dans le document && "visibilityState" dans le document) {
      this.on(document, 'visibilitychange', this.toggleVisibility_) ;
    }
  }

  toggleVisibility_(e) {
    if (document.visibilityState === 'hidden') {
      this.cancelNamedAnimationFrame('SeekBar#update') ;
      this.cancelNamedAnimationFrame('Slider#update') ;
      this.disableInterval_(e) ;
    } else {
      if (!this.player_.ended() && !this.player_.paused()) {
        this.enableInterval_() ;
      }

      // nous venons juste de revenir à la page et quelqu'un pourrait être en train de regarder, donc, mettez à jour ASAP
      this.update() ;
    }
  }

  enableInterval_() {
    if (this.updateInterval) {
      retour ;

    }
    this.updateInterval = this.setInterval(this.update, Fn.UPDATE_REFRESH_INTERVAL) ;
  }

  disableInterval_(e) {
    if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
      retour ;
    }

    if (!this.updateInterval) {
      retour ;
    }

    this.clearInterval(this.updateInterval) ;
    this.updateInterval = null ;
  }

  /**
   * Créer l'élément DOM du `Composant`
   *
   * @return {Element}
   *         L'élément qui a été créé.
   */
  createEl() {
    return super.createEl('div', {
      className : 'vjs-progress-holder'
    }, {
      'aria-label' : this.localize('Barre de progression')
    }) ;
  }

  /**
   * Cette fonction met à jour la barre de progression de la lecture et l'accessibilité
   * à tout ce qui est transmis.
   *
   * @param {EventTarget~Event} [event]
   *        L'événement `timeupdate` ou `ended` qui a provoqué l'exécution.
   *
   * @listens Player#timeupdate
   *
   * @return {number}
   *          Le pourcentage actuel d'un nombre compris entre 0 et 1
   */
  update(event) {
    // ignorer les mises à jour lorsque l'onglet est caché
    if (document.visibilityState === 'hidden') {
      retour ;
    }

    const percent = super.update() ;

    this.requestNamedAnimationFrame('SeekBar#update', () => {
      const currentTime = this.player_.ended() ?
        this.player_.duration() : this.getCurrentTime_() ;
      const liveTracker = this.player_.liveTracker ;
      let duration = this.player_.duration() ;

      if (liveTracker && liveTracker.isLive()) {
        duration = this.player_.liveTracker.liveCurrentTime() ;
      }

      if (this.percent_ !== percent) {
        // Valeur lisible par la machine de la barre de progression (pourcentage d'achèvement)
        this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)) ;
        this.percent_ = percent ;
      }

      if (this.currentTime_ !== currentTime || this.duration_ !== duration) {
        // valeur lisible par l'homme de la barre de progression (temps écoulé)
        this.el_.setAttribute(
          aria-valuetext",
          this.localize(
            'progress bar timing : currentTime={1} duration={2}',
            [formatTime(currentTime, duration),
              formatTime(duration, duration)],
            "{1} de {2}
          )
        ) ;

        this.currentTime_ = currentTime ;
        this.duration_ = duration ;
      }

      // mettre à jour l'info-bulle de la barre de progression avec l'heure actuelle
      if (this.bar) {
        this.bar.update(Dom.getBoundingClientRect(this.el()), this.getProgress()) ;
      }
    }) ;

    le pourcentage de retour ;
  }

  /**
   * Empêcher liveThreshold de donner l'impression aux recherches qu'elles sont en cours
   * ne se produisent pas du point de vue de l'utilisateur.
   *
   * @param {number} ct
   *        le moment actuel de chercher à
   */
  userSeek_(ct) {
    if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
      this.player_.liveTracker.nextSeekedFromUser() ;
    }

    this.player_.currentTime(ct) ;
  }

  /**
   * Permet d'obtenir la valeur de l'heure actuelle, mais permet un nettoyage en douceur,
   * lorsque le joueur ne peut pas suivre.
   *
   * @return {number}
   *         Valeur de l'heure actuelle à afficher
   *
   * @private
   */
  getCurrentTime_() {
    return (this.player_.scrubbing()) ?
      this.player_.getCache().currentTime :
      this.player_.currentTime() ;
  }

  /**
   * Obtenir le pourcentage de médias lus jusqu'à présent.
   *
   * @return {number}
   *         Le pourcentage de médias lus jusqu'à présent (0 à 1).
   */
  getPercent() {
    const currentTime = this.getCurrentTime_() ;
    laisser pour cent ;
    const liveTracker = this.player_.liveTracker ;

    if (liveTracker && liveTracker.isLive()) {
      pourcentage = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow() ;

      // empêche le pourcentage de changer au niveau du bord vivant
      if (liveTracker.atLiveEdge()) {
        pourcentage = 1 ;
      }
    } else {
      pourcentage = currentTime / this.player_.duration() ;
    }

    le pourcentage de retour ;
  }

  /**
   * Manipuler la souris vers le bas sur la barre de recherche
   *
   * @param {EventTarget~Event} event
   *        L'événement `mousedown` qui a provoqué l'exécution de ce programme.
   *
   * @listens mousedown
   */
  handleMouseDown(event) {
    if (!Dom.isSingleLeftClick(event)) {
      retour ;
    }

    // Arrêter la propagation des événements pour éviter les doubles déclenchements dans progress-control.js
    event.stopPropagation() ;

    this.videoWasPlaying = !this.player_.paused() ;
    ce.joueur_.pause() ;

    super.handleMouseDown(event) ;
  }

  /**
   * Gérer le déplacement de la souris sur la barre de recherche
   *
   * @param {EventTarget~Event} event
   *        L'événement `mousemove` qui a provoqué l'exécution de ce programme.
   * @param {boolean} mouseDown c'est un drapeau qui doit être mis à true si `handleMouseMove` est appelé directement. Cela nous permet d'ignorer les choses qui ne devraient pas se produire si elles proviennent de la souris vers le bas, mais qui devraient se produire avec un gestionnaire de mouvement de souris normal. La valeur par défaut est false
   *
   * @listens mousemove
   */
  handleMouseMove(event, mouseDown = false) {
    if (!Dom.isSingleLeftClick(event)) {
      retour ;
    }

    if (!mouseDown && !this.player_.scrubbing()) {
      this.player_.scrubbing(true) ;
    }

    let newTime ;
    const distance = this.calculateDistance(event) ;
    const liveTracker = this.player_.liveTracker ;

    if (!liveTracker || !liveTracker.isLive()) {
      newTime = distance * this.player_.duration() ;

      // Ne pas laisser la vidéo se terminer pendant le nettoyage.
      if (newTime === this.player_.duration()) {
        newTime = newTime - 0.1 ;
      }
    } else {

      if (distance >= 0.99) {
        liveTracker.seekToLiveEdge() ;
        retour ;
      }
      const seekableStart = liveTracker.seekableStart() ;
      const seekableEnd = liveTracker.liveCurrentTime() ;

      newTime = seekableStart + (distance * liveTracker.liveWindow()) ;

      // Ne pas laisser la vidéo se terminer pendant le nettoyage.
      if (newTime >= seekableEnd) {
        newTime = seekableEnd ;
      }

      // Compenser les différences de précision pour que currentTime ne soit pas inférieur
      // que le début de la recherche
      if (newTime <= seekableStart) {
        newTime = seekableStart + 0.1 ;
      }

      // Sur android, seekableEnd peut parfois être Infinity,
      // ceci aura pour effet de donner à newTime la valeur Infinity, ce qui est
      // ce n'est pas une heure courante valide.
      if (newTime === Infinity) {
        retour ;
      }
    }

    // Régler la nouvelle heure (dire au joueur de chercher la nouvelle heure)
    this.userSeek_(newTime) ;
  }

  enable() {
    super.enable() ;
    const mouseTimeDisplay = this.getChild('mouseTimeDisplay') ;

    if (!mouseTimeDisplay) {
      retour ;
    }

    mouseTimeDisplay.show() ;
  }

  disable() {
    super.disable() ;
    const mouseTimeDisplay = this.getChild('mouseTimeDisplay') ;

    if (!mouseTimeDisplay) {
      retour ;
    }

    mouseTimeDisplay.hide() ;
  }

  /**
   * Manipuler la souris vers le haut sur la barre de recherche
   *
   * @param {EventTarget~Event} event
   *        L'événement `mouseup` qui a provoqué l'exécution de ce programme.
   *
   * @listens mouseup
   */
  handleMouseUp(event) {
    super.handleMouseUp(event) ;

    // Arrêter la propagation des événements pour éviter les doubles déclenchements dans progress-control.js
    if (event) {
      event.stopPropagation() ;
    }
    this.player_.scrubbing(false) ;

    /**
     * Déclencher timeupdate parce que nous avons fini de chercher et que l'heure a changé.
     * Cette fonction est particulièrement utile lorsque le lecteur est mis en pause pour chronométrer l'affichage de l'heure.
     *
     * @event Tech#timeupdate
     * @type {EventTarget~Event}
     */
    this.player_.trigger({ type : 'timeupdate', target : this, manuallyTriggered : true }) ;
    if (this.videoWasPlaying) {
      silencePromesse(this.player_.play()) ;
    } else {
      // Nous avons fini de chercher et le temps a changé.
      // Si le lecteur est en pause, il faut s'assurer d'afficher la bonne heure dans la barre de recherche.
      this.update_() ;
    }
  }

  /**
   * Avance rapide plus rapide pour les utilisateurs de clavier uniquement
   */
  stepForward() {
    this.userSeek_(this.player_.currentTime() + STEP_SECONDS) ;
  }

  /**
   * Remonter plus rapidement en arrière pour les utilisateurs de clavier uniquement
   */
  stepBack() {
    this.userSeek_(this.player_.currentTime() - STEP_SECONDS) ;
  }

  /**
   * Bascule l'état de lecture du lecteur
   * Cette fonction est appelée lorsque l'entrée ou l'espace est utilisé dans la barre de recherche
   *
   * @param {EventTarget~Event} event
   *        L'événement `keydown` qui a provoqué l'appel de cette fonction
   *
   */
  handleAction(event) {
    if (this.player_.paused()) {
      this.player_.play() ;
    } else {
      ce.joueur_.pause() ;
    }
  }

  /**
   * Appelé lorsque cette SeekBar est au centre de l'attention et qu'une touche est enfoncée.
   * Prend en charge les clés suivantes :
   *
   *   L'espace ou la touche Entrée déclenche un événement de clic
   *   La touche Home permet de revenir au début de la ligne de temps
   *   La touche de fin permet de se déplacer à la fin de la ligne de temps
   *   Les touches des chiffres "0" à "9" se déplacent vers 0%, 10% ... 80 %, 90 % du calendrier
   *   La touche PageDown permet de reculer d'un pas plus important que la touche ArrowDown
   *   La touche PageUp permet d'avancer d'un grand pas
   *
   * @param {EventTarget~Event} event
   *        L'événement `keydown` qui a provoqué l'appel de cette fonction.
   *
   * @listens keydown
   */
  handleKeyDown(event) {
    const liveTracker = this.player_.liveTracker ;

    if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
      event.preventDefault() ;
      event.stopPropagation() ;
      this.handleAction(event) ;
    } else if (keycode.isEventKey(event, 'Home')) {
      event.preventDefault() ;
      event.stopPropagation() ;
      this.userSeek_(0) ;
    } else if (keycode.isEventKey(event, 'End')) {
      event.preventDefault() ;
      event.stopPropagation() ;
      if (liveTracker && liveTracker.isLive()) {
        this.userSeek_(liveTracker.liveCurrentTime()) ;
      } else {
        this.userSeek_(this.player_.duration()) ;
      }
    else if (/^[0-9]$/.test(keycode(event))) {
      event.preventDefault() ;
      event.stopPropagation() ;
      const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0 ;

      if (liveTracker && liveTracker.isLive()) {
        this.userSeek_(liveTracker.seekableStart() + (liveTracker.liveWindow() * gotoFraction)) ;
      } else {
        this.userSeek_(this.player_.duration() * gotoFraction) ;
      }
    } else if (keycode.isEventKey(event, 'PgDn')) {
      event.preventDefault() ;
      event.stopPropagation() ;
      this.userSeek_(this.player_.currentTime() - (STEP_SECONDS * PAGE_KEY_MULTIPLIER)) ;
    } else if (keycode.isEventKey(event, 'PgUp')) {
      event.preventDefault() ;
      event.stopPropagation() ;
      this.userSeek_(this.player_.currentTime() + (STEP_SECONDS * PAGE_KEY_MULTIPLIER)) ;
    } else {
      // Transmettre la gestion des touches non prises en charge vers le haut
      super.handleKeyDown(event) ;
    }
  }

  dispose() {
    this.disableInterval_() ;

    this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update) ;
    if (this.player_.liveTracker) {
      this.off(this.player_.liveTracker, 'liveedgechange', this.update) ;
    }

    this.off(this.player_, ['playing'], this.enableIntervalHandler_) ;
    this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_) ;

    // il n'est pas nécessaire de mettre à jour la progression de la lecture si le document est caché,
    // également, cela provoque un pic du CPU et éventuellement un plantage de la page sur IE11.
    if ("hidden" dans le document && "visibilityState" dans le document) {
      this.off(document, 'visibilitychange', this.toggleVisibility_) ;
    }

    super.dispose() ;
  }
}

/**
 * Options par défaut pour la `SeekBar` (barre de recherche)
 *
 * @type {Objet}
 * @private
 */
SeekBar.prototype.options_ = {
  enfants : [
    loadProgressBar",
    playProgressBar' (barre de progression)
  ],
  barName : 'playProgressBar'
};

// Les infobulles MouseTimeDisplay ne doivent pas être ajoutées à un lecteur sur les appareils mobiles
if (!IS_IOS && !IS_ANDROID) {
  SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay') ;
}

Component.registerComponent('SeekBar', SeekBar) ;
exporter la SeekBar par défaut ;