/**
 * @file mixins/evented.js
 * @module ereignete sich
 * /
import window from 'global/window';
importiere * als Dom aus '.. /utils/dom ';
importiere * als Ereignisse aus '.. /utils/events ';
importiere * als Fn aus '.. /utils/de ';
import * as Obj from '../utils/obj';
import EventTarget from '../event-target';
importiere DOMData aus '.. /utils/dom-data ';
import log from '../utils/log';

const objName = (obj) => {
  if (typeof obj.name === 'Funktion') {
    gib obj.name () zurück;
  }

  if (typeof obj.name === 'Zeichenfolge') {
    gib obj.name zurück;
  }

  wenn (obj.name_) {
    gib obj.name_ zurück;
  }

  wenn (obj.constructor && obj.constructor.name) {
    gib obj.constructor.name zurück;
  }

  gibt den Typ des Objekts zurück;
};

/**
 * Gibt zurück, ob auf ein Objekt das Event-Mixin angewendet wurde oder nicht.
 *
 * @param {Object} object
 * Ein Objekt zum Testen.
 *
 * @return {boolean}
 * Ob das Objekt ein Ereignis zu sein scheint oder nicht.
 * /
const isEvented = (Objekt) =>
  Objektinstanz von EventTarget ||
  !! Objekt.EventBusel_ &&
  ['on', 'one', 'off', 'trigger']. every (k => Objekttyp [k] === 'Funktion');

/**
 * Fügt einen Callback hinzu, der ausgeführt wird, nachdem das Event-Mixin angewendet wurde.
 *
 * @param {Object} object
 * Ein Objekt zum Hinzufügen
 * @param {Function} Rückruf
 * Der Callback, der ausgeführt werden soll.
 * /
const addEventedCallback = (Ziel, Rückruf) => {
  if (isEvent (Ziel)) {
    callback();
  } sonst {
    wenn (! target.eventedCallbacks) {
      target.eventedCallbacks = [];
    }
    target.eventedCallbacks.push (Rückruf);
  }
};

/**
 * Ob ein Wert ein gültiger Ereignistyp ist — eine nicht leere Zeichenfolge oder ein Array.
 *
 * @privat
 * @param {string|Array} type
 * Der zu testende Typwert.
 *
 * @return {boolean}
 * Ob der Typ ein gültiger Ereignistyp ist oder nicht.
 * /
const isValidEventType = (Typ) =>
  //Der Regex hier überprüft, ob der `Typ` mindestens einen Nicht-Typ enthält
  //Leerzeichen.
  (Typ === 'Zeichenfolge' && (/\ S/) .test (Typ)) ||
  (Array.isArray (Typ) &&!! typ.länge);

/**
 * Überprüft einen Wert, um festzustellen, ob er ein gültiges Ereignisziel ist. Wirft, wenn nicht.
 *
 * @privat
 * @throws {Fehler}
 * Wenn das Ziel kein gültiges Ereignisziel zu sein scheint.
 *
 * @param {Object} target
 * Das zu testende Objekt.
 *
 * @param {Object} obj
 *         Das Ereignisobjekt, für das wir die Validierung durchführen
 *
 * @param {string} fnName
 *         Der Name der ereignisgesteuerten Mixin-Funktion, die diese Funktion aufgerufen hat.
 * /
const validateTarget = (Ziel, obj, fnName) => {
  wenn (! Ziel || (! Ziel.nodename &&! isEvented (Ziel)) {
    throw new Error (`Ungültiges Ziel für $ {objName (obj)} #$ {fnName}; muss ein DOM-Knoten oder ein Ereignisobjekt sein.`);
  }
};

/**
 * Überprüft einen Wert, um festzustellen, ob er ein gültiges Ereignisziel ist. Wirft, wenn nicht.
 *
 * @privat
 * @throws {Fehler}
 * Wenn der Typ kein gültiger Ereignistyp zu sein scheint.
 *
 * @param {string|Array} type
 * Der zu testende Typ.
 *
 * @param {Object} obj
*         Das Ereignisobjekt, für das wir die Validierung durchführen
 *
 * @param {string} fnName
 *         Der Name der ereignisgesteuerten Mixin-Funktion, die diese Funktion aufgerufen hat.
 * /
const validateEventType = (Typ, obj, fnName) => {
  if (!isValidEventType(type)) {
    throw new Error (`Ungültiger Ereignistyp für $ {objName (obj)} #$ {fnName}; muss eine nicht leere Zeichenfolge oder ein Array sein.`);
  }
};

/**
 * Validiert einen Wert, um festzustellen, ob es sich um einen gültigen Listener handelt. Wirft, wenn nicht.
 *
 * @privat
 * @throws {Fehler}
 * Wenn der Listener keine Funktion ist.
 *
 * @param {Funktion} listener
 * Der zu testende Hörer.
 *
 * @param {Object} obj
 *         Das Ereignisobjekt, für das wir die Validierung durchführen
 *
 * @param {string} fnName
 *         Der Name der ereignisgesteuerten Mixin-Funktion, die diese Funktion aufgerufen hat.
 * /
const validateListener = (Listener, obj, fnName) => {
  if (Typ des Zuhörers! == 'Funktion') {
    throw new Error (`Ungültiger Listener für $ {objName (obj)} #$ {fnName}; muss eine Funktion sein.`);
  }
};

/**
 * Nimmt ein Array von Argumenten, die an `on () `oder `one ()` übergeben wurden, validiert sie und
 * normalisiert sie in ein Objekt.
 *
 * @privat
 * @param {Object} selbst
 * Das Ereignisobjekt, für das `on () `oder `one ()` aufgerufen wurde. Diese
 * Objekt wird als `this` -Wert für den Listener gebunden.
 *
 * @param {Array} args
 * Ein Array von Argumenten, die an `on () `oder `one ()` übergeben wurden.
 *
 * @param {string} fnName
 *         Der Name der ereignisgesteuerten Mixin-Funktion, die diese Funktion aufgerufen hat.
 *
 * @return {Object}
 * Ein Objekt, das nützliche Werte für `on () `- oder `one ()` -Aufrufe enthält.
 * /
const normalizeListenArgs = (self, args, fnName) => {

  //Wenn die Anzahl der Argumente kleiner als 3 ist, ist das Ziel immer
  //das Objekt selbst ereignete sich.
  const isTargetingSelf = args.length < 3 || args [0] === self || args [0] === self.EventBusel_;
  lass das Ziel anvisieren;
  lass tippen;
  lass den Zuhörer;

  if (isTargetingSelf) {
    Ziel = self.EventBusel_;

    //Behandle Fälle, in denen wir 3 Argumente haben, aber wir hören immer noch zu
    //das Ereignisobjekt selbst.
    wenn (args.length >= 3) {
      args.shift ();
    }

    [Typ, Listener] = Argumente;
  } sonst {
    [Ziel, Typ, Listener] = Argumente;
  }

  validateTarget (Ziel, Selbst, FNName);
  validateEventType (Typ, self, fnName);
  validateListener (Listener, self, fnName);

  listener = fn.Bind (selbst, Zuhörer);

  return {isTargetingSelf, Ziel, Typ, Listener};
};

/**
 * Fügt den Listener zu den Ereignistypen auf dem Ziel hinzu und normalisiert für
 * die Art des Ziels.
 *
 * @privat
 * @param {Element|Object} Ziel
 * Ein DOM-Knoten oder ein Ereignisobjekt.
 *
 * @param {string} Methode
 * Die zu verwendende Methode zur Event-Bindung („on“ oder „one“).
 *
 * @param {string|Array} type
 * Ein oder mehrere Veranstaltungstyp (en).
 *
 * @param {Funktion} listener
 * Eine Listener-Funktion.
 * /
const listen = (Ziel, Methode, Typ, Listener) => {
  validateTarget (Ziel, Ziel, Methode);

  if (target.nodeName) {
    Ereignisse [Methode] (Ziel, Typ, Listener);
  } sonst {
    target [Methode] (Typ, Listener);
  }
};

/**
 * Enthält Methoden, die Ereignisfunktionen für ein übergebenes Objekt bereitstellen
 * an {@link module:evented|evented}.
 *
 * @mixin EventedMixin
 * /
const eventedMixin = {

  /**
   * Hinzufügen eines Listeners für ein Ereignis (oder Ereignisse) auf diesem Objekt oder einem anderen Ereignis
   * objekt.
   *
   * @param {String|Array|Element|Objekt} targetOrType
   *         Wenn dies eine Zeichenkette oder ein Array ist, steht es für die Ereignisart(en)
   *         die den Hörer auslösen werden.
   *
   *         Stattdessen kann hier ein anderes ereignisgesteuertes Objekt übergeben werden, das
   *         veranlassen den Listener, auf Ereignisse für _das_ Objekt zu warten.
   *
   *         In beiden Fällen wird der `this'-Wert des Hörers an
   *         dieses Objekt.
   *
   * @param {string|Array|Funktion} typeOrListener
   *         Wenn das erste Argument eine Zeichenkette oder ein Array war, sollte dies die
   *         hörerfunktion. Andernfalls ist dies eine Zeichenkette oder ein Array von Ereignis
   *         art(en).
   *
   * @param {Funktion} [Listener]
   *         Wenn das erste Argument ein anderes ereignisgesteuertes Objekt war, wird dies
   *         die Hörerfunktion.
   * /
  auf (... args) {
    const {isTargetingSelf, target, type, listener} = normalizeListenArgs (this, args, 'on');

    listen (target, 'on', type, listener);

    //Wenn dieses Objekt auf ein anderes Ereignisobjekt hört.
    wenn (! zielt auf sich selbst ab) {

      //Wenn dieses Objekt entsorgt wird, entferne den Listener.
      const removeListenerOnDispose = () => this.off (Ziel, Typ, Listener);

      //Benutze dieselbe Funktions-ID wie der Listener, damit wir sie später entfernen können
      //mit der ID des ursprünglichen Listeners.
      removeListenerOnDispose.GUID = listener.guid;

      //Füge auch einen Listener zum Dispose-Event des Ziels hinzu. Das gewährleistet
      //dass, wenn das Ziel VOR diesem Objekt entsorgt wird, wir das
      //Listener entfernen, der gerade hinzugefügt wurde. Andernfalls verursachen wir ein Speicherleck.
      const removeRemoverOnTargetDispose = () => this.off ('entsorgen', removeListenerOnDispose);

      // Verwenden Sie dieselbe Funktions-ID wie der Hörer, damit wir ihn später entfernen können
      // unter Verwendung der ID des ursprünglichen Zuhörers.
      removeRemoverOnTargetDispose.GUID = listener.guid;

      listen (this, 'on', 'dispose', removeListenerOnDispose);
      listen (target, 'on', 'dispose', removeRemoverOnTargetDispose);
    }
  },

  /**
   * Hinzufügen eines Listeners für ein Ereignis (oder Ereignisse) auf diesem Objekt oder einem anderen Ereignis
   * objekt. Der Listener wird einmal pro Ereignis aufgerufen und dann entfernt.
   *
   * @param {String|Array|Element|Objekt} targetOrType
   *         Wenn dies eine Zeichenkette oder ein Array ist, steht es für die Ereignisart(en)
   *         die den Hörer auslösen werden.
   *
   *         Stattdessen kann hier ein anderes ereignisgesteuertes Objekt übergeben werden, das
   *         veranlassen den Listener, auf Ereignisse für _das_ Objekt zu warten.
   *
   *         In beiden Fällen wird der `this'-Wert des Hörers an
   *         dieses Objekt.
   *
   * @param {string|Array|Funktion} typeOrListener
   *         Wenn das erste Argument eine Zeichenkette oder ein Array war, sollte dies die
   *         hörerfunktion. Andernfalls ist dies eine Zeichenkette oder ein Array von Ereignis
   *         art(en).
   *
   * @param {Funktion} [Listener]
   *         Wenn das erste Argument ein anderes ereignisgesteuertes Objekt war, wird dies
   *         die Hörerfunktion.
   * /
  eins (... args) {
    const {isTargetingSelf, target, type, listener} = normalizeListenArgs (this, args, 'one');

    // Auf dieses ereignisgesteuerte Objekt zielen.
    if (isTargetingSelf) {
      listen (target, 'one', type, listener);

    // Ein anderes ereignisgesteuertes Objekt anvisieren.
    } sonst {
      // TODO: Dieser Wrapper ist falsch! Es sollte nur
      //entferne den Wrapper für den Ereignistyp, der ihn aufgerufen hat.
      //Stattdessen werden beim ersten Trigger alle Listener entfernt!
      //siehe https://github.com/videojs/video.js/issues/5962
      const wrapper = (...largs) => {
        this.off(Ziel, Typ, Wrapper);
        listener.apply(null, largs);
      };

      // Verwenden Sie dieselbe Funktions-ID wie der Hörer, damit wir ihn später entfernen können
      // unter Verwendung der ID des ursprünglichen Zuhörers.
      wrapper.guid = listener.guid;
      listen (target, 'one', type, wrapper);
    }
  },

  /**
   * Hinzufügen eines Listeners für ein Ereignis (oder Ereignisse) auf diesem Objekt oder einem anderen Ereignis
   * objekt. Der Listener wird für das erste ausgelöste Ereignis nur einmal aufgerufen
   * dann entfernt.
   *
   * @param {String|Array|Element|Objekt} targetOrType
   *         Wenn dies eine Zeichenkette oder ein Array ist, steht es für die Ereignisart(en)
   *         die den Hörer auslösen werden.
   *
   *         Stattdessen kann hier ein anderes ereignisgesteuertes Objekt übergeben werden, das
   *         veranlassen den Listener, auf Ereignisse für _das_ Objekt zu warten.
   *
   *         In beiden Fällen wird der `this'-Wert des Hörers an
   *         dieses Objekt.
   *
   * @param {string|Array|Funktion} typeOrListener
   *         Wenn das erste Argument eine Zeichenkette oder ein Array war, sollte dies die
   *         hörerfunktion. Andernfalls ist dies eine Zeichenkette oder ein Array von Ereignis
   *         art(en).
   *
   * @param {Funktion} [Listener]
   *         Wenn das erste Argument ein anderes ereignisgesteuertes Objekt war, wird dies
   *         die Hörerfunktion.
   * /
  irgendein (... args) {
    const {isTargetingSelf, target, type, listener} = normalizeListenArgs (this, args, 'any');

    // Auf dieses ereignisgesteuerte Objekt zielen.
    if (isTargetingSelf) {
      listen (target, 'any', type, listener);

    // Ein anderes ereignisgesteuertes Objekt anvisieren.
    } sonst {
      const wrapper = (...largs) => {
        this.off(Ziel, Typ, Wrapper);
        listener.apply(null, largs);
      };

      // Verwenden Sie dieselbe Funktions-ID wie der Hörer, damit wir ihn später entfernen können
      // unter Verwendung der ID des ursprünglichen Zuhörers.
      wrapper.guid = listener.guid;
      listen (target, 'any', type, wrapper);
    }
  },

  /**
   * Entfernt Listener aus Ereignis (en) eines Ereignisobjekts.
   *
   * @param {String|Array|Element|Objekt} [TargetOrType]
   * Wenn es sich um eine Zeichenfolge oder ein Array handelt, stellt dies die Ereignistypen dar.
   *
   * Stattdessen kann hier ein anderes Event-Objekt übergeben werden. In diesem Fall
   * ALLE 3 Argumente sind _erforderlich_.
   *
   * @param {String|Array|Funktion} [typeOrListener]
   * Wenn das erste Argument eine Zeichenfolge oder ein Array war, kann dies der
   *         hörerfunktion. Andernfalls ist dies eine Zeichenkette oder ein Array von Ereignis
   *         art(en).
   *
   * @param {Funktion} [Listener]
   *         Wenn das erste Argument ein anderes ereignisgesteuertes Objekt war, wird dies
   * die Listener-Funktion; andernfalls sind _alle_ Listener an den gebunden
   * Eventtyp (en) werden entfernt.
   * /
  aus (TargetOrType, TypeOrListener, Listener) {

    // Auf dieses ereignisgesteuerte Objekt zielen.
    wenn (! targetOrType || isValidEventType (targetOrType) {
      events.off (this.EventBusel_, TargetOrType, TypeOrListener);

    // Ein anderes ereignisgesteuertes Objekt anvisieren.
    } sonst {
      const target = TargetOrType;
      const type = typeOrListener;

      //Scheitere schnell und auf sinnvolle Weise!
      validateTarget (target, this, 'off');
      validateEventType (type, this, 'off');
      validateListener (Listener, dies, 'aus');

      //Stelle sicher, dass es mindestens eine GUID gibt, auch wenn die Funktion nicht benutzt wurde
      listener = fn.Bind (das, Listener);

      //Entferne den Dispose-Listener für dieses Event-Objekt, der angegeben wurde
      //dieselbe Guid wie der Event-Listener in on ().
      this.off ('entsorgen', Zuhörer);

      if (target.nodeName) {
        Events.OFF (Ziel, Typ, Listener);
        Events.off (target, 'dispone', Listener);
      } sonst if (isEvented (target)) {
        target.off (Typ, Listener);
        target.off ('entsorgen', Zuhörer);
      }
    }
  },

  /**
   * Löst ein Ereignis für dieses Ereignisobjekt aus, wodurch dessen Listener aufgerufen werden.
   *
   * @param {string|Object} event
   *          Ein Ereignistyp oder ein Objekt mit einer Typeigenschaft.
   *
   * @param {Objekt} [Hash]
   * Ein zusätzliches Objekt, das an die Zuhörer weitergegeben werden soll.
   *
   * @return {boolean}
   * Ob das Standardverhalten verhindert wurde oder nicht.
   * /
  Trigger (Ereignis, Hash) {
    validateTarget (this.eventBusel_, das, 'trigger');

    const type = Ereignis & Art des Ereignisses! == 'Zeichenfolge'? event.type: Ereignis;

    if (!isValidEventType(type)) {
      const error = `Ungültiger Ereignistyp für $ {objName (this)} #trigger; `+
        'muss eine nicht leere Zeichenfolge oder ein Objekt mit einem Typschlüssel sein, der einen nicht leeren Wert hat. ';

      if (Ereignis) {
        (this.log || log) .error (Fehler);
      } sonst {
        einen neuen Fehler auslösen (Fehler);
      }
    }
    return events.trigger (this.EventBusel_, event, hash);
  }
};

/**
 * Wendet {@link module:evented~eventedMixin|eventedMixin} auf ein Zielobjekt an.
 *
 * @param {Object} target
 * Das Objekt, dem Event-Methoden hinzugefügt werden sollen.
 *
 * @param {Objekt} [Optionen= {}]
 * Optionen zum Anpassen des Mixin-Verhaltens.
 *
 * @param {string} [Options.eventBusKey]
 * Fügt standardmäßig ein `EventBusel_` DOM-Element zum Zielobjekt hinzu,
 * der als Eventbus genutzt wird. Wenn das Zielobjekt bereits eine hat
 * DOM-Element, das verwendet werden soll, geben Sie hier seinen Schlüssel ein.
 *
 * @return {Object}
 * Das Zielobjekt.
 * /
function event (Ziel, Optionen = {}) {
  const {eventBusKey} = Optionen;

  //Setze oder erstelle den EventBusel_.
  wenn (EventBusKey) {
    wenn (! ziel [eventBusKey] .nodeName) {
      throw new Error (`Der EventBusKey „$ {eventBusKey}“ bezieht sich nicht auf ein Element.`);
    }
    target.eventBusel_ = Ziel [EventBusKey];
  } sonst {
    target.eventBusel_ = dom.createEl ('span', {Klassenname: 'vjs-event-bus'});
  }

  obj.assign (target, eventedMixin);

  wenn (target.eventedCallbacks) {
    target.eventedCallbacks.forEach (Rückruf) => {
      callback();
    });
  }

  //Wenn ein Event-Objekt gelöscht wird, entfernt es alle seine Listener.
  target.on ('entsorgen', () => {
    ziel.aus ();
    [Ziel, ziel.el_, ziel.eventBusel_] .forEach (function (val) {
      wenn (val & domData.has (val)) {
        domData.delete (Wert);
      }
    });
    window.setTimeout(() => {
      target.eventBusel_ = null;
    }, 0);
  });

  ziel zurückgeben;
}

Standardereignisse exportieren;
exportiere {isEvent};
exportiere {addEventedCallback};