/**
 * @file middleware.js
 * @module Middleware
 * /
importiere {assign} aus '.. /utils/obj.js ';
importiere {toTitleCase} aus '../utils/string-cases.js';

const Middleware = {};
const MiddlewareInstances = {};

exportiere const TERMINATOR = {};

/**
 * Ein Middleware-Objekt ist ein einfaches JavaScript-Objekt mit Methoden, die
 * entspricht den {@link Tech} -Methoden, die in den Listen der zulässigen Methoden enthalten sind
 * {@link module:middleware.allowedGetters|Getter},
 * {@link module:middleware.allowedSetters|setters} und
 * {@link module:middleware.allowedMediators|Mediatoren}.
 *
 * @typedef {Objekt} Middleware-Objekt
 * /

/**
 * Eine Middleware-Factory-Funktion, die eine zurückgeben sollte
 * {@link Module:Middleware~MiddlewareObject|MiddlewareObjekt}.
 *
 * Diese Fabrik wird bei Bedarf für jeden Spieler zusammen mit dem Spieler aufgerufen
 * als Argument übergeben.
 *
 * @callback Middleware-Fabrik
 * @param {Player} Spieler
 * Ein Video.js Player.
 * /

/**
 * Definieren Sie eine Middleware, die der Spieler als Factory-Funktion verwenden soll
 * das gibt ein Middleware-Objekt zurück.
 *
 * @param {string} type
 *         Der zu übereinstimmende MIME-Typ oder `"*"` für alle MIME-Typen.
 *
 * @param {MiddlewareFactory} Middleware
 * Eine Middleware-Factory-Funktion, die ausgeführt wird für
 * passende Typen.
 * /
Exportfunktion verwenden (Typ, Middleware) {
  Middleware [Typ] = Middleware [Typ] | [];
  Middleware [Typ] .push (Middleware);
}

/**
 * Ruft Middlewares nach Typ ab (oder alle Middlewares).
 *
 * @param {string} type
 *         Der zu übereinstimmende MIME-Typ oder `"*"` für alle MIME-Typen.
 *
 * @return {Funktion [] |undefiniert}
 * Ein Array von Middlewares oder `undefined`, falls keine vorhanden sind.
 * /
Exportfunktion getMiddleware (Typ) {
  wenn (Typ) {
    Middlewares [Typ] zurückgeben;
  }

  Middleware zurückgeben;
}

/**
 * Legt mithilfe von Middleware asynchron eine Quelle fest, indem eine beliebige Quelle wiederholt wird
 * passende Middlewares und Aufruf von `setSource` für jede, wobei
 * jedes Mal der vorherige Rückgabewert.
 *
 * @param {Player} Spieler
 *         Eine {@link Player}-Instanz.
 *
 * @param {tech~sourceObject} src
 * Ein Quellobjekt.
 *
 * @param {Funktion}
 * Die nächste Middleware, die ausgeführt werden soll.
 * /
Exportfunktion setSource (player, src, next) {
  player.setTimeout () => setSourceHelper (src, Middlewares [src.type], next, player), 1);
}

/**
 * Wenn die Technologie eingestellt ist, wird sie an die `SetTech`-Methode jeder Middleware übergeben.
 *
 * @param {Objekt []} Middleware
 * Eine Reihe von Middleware-Instanzen.
 *
 * @param {Tech} tech
 * Ein Video.js Techniker.
 * /
Exportfunktion SetTech (Middleware, Tech) {
  Middleware.forEach (mw) => mw.SetTech & mw.SetTech (tech));
}

/**
 * Ruft zuerst einen Techniker über jede Middleware auf
 * von rechts nach links zum Spieler.
 *
 * @param {Object[]} middleware
 *         Ein Array von Middleware-Instanzen.
 *
 * @param {Tech} tech
 *         Die aktuelle tech.
 *
 * @param {string} Methode
 *         Ein Methodenname.
 *
 * @return {Mixed}
 * Der endgültige Wert der Technologie, nachdem die Middleware sie abgefangen hat.
 * /
Exportfunktion get (Middleware, Technik, Methode) {
  return Middleware.reduceRight (MiddlewareIterator (Methode), tech [Methode] ());
}

/**
 * Nimmt das dem Spieler übergebene Argument und ruft bei jedem die Setter-Methode auf
 * Middleware von links nach rechts zum Techniker.
 *
 * @param {Object[]} middleware
 *         Ein Array von Middleware-Instanzen.
 *
 * @param {Tech} tech
 *         Die aktuelle tech.
 *
 * @param {string} Methode
 *         Ein Methodenname.
 *
 * @param {Mixed} arg
 *         Der Wert, der auf der Tech eingestellt werden soll.
 *
 * @return {Mixed}
 * Der Rückgabewert der `Methode` des `tech`.
 * /
Funktionssatz exportieren (Middleware, Technik, Methode, Argument) {
  return tech [Methode] (middleware.reduce (MiddlewareIterator (Methode), arg));
}

/**
 * Nimmt das dem Spieler übergebene Argument und ruft die `Call`-Version des
 * Methode für jede Middleware von links nach rechts.
 *
 * Rufen Sie dann die übergebene Methode auf dem Tech auf und geben Sie das Ergebnis unverändert zurück
 * zurück zum Player, über Middleware, diesmal von rechts nach links.
 *
 * @param {Object[]} middleware
 *         Ein Array von Middleware-Instanzen.
 *
 * @param {Tech} tech
 *         Die aktuelle tech.
 *
 * @param {string} Methode
 *         Ein Methodenname.
 *
 * @param {Mixed} arg
 *         Der Wert, der auf der Tech eingestellt werden soll.
 *
 * @return {Mixed}
 * Der Rückgabewert der `Methode` des `tech`, unabhängig von
 * Rückgabewerte von Middlewares.
 * /
Exportfunktion mediate (Middleware, Tech, Methode, arg = null) {
  const callMethod = 'aufrufen' + toTitleCase (Methode);
  const MiddlewareValue = middleware.reduce (MiddlewareIterator (CallMethod), arg);
  const terminiert = MiddlewareValue === TERMINATOR;
  //veraltet. Der Rückgabewert `Null` sollte stattdessen TERMINATOR zurückgeben zu
  //verhindert Verwirrung, wenn eine Tech-Methode tatsächlich Null zurückgibt.
  const ReturnValue = beendet? null: tech [Methode] (MiddlewareValue);

  executeRight (Middleware, Methode, ReturnValue, beendet);

  ReturnValue zurückgeben;
}

/**
 * Aufzählung der zulässigen Getter, wobei die Schlüssel Methodennamen sind.
 *
 * @Typ {Objekt}
 * /
exportiere const allowedGetters = {
  gepuffert: 1,
  aktuelleZeit: 1,
  Dauer: 1,
  stummgeschaltet: 1,
  gespielt: 1,
  angehalten: 1,
  suchbar: 1,
  volumen: 1,
  endete: 1
};

/**
 * Aufzählung der zulässigen Setter, wobei die Schlüssel Methodennamen sind.
 *
 * @Typ {Objekt}
 * /
exportiere const AllowedSetters = {
  Aktuelle Uhrzeit festlegen: 1,
  Stummschalten: 1,
  Lautstärke einstellen: 1
};

/**
 * Aufzählung der zulässigen Mediatoren, wobei die Schlüssel Methodennamen sind.
 *
 * @Typ {Objekt}
 * /
exportiere const AllowedMediators = {
  abspielen: 1,
  Pause: 1
};

Funktion MiddlewareIterator (Methode) {
  return (value, mw) => {
    //Wenn die vorherige Middleware beendet wurde, geben Sie die Terminierung weiter
    if (Wert === TERMINATOR) {
      TERMINATOR zurückgeben;
    }

    if (mw[Methode]) {
      gib mw [Methode] (Wert) zurück;
    }

    rückgabewert;
  };
}

Funktion executeRight (mws, Methode, Wert, terminiert) {
  für (sei i = mws.length - 1; i >= 0; i--) {
    const mw = mws [i];

    if (mw[Methode]) {
      mw [Methode] (abgeschlossen, Wert);
    }
  }
}

/**
 * Leeren Sie den Middleware-Cache für einen Spieler.
 *
 * @param {Player} Spieler
 *         Eine {@link Player}-Instanz.
 * /
Exportfunktion ClearCacheForPlayer (Spieler) {
  MiddlewareInstances [player.id ()] = null;
}

/**
 * {
 * [PlayerID]: [MwFactory, MWInstanz],...]
 * }
 *
 * @privat
 * /
Funktion getOrCreateFactory (Spieler, mwFactory) {
  const mws = MiddlewareInstances [player.id ()];
  lass mich = null;

  if (ms === undefiniert || ms === null) {
    mw = mwFactory(player);
    MiddlewareInstances [player.id ()] = [mwFactory, mw]];
    zurück mw;
  }

  für (sei i = 0; i < mws.length; i++) {
    const [mwf, mwi] = mws [i];

    wenn (mwf! == MWFactory) {
      weiter;
    }

    mw = mwi;
  }

  wenn (mw === null) {
    mw = mwFactory(player);
    ms.push ([MWFactory, mw]);
  }

  zurück mw;
}

function setSourceHelper (src = {}, Middleware = [], next, player, acc = [], lastRun = falsch) {
  const [mwFactory,... mwrest] = Middleware;

  //wenn mwFactory eine Zeichenfolge ist, dann befinden wir uns an einer Weggabelung
  if (typeof MWFactory === 'Zeichenfolge') {
    setSourceHelper (src, Middlewares [MWFactory], next, player, acc, lastRun);

  //wenn wir eine MWFactory haben, rufe sie mit dem Player auf, um das MW zu bekommen,
  //dann rufe die setSource-Methode von mw auf
  } sonst wenn (mwFactory) {
    const mw = getOrCreateFactory (Spieler, mwFactory);

    //wenn setSource nicht vorhanden ist, wählen Sie implizit diese Middleware aus
    wenn (! mw.setSource) {
      acc.push(mw);
      return setSourceHelper(src, mwrest, next, player, acc, lastRun);
    }

    mw.setSource (assign ({}, src), function (err, _src) {

      //etwas ist passiert, probiere die nächste Middleware auf dem aktuellen Level aus
      //stelle sicher, dass du das alte src benutzt
      if (err) {
        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
      }

      //Es ist uns gelungen, jetzt müssen wir tiefer gehen
      acc.push(mw);

      //wenn es derselbe Typ ist, setze die aktuelle Kette fort
      //sonst wollen wir die neue Kette hinuntergehen
      setSourceHelper (
        _src,
        src.type === _src.type? mrest: Middleware [_src.type],
        als nächstes,
        spieler,
        gemäß,
        Letzter Run
      );
    });

  } sonst wenn (mwrest.length) {
    setSourceHelper (src, mwrest, next, player, acc, lastRun);
  } sonst wenn (LastRun) {
    weiter (src, acc);
  } sonst {
    setSourceHelper (src, Middlewares ['*'], next, player, acc, true);
  }
}