import window from 'global/window' ;
import document from 'global/document' ;
import mergeOptions from '../utils/merge-options' ;
import {getAbsoluteURL} de '../utils/url' ;

/**
 * Cette fonction est utilisée pour déclencher un jeu de sources lorsqu'il y a quelque chose à faire
 * similaire à l'appel de `mediaEl.load()`. Il essaiera de trouver la source via
 * l'attribut `src` puis les éléments `<source>`. Il lancera alors `sourceset`
 * avec la source trouvée ou une chaîne vide si nous ne pouvons pas savoir. S'il ne peut pas
 * ne trouve pas de source, alors `sourceset` ne sera pas déclenché.
 *
 * @param {Html5} tech
 *        L'objet technique sur lequel le sourceset a été configuré
 *
 * @return {boolean}
 *         renvoie false si le jeu de sources n'a pas été déclenché et true dans le cas contraire.
 */
const sourcesetLoad = (tech) => {
  const el = tech.el() ;

  // si `el.src` est défini, cette source sera chargée.
  if (el.hasAttribute('src')) {
    tech.triggerSourceset(el.src) ;
    retourner vrai ;
  }

  /**
   * Comme il n'y a pas de propriété src sur l'élément media, les éléments source seront utilisés pour les éléments
   * la mise en œuvre de l'algorithme de sélection des sources. Cela se produit de manière asynchrone et
   * dans la plupart des cas où il y a plus d'une source, nous ne pouvons pas dire quelle source sera utilisée
   * être chargée, sans réimplémenter l'algorithme de sélection de la source. Pour l'instant, nous ne sommes pas
   * va le faire. Il existe cependant trois cas particuliers que nous traitons ici :
   *
   * 1. S'il n'y a pas de sources, ne pas lancer `sourceset`.
   * 2. S'il n'y a qu'une seule `<source>` avec une propriété/attribut `src` qui est notre `src`
   * 3. S'il y a plus d'un `<source>` mais que tous ont la même url `src`.
   *    Ce sera notre src.
   */
  const sources = tech.$$('source') ;
  const srcUrls = [] ;
  let src = '' ;

  // s'il n'y a pas de sources, ne pas déclencher le jeu de sources
  if (!sources.length) {
    retourner faux ;
  }

  // ne compte que les éléments sources valides/non dupliqués
  for (let i = 0 ; i < sources.length ; i++) {
    const url = sources[i].src ;

    if (url && srcUrls.indexOf(url) === -1) {
      srcUrls.push(url) ;
    }
  }

  // il n'y avait pas de sources valables
  if (!srcUrls.length) {
    retourner faux ;
  }

  // il n'y a qu'un seul élément source valide url
  // utiliser cela
  if (srcUrls.length === 1) {
    src = srcUrls[0] ;
  }

  tech.triggerSourceset(src) ;
  retourner vrai ;
};

/**
 * notre implémentation d'un descripteur `innerHTML` pour les navigateurs
 * qui n'en ont pas.
 */
const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  get() {
    return this.cloneNode(true).innerHTML ;
  },
  set(v) {
    // créer un nœud factice pour utiliser innerHTML
    const dummy = document.createElement(this.nodeName.toLowerCase()) ;

    // fixer la valeur de innerHTML à la valeur fournie
    dummy.innerHTML = v ;

    // créer un fragment de document pour contenir les nœuds de dummy
    const docFrag = document.createDocumentFragment() ;

    // copie tous les nœuds créés par le innerHTML sur dummy
    // au fragment de document
    while (dummy.childNodes.length) {
      docFrag.appendChild(dummy.childNodes[0]) ;
    }

    // supprimer le contenu
    this.innerText = '' ;

    // maintenant nous ajoutons tout ce html en un seul en ajoutant la balise
    // fragment de document. Voici comment innerHTML procède.
    window.Element.prototype.appendChild.call(this, docFrag) ;

    // puis renvoie le résultat que le setter de innerHTML aurait obtenu
    return this.innerHTML ;
  }
}) ;

/**
 * Obtenir un descripteur de propriété à partir d'une liste de priorités et de l'option
 * à obtenir.
 */
const getDescriptor = (priority, prop) => {
  let descripteur = {} ;

  for (let i = 0 ; i < priority.length ; i++) {
    descriptor = Object.getOwnPropertyDescriptor(priority[i], prop) ;

    if (descriptor && descriptor.set && descriptor.get) {
      pause ;
    }
  }

  descriptor.enumerable = true ;
  descriptor.configurable = true ;

  retourner le descripteur ;
};

const getInnerHTMLDescriptor = (tech) => getDescriptor([
  tech.el(),
  window.HTMLMediaElement.prototype,
  window.Element.prototype,
  Polyfill de descripteur HTML interne
], 'innerHTML') ;

