/**
 * @file plugin.js
 * /
importiere evented von './mixins/evented';
import stateful from './mixins/stateful';
importiere * als Ereignisse aus '. /utils/events ';
Protokoll aus 'importieren. /utils/log ';
import Player von './player';

/**
 * Der Name des Basis-Plugins.
 *
 * @privat
 * @konstant
 * @Typ {String}
 * /
const BASE_PLUGIN_NAME = 'Erweiterung';

/**
 * Der Schlüssel, auf dem der aktive Plugins-Cache eines Spielers gespeichert ist.
 *
 * @privat
 * @konstant
 * @type {Zeichenfolge}
 * /
const PLUGIN_CACHE_KEY = 'ActivePlugins_';

/**
 * Speichert registrierte Plugins in einem privaten Bereich.
 *
 * @privat
 * @type {Objekt}
 * /
const pluginStorage = {};

/**
 * Meldet, ob ein Plugin registriert wurde oder nicht.
 *
 * @privat
 * @param {string} name
 *          Der Name eines Plugins.
 *
 * @return {boolean}
 * Ob das Plugin registriert wurde oder nicht.
 * /
const pluginExists = (Name) => pluginStorage.hasOwnProperty (Name);

/**
 * Holen Sie sich ein einzelnes registriertes Plugin mit Namen.
 *
 * @privat
 * @param {string} name
 *          Der Name eines Plugins.
 *
 * @return {Funktion|undefined}
 * Das Plugin (oder undefiniert).
 * /
const getPlugin = (Name) => PluginExists (Name)? pluginStorage [Name]: undefiniert;

/**
 * Markiert ein Plugin auf einem Player als „aktiv“.
 *
 * Stellt außerdem sicher, dass der Player über ein Objekt verfügt, um aktive Plugins zu verfolgen.
 *
 * @privat
 * @param {Player} -Spieler
 * Eine Player-Instanz von Video.js.
 *
 * @param {string} name
 *          Der Name eines Plugins.
 * /
const markPluginAsActive = (Spieler, Name) => {
  spieler [PLUGIN_CACHE_KEY] = Spieler [PLUGIN_CACHE_KEY] || {};
  Spieler [PLUGIN_CACHE_KEY] [Name] = wahr;
};

/**
 * Löst ein Paar Plugin-Setup-Ereignisse aus.
 *
 * @privat
 * @param {Player} Spieler
 * Eine Player-Instanz von Video.js.
 *
 * @param {plugin~pluginEventHash} -Hash
 * Ein Plugin-Event-Hash.
 *
 * @param {boolean} [vorher]
 * Falls wahr, wird dem Eventnamen das Präfix „before“ vorangestellt. Mit anderen Worten,
 * benutze dies, um „beforepluginsetup“ statt „pluginsetup“ auszulösen.
 * /
const triggerSetupEvent = (Spieler, Hash, vorher) => {
  const eventName = (vorher? 'vorher': „) + 'pluginsetup';

  player.trigger (EventName, Hash);
  player.trigger (EventName + ':' + Hashname, Hashwert);
};

/**
 * Nimmt eine grundlegende Plugin-Funktion und gibt eine Wrapper-Funktion zurück, die markiert
 * auf dem Player, dass das Plugin aktiviert wurde.
 *
 * @privat
 * @param {string} name
 *          Der Name des Plugins.
 *
 * @param {Funktion} plugin
 * Das Basis-Plugin.
 *
 * @return {Funktion}
 * Eine Wrapper-Funktion für das angegebene Plugin.
 * /
const createBasicPlugin = Funktion (Name, Plugin) {
  const basicPluginWrapper = Funktion () {

    //Wir lösen die Ereignisse „beforepluginsetup“ und „pluginsetup“ auf dem Player aus
    //unabhängig davon, aber wir möchten, dass der Hash mit dem angegebenen Hash übereinstimmt
    //für fortgeschrittene Plugins.
    // //
    //Die einzige potenziell kontraintuitive Sache hier ist die `Instanz` in
    //Das Ereignis „pluginsetup“ ist der Wert, der von der Funktion `plugin` zurückgegeben wird.
    triggerSetupEvent (this, {Name, Plugin, Instanz: null}, wahr);

    const instance = plugin.apply (dies, Argumente);

    markPluginAsActive (dies, Name);
    triggerSetupEvent (this, {Name, Plugin, Instanz});

    instanz zurückgeben;
  };

  Object.keys (Plugin) .forEach (Funktion (Requisite) {
    basicPluginWrapper [prop] = Plugin [Requisite];
  });

  gib basicPluginWrapper zurück;
};

/**
 * Nimmt eine Plugin-Unterklasse und gibt eine Factory-Funktion zum Generieren zurück
 * Beispiele dafür.
 *
 * Diese Factory-Funktion ersetzt sich selbst durch eine Instanz der angeforderten
 * Unterklasse des Plugins.
 *
 * @privat
 * @param {string} name
 *          Der Name des Plugins.
 *
 * @param {Plugin} PluginSubclass
 * Das erweiterte Plugin.
 *
 * @return {Funktion}
 * /
const createPluginFactory = (Name, PluginSubclass) => {

  //Füge dem Plugin-Prototyp eine `name`-Eigenschaft hinzu, damit jedes Plugin
  //bezeichnet sich selbst beim Namen.
  PluginSubClass.prototype.name = Name;

  Funktion zurückgeben (... args) {
    TriggerSetupEvent (dies, {name, plugin: pluginSubClass, Instanz: null}, wahr);

    const instance = new PluginSubClass (... [das,... Argumente]);

    //Das Plugin wird durch eine Funktion ersetzt, die die aktuelle Instanz zurückgibt.
    this [name] = () => Instanz;

    triggerSetupEvent (dies, instance.getEventHash ());

    instanz zurückgeben;
  };
};

/**
 * Elternklasse für alle fortgeschrittenen Plugins.
 *
 * @mixes Modul: Evented~EventedMixin
 * @mixes Modul: Stateful~StatefulMixin
 * @fires Spieler #beforepluginsetup
 * @fires Spieler #beforepluginsetup: $name
 * @fires Spieler #pluginsetup
 * @fires Spieler #pluginsetup: $name
 * @listens Spieler #dispose
 * @throws {Fehler}
 * Beim Versuch, die Basisklasse {@link Plugin} zu instanziieren
 * direkt statt über eine Unterklasse.
 * /
Klasse Plugin {

  /**
   * Erzeugt eine Instanz dieser Klasse.
   *
   * Unterklassen sollten `super` aufrufen, um sicherzustellen, dass die Plugins korrekt initialisiert sind.
   *
   * @param {Player} Spieler
   * Eine Player-Instanz von Video.js.
   * /
  Konstruktor (Spieler) {
    if (this.constructor === Plugin) {
      throw new Error ('Das Plugin muss unterklassiert sein; es muss nicht direkt instanziiert sein. ');
    }

    this.player = Spieler;

    wenn (! this.log) {
      this.log = this.player.log.createLogger (diese.name);
    }

    //Mache dieses Objekt ereignisreich, aber entferne die hinzugefügte `Trigger` Methode, damit wir
    //verwende stattdessen die Prototyp-Version.
    ereignete (dies);
    lösche this.trigger;

    stateful(this, this.constructor.defaultState);
    Plugin als aktiv markieren (player, this.name);

    //Bindet die Dispose-Methode automatisch, damit wir sie als Listener verwenden und die Bindung aufheben können
    //es später einfach.
    this.dispose = this.dispose.bind (dies);

    //Wenn der Player entsorgt wird, entsorge das Plugin.
    player.on ('entsorgen', diese.entsorgen);
  }

  /**
   * Ruft die Version des Plugins ab, die auf <pluginName>.version gesetzt wurde
   * /
  version () {
    gib this.constructor.Version zurück;
  }

  /**
   * Jedes durch Plugins ausgelöste Ereignis enthält einen Hash zusätzlicher Daten mit
   * konventionelle Eigenschaften.
   *
   * Dies gibt dieses Objekt zurück oder verändert einen vorhandenen Hash.
   *
   * @param {Object} [hash={}]
   * Ein Objekt, das als Event- und Event-Hash verwendet werden soll.
   *
   * @return {plugin~pluginEventHash}
   * Ein Event-Hash-Objekt mit eingemischten angegebenen Eigenschaften.
   * /
  getEventHash (Hashwert = {}) {
    hash.name = diese.name;
    hash.plugin = dieser.konstruktor;
    hash.instance = das;
    Hash zurückgeben;
  }

  /**
   * Löst ein Ereignis im Plugin-Objekt aus und überschreibt
   * {@link module:evented~eventedMixin.trigger|EventedMixin.trigger}.
   *
   * @param {string|Object} event
   *          Ein Ereignistyp oder ein Objekt mit einer Typeigenschaft.
   *
   * @param {Object} [hash={}]
   * Zusätzlicher Daten-Hash zum Zusammenführen mit einem
   * {@link plugin~pluginEventHash|pluginEventHash}.
   *
   * @return {boolean}
   * Ob die Standardeinstellung verhindert wurde oder nicht.
   * /
  Trigger (Ereignis, Hash = {}) {
    return events.trigger (this.eventBusel_, event, this.getEventHash (hash));
  }

  /**
   * Behandelt „statechanged“ -Ereignisse im Plugin. Standardmäßig kein OP, überschreiben von
   * Unterklassifizierung.
   *
   * @Abstrakt
   * @param {Veranstaltung}
   * Ein Ereignisobjekt, das durch ein „statechanged“ -Ereignis bereitgestellt wird.
   *
   * @param {Object} e.changes
   * Ein Objekt, das Änderungen beschreibt, die mit dem „statechanged“ aufgetreten sind
   * Veranstaltung.
   * /
  handleStateChanged (e) {}

  /**
   * Verfügt über ein Plugin.
   *
   * Unterklassen können dies überschreiben, wenn sie wollen, aber aus Sicherheitsgründen
   * Es ist wahrscheinlich am besten, die Veranstaltung „Dispose“ zu abonnieren.
   *
   * @fires Plugin #dispose
   * /
  dispose() {
    const {Name, Spieler} = das;

    /**
     * Signalisiert, dass ein erweitertes Plugin bald gelöscht wird.
     *
     * @event Plugin #dispose
     * @type {EventTarget~Ereignis}
     * /
    this.trigger('dispose');
    this.off ();
    player.off ('entsorgen', diese.entsorgen);

    //Beseitigen Sie alle möglichen Quellen für Speicherverlust, indem Sie aufräumen
    //Verweise zwischen dem Player und der Plugin-Instanz und Nullstellen
    //der Status des Plugins und das Ersetzen von Methoden durch eine Funktion, die auslöst.
    Spieler [PLUGIN_CACHE_KEY] [Name] = falsch;
    this.player = this.state = null;

    //Ersetze abschließend den Plugin-Namen auf dem Player durch eine neue Factory
    //Funktion, sodass das Plugin bereit ist, erneut eingerichtet zu werden.
    spieler [name] = createPluginFactory (Name, PluginStorage [Name]);
  }

  /**
   * Stellt fest, ob ein Plugin ein Basis-Plugin ist (also keine Unterklasse von `Plugin`).
   *
   * @param {string|Function} -Plugin
   * Wenn eine Zeichenfolge, entspricht dies dem Namen eines Plugins. Wenn eine Funktion, wird
   * direkt getestet.
   *
   * @return {boolean}
   * Ob ein Plugin ein Basis-Plugin ist oder nicht.
   * /
  static isBasic (Plugin) {
    const p = (Plugin-Art === 'Zeichenfolge')? getPlugin (Erweiterung): Erweiterung;

    return typeof p === 'function' &&! plugin.prototype.isPrototypeOf (p.prototype);
  }

  /**
   * Registrieren Sie ein Video.js Plugin.
   *
   * @param {string} name
   * Der Name des Plugins, das registriert werden soll. Muss eine Zeichenfolge sein und
   * darf keinem existierenden Plugin oder einer Methode auf dem `Player` entsprechen
   * Prototyp.
   *
   * @param {Funktion} plugin
   * Eine Unterklasse von `Plugin` oder eine Funktion für einfache Plugins.
   *
   * @return {Funktion}
   * Für erweiterte Plugins eine Factory-Funktion für dieses Plugin. Für
   * Basis-Plugins, eine Wrapper-Funktion, die das Plugin initialisiert.
   * /
  statisches RegisterPlugin (Name, Plugin) {
    if (Art des Namens! == 'Zeichenfolge') {
      einen neuen Fehler auslösen (`Ungültiger Plugin-Name, „$ {name}“, muss eine Zeichenfolge sein, war $ {typeof name} .`);
    }

    if (pluginExists(name)) {
      log.warn (`Ein Plugin mit dem Namen „$ {name}“ existiert bereits. Möglicherweise möchten Sie die erneute Registrierung von Plugins vermeiden! `);
    } sonst wenn (player.prototype.hasOwnProperty (name)) {
      einen neuen Fehler auslösen (`Ungültiger Plugin-Name, „$ {name}“, kann keinen Namen mit einer vorhandenen Player-Methode teilen! `);
    }

    if (Plugin-Art! == 'Funktion') {
      einen neuen Fehler auslösen (`Ungültiges Plugin für „$ {name}“, muss eine Funktion sein, war $ {typeof plugin} .`);
    }

    pluginStorage [Name] = Plugin;

    //Füge eine Player-Prototyp-Methode für alle Plugins der Unterklasse hinzu (aber nicht für
    //die Basis-Plugin-Klasse).
    wenn (Name! == NAME DES BASIS-PLUGINS) {
      wenn (plugin.isBasic (Plugin)) {
        player.prototype [name] = createBasicPlugin (Name, Plugin);
      } sonst {
        player.prototype [name] = createPluginFactory (Name, Plugin);
      }
    }

    Plugin zurückgeben;
  }

  /**
   * Deregistrieren Sie ein Video.js Plugin.
   *
   * @param {string} name
   * Der Name des Plugins, das deregistriert werden soll. Muss eine Zeichenfolge sein, die
   * entspricht einem vorhandenen Plugin.
   *
   * @throws {Fehler}
   * Wenn versucht wird, das Basis-Plugin zu deregistrieren.
   * /
  statisches DeregisterPlugin (Name) {
    wenn (name === BASE_PLUGIN_NAME) {
      einen neuen Fehler auslösen ('Das Basis-Plugin kann nicht deregistriert werden. ');
    }
    if (pluginExists(name)) {
      lösche PluginStorage [Name];
      lösche Player.prototype [Name];
    }
  }

  /**
   * Ruft ein Objekt ab, das mehrere Video.js Plugins enthält.
   *
   * @param {Array} [Namen]
   * Falls angegeben, sollte es sich um ein Array von Plugin-Namen handeln. Die Standardeinstellung ist _all_
   * Plugin-Namen.
   *
   * @return {Object|undefined}
   * Ein Objekt, das Plugins enthält, die mit ihren Namen verknüpft sind, oder
   * `undefined` falls keine passenden Plugins existieren).
   * /
  statische getPlugins (names = Object.keys (pluginStorage)) {
    lass das Ergebnis;

    names.forEach (name => {
      const plugin = getPlugin(name);

      wenn (Plugin) {
        Ergebnis = Ergebnis || {};
        Ergebnis [Name] = Plugin;
      }
    });

    ergebnis zurückgeben;
  }

  /**
   * Ruft die Version eines Plugins ab, falls verfügbar
   *
   * @param {string} name
   *          Der Name eines Plugins.
   *
   * @return {string}
   * Die Version des Plugins oder eine leere Zeichenfolge.
   * /
  statische getPluginVersion (Name) {
    const plugin = getPlugin(name);

    gib das Plugin zurück & plugin.Version || „;
  }
}

/**
 * Ruft ein Plugin anhand seines Namens ab, falls es existiert.
 *
 * @static
 * @method GetPlugin
 * @memberOf -Plugin
 * @param {string} name
 * Der Name eines Plugins.
 *
 * @returns {Funktion|undefiniert}
 * Das Plugin (oder `undefiniert`).
 * /
plugin.getPlugin = GetPlugin;

/**
 * Der Name der Basis-Plugin-Klasse, so wie sie registriert ist.
 *
 * @Typ {String}
 * /
plugin.base_plugin_name = BASIS-PLUGINNAME;

plugin.registerPlugin (BASE_PLUGINNAME, Erweiterung);

/**
 * Dokumentiert in player.js
 *
 * @ignore
 * /
player.prototype.UsingPlugin = Funktion (Name) {
  kehre zurück!! dieser [PLUGIN_CACHE_KEY] && dieser [PLUGIN_CACHE_KEY] [Name] === wahr;
};

/**
 * Dokumentiert in player.js
 *
 * @ignore
 * /
player.prototype.hasPlugin = Funktion (Name) {
  kehre zurück!! PluginExists (Name);
};

Standard-Plugin exportieren;

/**
 * Signalisiert, dass ein Plugin auf einem Player eingerichtet werden soll.
 *
 * @event Spieler #beforepluginsetup
 * @Typ {Plugin~PluginEventHash}
 * /

/**
 * Signalisiert, dass ein Plugin auf einem Player eingerichtet werden soll — namentlich. Der Name
 * ist der Name des Plugins.
 *
 * @event Spieler #beforepluginsetup: $name
 * @Typ {Plugin~PluginEventHash}
 * /

/**
 * Signalisiert, dass gerade ein Plugin auf einem Player eingerichtet wurde.
 *
 * @event Spieler #pluginsetup
 * @Typ {Plugin~PluginEventHash}
 * /

/**
 * Signalisiert, dass gerade ein Plugin auf einem Player eingerichtet wurde — namentlich. Der Name
 * ist der Name des Plugins.
 *
 * @event Spieler #pluginsetup: $name
 * @Typ {Plugin~PluginEventHash}
 * /

/**
 * @typedef {Object} plugin~pluginEventHash
 *
 * @property {string} -Instanz
 * Für einfache Plugins der Rückgabewert der Plugin-Funktion. Für
 * erweiterte Plugins, die Plugin-Instanz, auf der das Ereignis ausgelöst wird.
 *
 * @property {string} name
 * Der Name des Plugins.
 *
 * @property {string} -Plugin
 * Für einfache Plugins die Plugin-Funktion. Für fortgeschrittene Plugins ist das
 * Plugin-Klasse/Konstruktor.
 * /