import Component from './component.js' ;
import mergeOptions from './utils/merge-options.js' ;
import document from 'global/document' ;
import * as browser from './utils/browser.js' ;
import window from 'global/window' ;
import * as Fn from './utils/fn.js' ;

const defaults = {
  seuil de suivi : 20,
  liveTolerance : 15
};

/*
  suivre le moment où nous sommes au bord de la scène, et d'autres aides pour la lecture en direct */

/**
 * Une classe permettant de vérifier l'heure en direct et de déterminer quand le joueur
 * est au niveau ou derrière le bord vivant.
 */
class LiveTracker extends Component {

  /**
   * 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.
   *
   * @param {number} [options.trackingThreshold=20]
   *        Nombre de secondes de la fenêtre en direct (seekableEnd - seekableStart) que
   *        doit avoir avant que le liveui ne soit diffusé.
   *
   * @param {number} [options.liveTolerance=15]
   *        Nombre de secondes de retard par rapport au direct que nous devons avoir
   *        avant d'être considéré comme non vivant. Notez que cela n'aura pour effet que de
   *        être utilisé pour jouer sur le bord de la scène. Cela permet d'obtenir de grandes extrémités recherchables
   *        les modifications n'ont pas d'incidence sur le fait que nous soyons en direct ou non.
   */
  constructor(player, options) {
    // LiveTracker n'a pas besoin d'élément
    const options_ = mergeOptions(defaults, options, {createEl : false}) ;

    super(player, options_) ;

    this.handleVisibilityChange_ = (e) => this.handleVisibilityChange(e) ;
    this.trackLiveHandler_ = () => this.trackLive_() ;
    this.handlePlay_ = (e) => this.handlePlay(e) ;
    this.handleFirstTimeupdate_ = (e) => this.handleFirstTimeupdate(e) ;
    this.handleSeeked_ = (e) => this.handleSeeked(e) ;
    this.seekToLiveEdge_ = (e) => this.seekToLiveEdge(e) ;

    this.reset_() ;

    this.on(this.player_, 'durationchange', (e) => this.handleDurationchange(e)) ;
    // nous devrions essayer de basculer le suivi sur canplay comme les moteurs de lecture natifs, comme Safari
    // peut ne pas avoir les bonnes valeurs pour des choses comme seekableEnd jusqu'à ce moment-là
    this.on(this.player_, 'canplay', () => this.toggleTracking()) ;

    // nous n'avons pas besoin de suivre la lecture en direct si le document est caché,
    // également, le suivi de l'occultation du document peut
    // provoquent un pic d'activité de l'unité centrale et finissent par faire planter la page sur IE11.
    if (browser.IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
      this.on(document, 'visibilitychange', this.handleVisibilityChange_) ;
    }
  }

  /**
   * suivi des bascules en fonction de la visibilité du document
   */
  handleVisibilityChange() {
    if (this.player_.duration() !== Infinity) {
      retour ;
    }

    if (document.hidden) {
      this.stopTracking() ;
    } else {
      this.startTracking() ;
    }
  }

  /**
   * toutes les fonctionnalités permettant de suivre les changements de fin de recherche
   * et pour savoir à quelle distance de la fin de la recherche nous devrions nous trouver
   */
  trackLive_() {
    const seekable = this.player_.seekable() ;

    // Sauter les éléments recherchés indéfinis
    if (!seekable || !seekable.length) {
      retour ;
    }

    const newTime = Number(window.performance.now().toFixed(4)) ;
    const deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000 ;

    this.lastTime_ = newTime ;

    this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime ;

    const liveCurrentTime = this.liveCurrentTime() ;
    const currentTime = this.player_.currentTime() ;

    // nous sommes derrière le direct si l'un d'entre eux est vrai
    // 1. le lecteur est en pause
    // 2. l'utilisateur a recherché un lieu situé à 2 secondes de l'endroit où il se trouve
    // 3. la différence entre l'heure réelle et l'heure actuelle est plus importante
    // liveTolerance dont la valeur par défaut est de 15s
    let isBehind = this.player_.paused() || this.seekedBehindLive_ ||
      Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance ;

    // nous ne pouvons pas être en retard si
    // 1. jusqu'à ce que nous n'ayons pas encore vu de timeupdate
    // 2. liveCurrentTime est Infinity, ce qui se produit sur Android et Native Safari
    if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
      isBehind = false ;
    }

