/**
 * @archivo plugin.js
 * /
importar eventos desde './mixins/evented';
importar con estado desde './mixins/stateful';
importar * como Eventos desde './utils/events';
importar registro desde './utils/log';
importar jugador desde './jugador';

/**
 * El nombre del complemento base.
 *
 * @privado
 * @constante
 * @tipo {cadena}
 * /
const BASE_PLUGIN_NAME = 'complemento';

/**
 * La clave en la que se almacena el caché de complementos activos de un jugador.
 *
 * @privado
 * @constante
 * @tipo {cadena}
 * /
const PLUGIN_CACHE_KEY = 'activePlugins_';

/**
 * Almacena complementos registrados en un espacio privado.
 *
 * @privado
 * @type {Objeto}
 * /
const pluginStorage = {};

/**
 * Informa si se ha registrado o no un complemento.
 *
 * @privado
 * @param {cadena} nombre
 * El nombre de un complemento.
 *
 * @return {booleano}
 * Si el complemento se ha registrado o no.
 * /
const pluginExists = (nombre) => pluginStorage.hasOwnProperty(nombre);

/**
 * Obtenga un solo complemento registrado por nombre.
 *
 * @privado
 * @param {cadena} nombre
 * El nombre de un complemento.
 *
 * @return {Función|indefinido}
 * El complemento (o indefinido).
 * /
const getPlugin = (nombre) => pluginExiste(nombre) ? pluginStorage[nombre]: indefinido;

/**
 * Marca un complemento como "activo" en un reproductor.
 *
 * Además, asegura que el reproductor tenga un objeto para rastrear complementos activos.
 *
 * @privado
 * @param {Jugador} jugador
 * Una instancia del reproductor Video.js.
 *
 * @param {cadena} nombre
 * El nombre de un complemento.
 * /
const markPluginAsActive = (jugador, nombre) => {
  jugador[PLUGIN_CACHE_KEY] = jugador[PLUGIN_CACHE_KEY] || {};
  jugador[PLUGIN_CACHE_KEY][nombre] = verdadero;
};

/**
 * Activa un par de eventos de configuración de complementos.
 *
 * @privado
 * @param {Jugador} jugador
 * Una instancia del reproductor Video.js.
 *
 * @param {Plugin~PluginEventHash} hash
 * Un hash de evento de complemento.
 *
 * @param {booleano} [antes]
 * Si es verdadero, antecede el nombre del evento con "antes". En otras palabras,
 * use esto para activar "beforepluginsetup" en lugar de "pluginsetup".
 * /
const triggerSetupEvent = (jugador, hash, antes) => {
  const eventName = (antes? 'antes': '') + 'pluginsetup';

  player.trigger(nombreEvento, hash);
  jugador.trigger(nombreEvento + ':' + hash.nombre, hash);
};

/**
 * Toma una función básica de complemento y devuelve una función contenedora que marca
 * en el reproductor que se ha activado el complemento.
 *
 * @privado
 * @param {cadena} nombre
 * El nombre del complemento.
 *
 * Complemento @param {Función}
 * El complemento básico.
 *
 * @return {Función}
 * Una función contenedora para el complemento dado.
 * /
const createBasicPlugin = función (nombre, complemento) {
  const basicPluginWrapper = function() {

    // Activamos los eventos "beforepluginsetup" y "pluginsetup" en el reproductor
    // independientemente, pero queremos que el hash sea coherente con el hash proporcionado
    // para complementos avanzados.
    //
    // Lo único potencialmente contrario a la intuición aquí es la `instancia` en
    // el evento "pluginsetup" es el valor devuelto por la función `plugin`.
    triggerSetupEvent (esto, {nombre, complemento, instancia: nulo}, verdadero);

    const instancia = plugin.apply(esto, argumentos);

    markPluginAsActive(esto, nombre);
    triggerSetupEvent(este, {nombre, complemento, instancia});

    instancia de retorno;
  };

  Object.keys(complemento).forEach(función(prop) {
    basicPluginWrapper[prop] = plugin[prop];
  });

  volver basicPluginWrapper;
};

/**
 * Toma una subclase de complemento y devuelve una función de fábrica para generar
 * instancias de la misma.
 *
 * Esta función de fábrica se reemplazará con una instancia de la solicitada
 * subclase de Complemento.
 *
 * @privado
 * @param {cadena} nombre
 * El nombre del complemento.
 *
 * @param {Complemento} Subclase de complemento
 * El complemento avanzado.
 *
 * @return {Función}
 * /
const createPluginFactory = (nombre, PluginSubClass) => {

  // Agregue una propiedad `nombre` al prototipo del complemento para que cada complemento pueda
  // referirse a sí mismo por su nombre.
  PluginSubClass.prototype.name = nombre;

  función de retorno (... argumentos) {
    triggerSetupEvent(este, {nombre, complemento: PluginSubClass, instancia: nulo}, verdadero);

    const instancia = nueva PluginSubClass(...[this, ...args]);

    // El complemento se reemplaza por una función que devuelve la instancia actual.
    este[nombre] = () => instancia;

    triggerSetupEvent(esto, instancia.getEventHash());

    instancia de retorno;
  };
};

/**
 * Clase principal para todos los complementos avanzados.
 *
 * Módulo @mixes:evented~EventedMixin
 * Módulo @mixes: stateful~StatefulMixin
 * @fires Player#beforepluginsetup
 * @fires Player#beforepluginsetup:$nombre
 * @fires jugador#pluginsetup
 * @fires Player#pluginsetup:$nombre
 * @escucha Player#dispose
 * @throws {Error}
 * Si se intenta crear una instancia de la clase base {@link Plugin}
 * directamente en lugar de a través de una subclase.
 * /
Complemento de clase {

  /**
   * Crea una instancia de esta clase.
   *
   * Las subclases deben llamar a `super` para garantizar que los complementos se inicialicen correctamente.
   *
   * @param {Jugador} jugador
   * Una instancia del reproductor Video.js.
   * /
  constructor (jugador) {
    if (this.constructor === Complemento) {
      throw new Error('El complemento debe ser subclasificado; no instanciado directamente.');
    }

    este.jugador = jugador;

    si (!este.registro) {
      this.log = this.player.log.createLogger(this.name);
    }

    // Haga que este objeto sea un evento, pero elimine el método `trigger` agregado para que podamos
    // use la versión prototipo en su lugar.
    evento(esto);
    eliminar este disparador;

    stateful(this, this.constructor.defaultState);
    markPluginAsActive(jugador, este.nombre);

    // Vincular automáticamente el método dispose para que podamos usarlo como oyente y desvincularlo
    // más tarde fácilmente.
    this.dispose = this.dispose.bind(this);

    // Si se desecha el reproductor, desechar el complemento.
    player.on('dispose', this.dispose);
  }

  /**
   * Obtenga la versión del complemento que se configuró < pluginName> .VERSIÓN
   * /
  versión() {
    devuelve este.constructor.VERSIÓN;
  }

  /**
   * Cada evento activado por complementos incluye un hash de datos adicionales con
   * propiedades convencionales.
   *
   * Esto devuelve ese objeto o muta un hash existente.
   *
   * @param {Objeto} [hash={}]
   * Un objeto para ser utilizado como evento y hash de evento.
   *
   * @return {Complemento~PluginEventHash}
   * Un objeto hash de evento con las propiedades proporcionadas mezcladas.
   * /
  getEventHash(hash = {}) {
    hash.nombre = este.nombre;
    hash.plugin = este.constructor;
    hash.instancia = esto;
    hash de retorno;
  }

  /**
   * Activa un evento en el objeto del complemento y anula
   * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
   *
   * @param {cadena|Objeto} evento
   * Un tipo de evento o un objeto con una propiedad de tipo.
   *
   * @param {Objeto} [hash={}]
   * Hash de datos adicional para fusionar con un
   * {Complemento @link~PluginEventHash|PluginEventHash}.
   *
   * @return {booleano}
   * Si se evitó o no el incumplimiento.
   * /
  disparador (evento, hash = {}) {
    return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash));
  }

  /**
   * Maneja eventos de &quot;cambio de estado&quot; en el complemento. No-op por defecto, anular por
   * subclasificación.
   *
   * @abstracto
   * @param {Evento} e
   * Un objeto de evento proporcionado por un evento de &quot;cambio de estado&quot;.
   *
   * @param {Objeto} e.cambios
   * Un objeto que describe los cambios que ocurrieron con el &quot;estado cambiado&quot;
   * evento.
   * /
  manejarEstadoCambiado(e) {}

  /**
   * Dispone de un complemento.
   *
   * Las subclases pueden anular esto si quieren, pero por razones de seguridad,
   * Probablemente sea mejor suscribirse al evento &quot;dispose&quot;.
   *
   * Complemento @fires#dispose
   * /
  disponer () {
    const {nombre, jugador} = esto;

    /**
     * Señales de que un complemento avanzado está a punto de ser eliminado.
     *
     * Complemento @event #dispose
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('dispose');
    esto.off();
    player.off('dispose', this.dispose);

    // Elimine cualquier posible fuente de pérdida de memoria limpiando
    // referencias entre el reproductor y la instancia del complemento y la anulación
    // el estado del complemento y los métodos de reemplazo con una función que lanza.
    jugador[PLUGIN_CACHE_KEY][nombre] = falso;
    este.jugador = este.estado = nulo;

    // Finalmente, reemplace el nombre del complemento en el reproductor con una nueva fábrica
    // función, para que el complemento esté listo para configurarse nuevamente.
    jugador[nombre] = createPluginFactory(nombre, pluginStorage[nombre]);
  }

  /**
   * Determina si un complemento es un complemento básico (es decir, no una subclase de `Plugin`).
   *
   * complemento @param {cadena|Función}
   * Si es una cadena, coincide con el nombre de un complemento. Si una función, será
   * probado directamente.
   *
   * @return {booleano}
   * Si un complemento es o no un complemento básico.
   * /
  isBasic estático (complemento) {
    const p = (tipo de complemento === 'cadena') ? getPlugin(complemento): complemento;

    tipo de retorno de p === 'función' && !Plugin.prototype.isPrototypeOf(p.prototype);
  }

  /**
   * Registre un complemento Video.js.
   *
   * @param {cadena} nombre
   * El nombre del plugin a registrar. Debe ser una cadena y
   * no debe coincidir con un complemento existente o un método en el &quot;Reproductor&quot;
   * prototipo.
   *
   * Complemento @param {Función}
   * Una subclase de `Plugin` o una función para complementos básicos.
   *
   * @return {Función}
   * Para complementos avanzados, una función de fábrica para ese complemento. Para
   * Complementos básicos, una función contenedora que inicializa el complemento.
   * /
  static registerPlugin(nombre, complemento) {
    if (tipo de nombre !== 'cadena') {
      throw new Error(`Nombre de complemento ilegal, &quot;${name}&quot;, debe ser una cadena, era ${typeof name}.`);
    }

    if (el complemento existe (nombre)) {
      log.warn(`Ya existe un complemento llamado &quot;${name}&quot;. ¡Es posible que desee evitar volver a registrar complementos!`);
    } else if (Jugador.prototipo.hasOwnProperty(nombre)) {
      throw new Error(`Nombre de complemento ilegal, &quot;${name}&quot;, ¡no se puede compartir un nombre con un método de reproductor existente!`);
    }

    if (tipo de complemento! == 'función') {
      throw new Error(`Complemento ilegal para &quot;${name}&quot;, debe ser una función, era ${typeof plugin}.`);
    }

    pluginStorage[nombre] = complemento;

    // Agregue un método de prototipo de jugador para todos los complementos subclasificados (pero no para
    // la clase de complemento base).
    if (nombre !== NOMBRE_PLUGIN_BASE) {
      if (Plugin.isBasic(complemento)) {
        Player.prototype[nombre] = createBasicPlugin(nombre, complemento);
      } else {
        Player.prototype[nombre] = createPluginFactory(nombre, complemento);
      }
    }

    complemento de retorno;
  }

  /**
   * Dar de baja un complemento Video.js.
   *
   * @param {cadena} nombre
   * El nombre del complemento que se va a dar de baja. Debe ser una cadena que
   * coincide con un complemento existente.
   *
   * @throws {Error}
   * Si se intenta anular el registro del complemento base.
   * /
  static deregisterPlugin(nombre) {
    if (nombre === NOMBRE_PLUGIN_BASE) {
      throw new Error('No se puede cancelar el registro del complemento base.');
    }
    if (el complemento existe (nombre)) {
      eliminar pluginStorage[nombre];
      eliminar Player.prototype[nombre];
    }
  }

  /**
   * Obtiene un objeto que contiene varios complementos de Video.js.
   *
   * @param {Array} [nombres]
   * Si se proporciona, debe ser una matriz de nombres de complementos. El valor predeterminado es _todos_
   * nombres de complementos.
   *
   * @return {Objeto|indefinido}
   * Un objeto que contiene complemento(s) asociado(s) con su(s) nombre(s) o
   * `undefined` si no existen complementos coincidentes).
   * /
  getPlugins estáticos (nombres = Object.keys (pluginStorage)) {
    deja que resulte;

    nombres.paraCada(nombre => {
      complemento const = getPlugin(nombre);

      si (complemento) {
        resultado = resultado || {};
        resultado[nombre] = complemento;
      }
    });

    resultado devuelto;
  }

  /**
   * Obtiene la versión de un complemento, si está disponible
   *
   * @param {cadena} nombre
   * El nombre de un complemento.
   *
   * @return {cadena}
   * La versión del complemento o una cadena vacía.
   * /
  getPluginVersion estática (nombre) {
    complemento const = getPlugin(nombre);

    complemento de retorno && complemento.VERSIÓN || '';
  }
}

/**
 * Obtiene un complemento por su nombre si existe.
 *
 * @estático
 * @método getPlugin
 * Complemento @memberOf
 * @param {cadena} nombre
 * El nombre de un complemento.
 *
 * @returns {Función|indefinido}
 * El complemento (o `indefinido`).
 * /
Complemento.getPlugin = getPlugin;

/**
 * El nombre de la clase de complemento base tal como está registrada.
 *
 * @tipo {cadena}
 * /
Complemento.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;

Plugin.registerPlugin(BASE_PLUGIN_NAME, Complemento);

/**
 * Documentado en player.js
 *
 * @ignorar
 * /
Player.prototype.usingPlugin = función (nombre) {
  devolver !!este[PLUGIN_CACHE_KEY] && este[PLUGIN_CACHE_KEY][nombre] === verdadero;
};

/**
 * Documentado en player.js
 *
 * @ignorar
 * /
Player.prototype.hasPlugin = función (nombre) {
  return !!pluginExists(nombre);
};

Exportar complemento predeterminado;

/**
 * Indica que un complemento está a punto de configurarse en un reproductor.
 *
 * @event Player#beforepluginsetup
 * @type {Complemento~PluginEventHash}
 * /

/**
 * Indica que un complemento está a punto de configurarse en un reproductor, por nombre. El nombre
 * es el nombre del complemento.
 *
 * @event Player#beforepluginsetup:$nombre
 * @type {Complemento~PluginEventHash}
 * /

/**
 * Indica que se acaba de configurar un complemento en un reproductor.
 *
 * @reproductor de eventos#pluginsetup
 * @type {Complemento~PluginEventHash}
 * /

/**
 * Indica que se acaba de configurar un complemento en un reproductor, por nombre. El nombre
 * es el nombre del complemento.
 *
 * @event Player#pluginsetup:$nombre
 * @type {Complemento~PluginEventHash}
 * /

/**
 * @typedef {Objeto} Complemento ~ PluginEventHash
 *
 * instancia de @property {cadena}
 * Para complementos básicos, el valor de retorno de la función del complemento. Para
 * Complementos avanzados, la instancia del complemento en la que se activa el evento.
 *
 * @propiedad {cadena} nombre
 * El nombre del complemento.
 *
 * Complemento @property {cadena}
 * Para complementos básicos, la función de complemento. Para complementos avanzados, el
 * Clase de complemento/constructor.
 * /