/**
 * @file text-track.js
 */
import TextTrackCueList from './text-track-cue-list' ;
import * as Fn from '../utils/fn.js' ;
import {TextTrackKind, TextTrackMode} from './track-enums' ;
import log from '../utils/log.js' ;
import window from 'global/window' ;
import Track from './track.js' ;
import { isCrossOrigin } from '../utils/url.js' ;
import XHR de '@videojs/xhr' ;
import merge from '../utils/merge-options' ;

/**
 * Prend le contenu d'un fichier webvtt et l'analyse en signaux
 *
 * @param {string} srcContent
 *        contenu du fichier webVTT
 *
 * @param {TextTrack} track
 *        Piste de texte à laquelle ajouter des repères. Les indices proviennent du srcContent.
 *
 * @private
 */
const parseCues = function(srcContent, track) {
  const parser = new window.WebVTT.Parser(
    fenêtre,
    window.vttjs,
    window.WebVTT.StringDecoder()
  ) ;
  const errors = [] ;

  parser.oncue = function(cue) {
    track.addCue(cue) ;
  };

  parser.onparsingerror = function(error) {
    errors.push(error) ;
  };

  parser.onflush = function() {
    track.trigger({
      type : "loadeddata",
      cible : piste
    }) ;
  };

  parser.parse(srcContent) ;
  if (errors.length > 0) {
    if (window.console && window.console.groupCollapsed) {
      window.console.groupCollapsed(`Text Track parsing errors for ${track.src}`) ;
    }
    errors.forEach((error) => log.error(error)) ;
    if (window.console && window.console.groupEnd) {
      window.console.groupEnd() ;
    }
  }

  parser.flush() ;
};

/**
 * Charge un `TextTrack` à partir d'une url spécifiée.
 *
 * @param {string} src
 *        Url de chargement de la piste.
 *
 * @param {TextTrack} track
 *        Piste à laquelle ajouter des repères. Provient du contenu à la fin de `url`.
 *
 * @private
 */
const loadTrack = function(src, track) {
  const opts = {
    uri : src
  };
  const crossOrigin = isCrossOrigin(src) ;

  if (crossOrigin) {
    opts.cors = crossOrigin ;
  }

  const withCredentials = track.tech_.crossOrigin() === 'use-credentials' ;

  if (withCredentials) {
    opts.withCredentials = withCredentials ;
  }

  XHR(opts, Fn.bind(this, function(err, response, responseBody) {
    if (err) {
      return log.error(err, response) ;
    }

    track.loaded_ = true ;

    // S'assurer que vttjs a été chargé, sinon attendre qu'il ait fini de se charger
    // NOTE : ceci n'est utilisé que pour la construction de alt/video.novtt.js
    if (typeof window.WebVTT !== 'function') {
      if (track.tech_) {
        // pour empêcher l'utilisation avant l'erreur eslint, nous définissons loadHandler
        // en tant que let ici
        track.tech_.any(['vttjsloaded', 'vttjserror'], (event) => {
          if (event.type === 'vttjserror') {
            log.error(`vttjs failed to load, stopping trying to process ${track.src}`) ;
            retour ;
          }
          return parseCues(responseBody, track) ;
        }) ;
      }
    } else {
      parseCues(responseBody, track) ;
    }

  })) ;
};

/**
 * Une représentation d'une seule `TextTrack`.
 *
 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
 * @extends Track
 */
class TextTrack extends Track {