    if (isBehind !== this.behindLiveEdge_) {
      this.behindLiveEdge_ = isBehind ;
      this.trigger('liveedgechange') ;
    }
  }

  /**
   * gérer un événement de changement de durée sur le joueur
   * et démarrer/arrêter le suivi en conséquence.
   */
  handleDurationchange() {
    this.toggleTracking() ;
  }

  /**
   * démarrage/arrêt du suivi
   */
  toggleTracking() {
    if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
      if (this.player_.options_.liveui) {
        this.player_.addClass('vjs-liveui') ;
      }
      this.startTracking() ;
    } else {
      this.player_.removeClass('vjs-liveui') ;
      this.stopTracking() ;
    }
  }

  /**
   * début du suivi lecture en direct
   */
  startTracking() {
    if (this.isTracking()) {
      retour ;
    }

    // Si nous n'avons pas vu de timeupdate, nous devons vérifier si le playback
    // a commencé avant que ce composant ne commence le suivi. Cela peut se produire couramment
    // lors de l'utilisation de l'autoplay.
    if (!this.timeupdateSeen_) {
      this.timeupdateSeen_ = this.player_.hasStarted() ;
    }

    this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, Fn.UPDATE_REFRESH_INTERVAL) ;
    this.trackLive_() ;

    this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_) ;

    if (!this.timeupdateSeen_) {
      this.one(this.player_, 'play', this.handlePlay_) ;
      this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_) ;
    } else {
      this.on(this.player_, 'seeked', this.handleSeeked_) ;
    }
  }

  /**
   * gérer la première mise à jour temporelle du lecteur s'il n'était pas déjà en train de jouer
   * date à laquelle le live tracker a commencé à suivre.
   */
  handleFirstTimeupdate() {
    this.timeupdateSeen_ = true ;
    this.on(this.player_, 'seeked', this.handleSeeked_) ;
  }

  /**
   * Gardez la trace de l'heure à laquelle une recherche commence, et écoutez les recherches
   * pour trouver la fin d'une recherche.
   */
  handleSeeked() {
    const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime()) ;

    this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2 ;
    this.nextSeekedFromUser_ = false ;
    this.trackLive_() ;
  }

  /**
   * gérer le premier jeu sur le joueur, et s'assurer que nous recherchons
   * jusqu'au bord de l'eau.
   */
  handlePlay() {
    this.one(this.player_, 'timeupdate', this.seekToLiveEdge_) ;
  }

  /**
   * Arrêter le suivi et mettre toutes les variables internes à
   * leur valeur initiale.
   */
  reset_() {
    this.lastTime_ = -1 ;
    this.pastSeekEnd_ = 0 ;
    this.lastSeekEnd_ = -1 ;
    this.behindLiveEdge_ = true ;
    this.timeupdateSeen_ = false ;
    this.seekedBehindLive_ = false ;
    this.nextSeekedFromUser_ = false ;

    this.clearInterval(this.trackingInterval_) ;
    this.trackingInterval_ = null ;

    this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_) ;
    this.off(this.player_, 'seeked', this.handleSeeked_) ;
    this.off(this.player_, 'play', this.handlePlay_) ;
    this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_) ;
    this.off(this.player_, 'timeupdate', this.seekToLiveEdge_) ;
  }

  /**
   * L'événement recherché suivant est celui de l'utilisateur. Cela signifie que toute recherche
   * > les 2 derrière le direct seront considérés comme derrière le direct pour de vrai et les 2 derrière le direct pour de vrai
   * liveTolerance sera ignorée.
   */
  nextSeekedFromUser() {
    this.nextSeekedFromUser_ = true ;
  }

  /**
   * arrêter le suivi lecture en direct
   */
  stopTracking() {
    if (!this.isTracking()) {
      retour ;
    }
    this.reset_() ;
    this.trigger('liveedgechange') ;
  }

  /**
   * Une aide pour obtenir la fin recherchable du joueur
   * afin de ne pas avoir à vérifier les nullités partout
   *
   * @return {number}
   *         L'extrémité la plus éloignée ou l'infini.
   */
  seekableEnd() {
    const seekable = this.player_.seekable() ;
    const seekableEnds = [] ;
    let i = seekable ? seekable.length : 0 ;

    while (i--) {
      seekableEnds.push(seekable.end(i)) ;
    }

    // saisir l'extrémité cherchable la plus éloignée après le tri, ou s'il n'y en a pas
    // par défaut à l'infini
    return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : L'infini ;
  }

  /**
   * Une aide pour obtenir le début de la recherche du joueur
   * afin de ne pas avoir à vérifier les nullités partout
   *
   * @return {number}
   *         Le début de la recherche au plus tôt ou 0.
   */
  seekableStart() {
    const seekable = this.player_.seekable() ;
    const seekableStarts = [] ;
    let i = seekable ? seekable.length : 0 ;

    while (i--) {
      seekableStarts.push(seekable.start(i)) ;
    }

    // prend le premier départ cherchable après le tri, ou s'il n'y en a pas
    // la valeur par défaut est 0
    return seekableStarts.length ? seekableStarts.sort()[0] : 0 ;
  }

  /**
   * Obtenir la fenêtre de temps en direct aka
   * le temps écoulé entre le début de la recherche et la fin de la recherche
   * l'heure actuelle en direct.
   *
   * @return {number}
   *         Le nombre de secondes qui peuvent être recherchées dans
   *         la vidéo en direct.
   */
  liveWindow() {
    const liveCurrentTime = this.liveCurrentTime() ;

    // si liveCurrenTime est Infinity alors nous n'avons pas de liveWindow du tout
    if (liveCurrentTime === Infinity) {
      retour 0 ;
    }

    return liveCurrentTime - this.seekableStart() ;
  }

  /**
   * Détermine si le lecteur est en direct, vérifie uniquement si ce composant
   * le suivi de la lecture en direct ou non
   *
   * @return {boolean}
   *         Si liveTracker suit
   */
  isLive() {
    return this.isTracking() ;
  }

  /**
   * Détermine si currentTime se trouve au bord de l'eau et ne risque pas de prendre du retard
   * sur chaque changement possible
   *
   * @return {boolean}
   *         Si la lecture se fait au bord de l'eau
   */
  atLiveEdge() {
    return !this.behindLiveEdge() ;
  }

  /**
   * obtenir ce que nous attendons de l'heure actuelle en direct
   *
   * @return {number}
   *         L'heure actuelle prévue en direct
   */
  liveCurrentTime() {
    return this.pastSeekEnd() + this.seekableEnd() ;
  }

  /**
   * Le nombre de secondes qui se sont écoulées après la fin de la recherche
   * a changé. Celui-ci sera remis à 0 une fois que la fin recherchée aura changé.
   *
   * @return {number}
   *         Secondes après la fin de la période de recherche en cours
   */
  pastSeekEnd() {
    const seekableEnd = this.seekableEnd() ;

    if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
      this.pastSeekEnd_ = 0 ;
    }
    this.lastSeekEnd_ = seekableEnd ;
    return this.pastSeekEnd_ ;
  }

  /**
   * Si nous sommes actuellement derrière le bord en direct, aka currentTime sera
   * en retard sur un changement possible
   *
   * @return {boolean}
   *         Si nous sommes derrière le bord en direct
   */
  behindLiveEdge() {
    return this.behindLiveEdge_ ;
  }

  /**
   * Si le traceur en direct est en cours de suivi ou non.
   */
  isTracking() {
    return typeof this.trackingInterval_ === 'number' ;
  }

  /**
   * Chercher le bord en direct si nous sommes derrière le bord en direct
   */
  seekToLiveEdge() {
    this.seekedBehindLive_ = false ;
    if (this.atLiveEdge()) {
      retour ;
    }
    this.nextSeekedFromUser_ = false ;
    this.player_.currentTime(this.liveCurrentTime()) ;

  }

  /**
   * Se débarrasser de liveTracker
   */
  dispose() {
    this.off(document, 'visibilitychange', this.handleVisibilityChange_) ;
    this.stopTracking() ;
    super.dispose() ;
  }
}

Component.registerComponent('LiveTracker', LiveTracker) ;
exporter le LiveTracker par défaut ;