/**
 * Les fonctions internes du navigateur sont corrigées afin que nous puissions dire de manière synchrone
 * si un `<source>` a été ajouté à l'élément multimédia. Pour une raison ou une autre, cette
 * provoque un `sourceset` si l'élément média est prêt et n'a pas de source.
 * C'est le cas lorsque
 * - La page vient d'être chargée et l'élément média n'a pas de source.
 * - L'élément média a été vidé de toutes ses sources, puis `load()` a été appelé.
 *
 * Pour ce faire, il corrige les fonctions/propriétés suivantes lorsqu'elles sont prises en charge :
 *
 * - `append()` - peut être utilisé pour ajouter un élément `<source>` à l'élément média
 * - `appendChild()` - peut être utilisé pour ajouter un élément `<source>` à l'élément média
 * - `insertAdjacentHTML()` - peut être utilisé pour ajouter un élément `<source>` à l'élément media
 * - `innerHTML` - peut être utilisé pour ajouter un élément `<source>` à l'élément media
 *
 * @param {Html5} tech
 *        L'objet technique sur lequel le sourceset est configuré.
 */
const firstSourceWatch = function(tech) {
  const el = tech.el() ;

  // s'assurer que firstSourceWatch n'est pas configuré deux fois.
  if (el.resetSourceWatch_) {
    retour ;
  }

  const old = {} ;
  const innerDescriptor = getInnerHTMLDescriptor(tech) ;
  const appendWrapper = (appendFn) => (...args) => {
    const retval = appendFn.apply(el, args) ;

    sourcesetLoad(tech) ;

    retourner retval ;
  };

  ['append', 'appendChild', 'insertAdjacentHTML'].forEach((k) => {
    if (!el[k]) {
      retour ;
    }

    // stocker l'ancienne fonction
    old[k] = el[k] ;

    // appeler l'ancienne fonction avec un jeu de sources si une source
    // a été chargé
    el[k] = appendWrapper(old[k]) ;
  }) ;

  Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
    set : appendWrapper(innerDescriptor.set)
  })) ;

  el.resetSourceWatch_ = () => {
    el.resetSourceWatch_ = null ;
    Object.keys(old).forEach((k) => {
      el[k] = old[k] ;
    }) ;

    Object.defineProperty(el, 'innerHTML', innerDescriptor) ;
  };

  // sur le premier jeu de sources, nous devons revenir sur nos modifications
  tech.one('sourceset', el.resetSourceWatch_) ;
};

/**
 * notre implémentation d'un descripteur `src` pour les navigateurs
 * qui n'en ont pas.
 */
const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  get() {
    if (this.hasAttribute('src')) {
      return getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src')) ;
    }

    retourner '' ;
  },
  set(v) {
    window.Element.prototype.setAttribute.call(this, 'src', v) ;

    retour v ;
  }
}) ;

const getSrcDescriptor = (tech) => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src') ;

/**
 * configurer la gestion du `sourceset` sur la technologie `Html5`. Cette fonction
 * corrige les propriétés/fonctions des éléments suivants :
 *
 * - `src` - pour déterminer quand `src` est défini
 * - `setAttribute()` - pour déterminer quand `src` est défini
 * - `load()` - cela redéclenche l'algorithme de sélection de la source, et peut
 *              provoquer un jeu de sources.
 *
 * S'il n'y a pas de source lorsque nous ajoutons le support `sourceset` ou pendant un `load()`
 * nous patchons également les fonctions listées dans `firstSourceWatch`.
 *
 * @param {Html5} tech
 *        La technologie au service de la correction
 */
const setupSourceset = function(tech) {
  if (!tech.featuresSourceset) {
    retour ;
  }

  const el = tech.el() ;

  // s'assurer que le jeu de sources n'est pas configuré deux fois.
  if (el.resetSourceset_) {
    retour ;
  }

  const srcDescriptor = getSrcDescriptor(tech) ;
  const oldSetAttribute = el.setAttribute ;
  const oldLoad = el.load ;

  Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
    set : (v) => {
      const retval = srcDescriptor.set.call(el, v) ;

      // nous utilisons le getter ici pour obtenir la valeur réelle définie sur src
      tech.triggerSourceset(el.src) ;

      retourner retval ;
    }
  })) ;

  el.setAttribute = (n, v) => {
    const retval = oldSetAttribute.call(el, n, v) ;

    if ((/src/i).test(n)) {
      tech.triggerSourceset(el.src) ;
    }

    retourner retval ;
  };

  el.load = () => {
    const retval = oldLoad.call(el) ;

    // si le chargement a été appelé, mais qu'il n'y avait pas de source à déclencher
    // sourceset on. Nous devons surveiller l'ajout d'une source
    // car cela peut déclencher un `sourceset` lorsque l'élément média
    // n'a pas de source
    if (!sourcesetLoad(tech)) {
      tech.triggerSourceset('') ;
      premièreSourceWatch(tech) ;
    }

    retourner retval ;
  };

  if (el.currentSrc) {
    tech.triggerSourceset(el.currentSrc) ;
  } else if (!sourcesetLoad(tech)) {
    premièreSourceWatch(tech) ;
  }

  el.resetSourceset_ = () => {
    el.resetSourceset_ = null ;
    el.load = oldLoad ;
    el.setAttribute = oldSetAttribute ;
    Object.defineProperty(el, 'src', srcDescriptor) ;
    if (el.resetSourceWatch_) {
      el.resetSourceWatch_() ;
    }
  };
};

export default setupSourceset ;