  /**
   * Créer une instance de cette classe.
   *
   * @param {Objet} options={}
   *        Objet de noms et de valeurs d'options
   *
   * @param {Tech} options.tech
   *        Une référence au technicien qui possède ce TextTrack.
   *
   * @param {TextTrack~Kind} [options.kind='subtitles']
   *        Un type de texte valide.
   *
   * @param {TextTrack~Mode} [options.mode='disabled']
   *        Un mode de suivi de texte valide.
   *
   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
   *        Un identifiant unique pour ce TextTrack.
   *
   * @param {string} [options.label='']
   *        L'étiquette du menu pour cette piste.
   *
   * @param {string} [options.language='']
   *        Un code linguistique valide à deux caractères.
   *
   * @param {string} [options.srclang='']
   *        Un code linguistique valide à deux caractères. Une alternative, mais dépourvue de priorité
   *        version de `options.language`
   *
   * @param {string} [options.src]
   *        Une url vers les indices TextTrack.
   *
   * @param {boolean} [options.default]
   *        Si cette piste doit être activée ou désactivée par défaut.
   */
  constructor(options = {}) {
    if (!options.tech) {
      throw new Error('Un tech n'a pas été fourni.') ;
    }

    const settings = merge(options, {
      genre : TextTrackKind[options.kind] || 'subtitles',
      language : options.language || options.srclang || ''
    }) ;
    let mode = TextTrackMode[settings.mode] || 'disabled' ;
    const default_ = settings.default ;

    if (settings.kind === 'metadata' || settings.kind === 'chapters') {
      mode = "caché" ;
    }
    super(settings) ;

    this.tech_ = settings.tech ;

    this.cues_ = [] ;
    this.activeCues_ = [] ;

    this.preload_ = this.tech_.preloadTextTracks !== false ;

    const cues = new TextTrackCueList(this.cues_) ;
    const activeCues = new TextTrackCueList(this.activeCues_) ;
    let changed = false ;

    this.timeupdateHandler = Fn.bind(this, function(event = {})) {
      if (this.tech_.isDisposed()) {
        retour ;
      }

      if (!this.tech_.isReady_) {
        if (event.type !== 'timeupdate') {
          this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler) ;
        }

        retour ;
      }

      // Accès à this.activeCues pour les effets secondaires de sa propre mise à jour
      // en raison de sa nature de fonction getter. Ne pas supprimer ou les signaux seront
      // arrêter la mise à jour !
      // Utiliser le setter pour empêcher la suppression de l'uglify (règle des pure_getters)
      this.activeCues = this.activeCues ;
      if (changed) {
        this.trigger('cuechange') ;
        changed = false ;
      }

      if (event.type !== 'timeupdate') {
        this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler) ;
      }

    }) ;

    const disposeHandler = () => {
      this.stopTracking() ;
    };

    this.tech_.one('dispose', disposeHandler) ;
    if (mode !== 'disabled') {
      this.startTracking() ;
    }

    Object.defineProperties(this, {
      /**
       * @membre de TextTrack
       * @member {boolean} default
       *         Si cette piste a été activée ou désactivée par défaut. Ne peut être modifié après
       *         création.
       * @instance
       *
       * en lecture seule
       */
      par défaut : {
        get() {
          retour default_ ;
        },
        set() {}
      },

      /**
       * @membre de TextTrack
       * @member {string} mode
       *         Définit le mode de ce TextTrack à un {@link TextTrack~Mode} valide. Volonté
       *         n'est pas activée si elle est réglée sur un mode non valide.
       * @instance
       *
       * @fires TextTrack#modechange
       */
      mode : {
        get() {
          mode de retour ;
        },
        set(newMode) {
          if (!TextTrackMode[newMode]) {
            retour ;
          }
          if (mode === newMode) {
            retour ;
          }

          mode = newMode ;
          if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
            // Chargement à la demande.
            loadTrack(this.src, this) ;
          }
          this.stopTracking() ;

          if (mode !== 'disabled') {
            this.startTracking() ;
          }
          /**
           * Un événement qui se déclenche lorsque le mode change sur cette piste. Cela permet
           * la TextTrackList qui contient cette piste pour agir en conséquence.
           *
           * > Remarque : Cela ne fait pas partie de la spécification !
           *
           * @event TextTrack#modechange
           * @type {EventTarget~Event}
           */
          this.trigger('modechange') ;

        }
      },

      /**
       * @membre de TextTrack
       * @member {TextTrackCueList} cues
       *         La liste de repères de la piste de texte pour cette TextTrack.
       * @instance
       */
      cues : {
        get() {
          if (!this.loaded_) {
            retourner null ;
          }

          les indices de retour ;
        },
        set() {}
      },

      /**
       * @membre de TextTrack
       * @member {TextTrackCueList} activeCues
       *         La liste des repères de la piste de texte qui sont actuellement actifs pour cette piste de texte.
       * @instance
       */
      activeCues : {
        get() {
          if (!this.loaded_) {
            retourner null ;
          }

          // rien à faire
          if (this.cues.length === 0) {
            return activeCues ;
          }

          const ct = this.tech_.currentTime() ;
          const active = [] ;

          for (let i = 0, l = this.cues.length ; i < l ; i++) {
            const cue = this.cues[i] ;

            if (cue.startTime <= ct && cue.endTime >= ct) {
              active.push(cue) ;
            } else if (cue.startTime === cue.endTime &&
                       cue.startTime <= ct &&
                       cue.startTime + 0.5 >= ct) {
              active.push(cue) ;
            }
          }

          changed = false ;

          if (active.length !== this.activeCues_.length) {
            changé = vrai ;
          } else {
            for (let i = 0 ; i < active.length ; i++) {
              if (this.activeCues_.indexOf(active[i]) === -1) {
                changé = vrai ;
              }
            }
          }

          this.activeCues_ = active ;
          activeCues.setCues_(this.activeCues_) ;

          return activeCues ;
        },

        // /!\N- Garder ce setter vide (voir le gestionnaire timeupdate ci-dessus)
        set() {}
      }
    }) ;

    if (settings.src) {
      this.src = settings.src ;
      if (!this.preload_) {
        // Les pistes seront chargées à la demande.
        // Agir comme si nous étions chargés à d'autres fins.
        this.loaded_ = true ;
      }
      if (this.preload_ || (settings.kind !== 'subtitles' && settings.kind !== 'captions')) {
        loadTrack(this.src, this) ;
      }
    } else {
      this.loaded_ = true ;
    }
  }

  startTracking() {
    // Indices plus précis basés sur requestVideoFrameCallback avec un fallback requestAnimationFram
    this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler) ;
    // Écouter également timeupdate au cas où rVFC/rAF s'arrêterait (fenêtre en arrière-plan, audio dans video el)
    this.tech_.on('timeupdate', this.timeupdateHandler) ;
  }

  stopTracking() {
    if (this.rvf_) {
      this.tech_.cancelVideoFrameCallback(this.rvf_) ;
      this.rvf_ = undefined ;
    }
    this.tech_.off('timeupdate', this.timeupdateHandler) ;
  }

  /**
   * Ajouter un repère à la liste interne des repères.
   *
   * @param {TextTrack~Cue} cue
   *        Le signal à ajouter à notre liste interne
   */
  addCue(originalCue) {
    let cue = originalCue ;

    if (window.vttjs && !(originalCue instanceof window.vttjs.VTTCue)) {
      cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text) ;

      for (const prop in originalCue) {
        if ( !(prop in cue)) {
          cue[prop] = originalCue[prop] ;
        }
      }

      // s'assurer que `id` est bien copié
      cue.id = originalCue.id ;
      cue.originalCue_ = originalCue ;
    }

    const tracks = this.tech_.textTracks() ;

    for (let i = 0 ; i < tracks.length ; i++) {
      if (tracks[i] !== this) {
        tracks[i].removeCue(cue) ;
      }
    }

    this.cues_.push(cue) ;
    this.cues.setCues_(this.cues_) ;
  }

  /**
   * Retirer un indice de notre liste interne
   *
   * @param {TextTrack~Cue} removeCue
   *        L'indice à retirer de notre liste interne
   */
  removeCue(removeCue) {
    let i = this.cues_.length ;

    while (i--) {
      const cue = this.cues_[i] ;

      if (cue === removeCue || (cue.originalCue_ && cue.originalCue_ === removeCue)) {
        this.cues_.splice(i, 1) ;
        this.cues.setCues_(this.cues_) ;
        pause ;
      }
    }
  }
}

/**
 * cuechange - Un ou plusieurs repères de la piste sont devenus actifs ou ont cessé de l'être.
 */
TextTrack.prototype.allowedEvents_ = {
  cuechange : 'cuechange'
};

exporter la piste de texte par défaut ;