/**
 * @archivo middleware.js
 * @módulo de software intermedio
 * /
importar {asignar} desde '../utils/obj.js';
importar {toTitleCase} desde '../utils/string-cases.js';

const middlewares = {};
const middlewareInstances = {};

exportar const TERMINATOR = {};

/**
 * Un objeto de middleware es un objeto simple de JavaScript que tiene métodos que
 * haga coincidir los métodos {@link Tech} que se encuentran en las listas de permitidos
 * {@módulo de enlace: middleware.allowedGetters|captadores},
 * {@link module:middleware.allowedSetters|setters}, y
 * {@módulo de enlace:middleware.allowedMediators|mediadores}.
 *
 * @typedef {Objeto} MiddlewareObject
 * /

/**
 * Una función de fábrica de middleware que debería devolver un
 * {@módulo de enlace:middleware~MiddlewareObject|MiddlewareObject}.
 *
 * Esta fábrica se llamará para cada jugador cuando sea necesario, con el jugador
 * pasado como un argumento.
 *
 * @callback MiddlewareFactory
 * @param {Jugador} jugador
 * Un reproductor de Video.js.
 * /

/**
 * Definir un middleware que el jugador debería usar por medio de una función de fábrica
 * que devuelve un objeto de middleware.
 *
 * @param {cadena} tipo
 * El tipo MIME para hacer coincidir o `"*"` para todos los tipos MIME.
 *
 * @param {MiddlewareFactory} software intermedio
 * Una función de fábrica de middleware que se ejecutará para
 * tipos coincidentes.
 * /
uso de la función de exportación (tipo, middleware) {
  middleware[tipo] = middleware[tipo] || [];
  middlewares[tipo].push(middleware);
}

/**
 * Obtiene middlewares por tipo (o todos los middlewares).
 *
 * @param {cadena} tipo
 * El tipo MIME para hacer coincidir o `"*"` para todos los tipos MIME.
 *
 * @return {Función[]|indefinido}
 * Una matriz de middlewares o `indefinido` si no existe ninguno.
 * /
función de exportación getMiddleware (tipo) {
  si (tipo) {
    volver middlewares[tipo];
  }

  devolver middlewares;
}

/**
 * Establece de forma asincrónica una fuente utilizando middleware recurriendo a través de cualquier
 * emparejando middlewares y llamando a `setSource` en cada uno, pasando a lo largo del
 * valor devuelto anterior cada vez.
 *
 * @param {Jugador} jugador
 * Una instancia de {@link Player}.
 *
 * @param {Tech~SourceObject} src
 * Un objeto fuente.
 *
 * @param {Función}
 * El próximo middleware a ejecutar.
 * /
función de exportación setSource (jugador, src, siguiente) {
  jugador.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
}

/**
 * Cuando se establece la tecnología, pasa la tecnología al método `setTech` de cada middleware.
 *
 * @param {Objeto[]} software intermedio
 * Una matriz de instancias de middleware.
 *
 * @param {Tecnología} tecnología
 * Una tecnología Video.js.
 * /
función de exportación setTech (middleware, tecnología) {
  middleware.forEach((mw) => mw.setTech && mw.setTech(tecnología));
}

/**
 * Primero llama a un getter en la tecnología, a través de cada middleware
 * de derecha a izquierda al jugador.
 *
 * @param {Objeto[]} software intermedio
 * Una matriz de instancias de middleware.
 *
 * @param {Tecnología} tecnología
 * La tecnología actual.
 *
 * Método @param {cadena}
 * Un nombre de método.
 *
 * @return {Mixto}
 * El valor final de la tecnología después de que el middleware la haya interceptado.
 * /
función de exportación get (middleware, tecnología, método) {
  return middleware.reduceRight(middlewareIterator(método), tech[método]());
}

/**
 * Toma el argumento dado al jugador y llama al método setter en cada
 * middleware de izquierda a derecha a la tecnología.
 *
 * @param {Objeto[]} software intermedio
 * Una matriz de instancias de middleware.
 *
 * @param {Tecnología} tecnología
 * La tecnología actual.
 *
 * Método @param {cadena}
 * Un nombre de método.
 *
 * @param {Mixto} argumento
 * El valor a establecer en la tecnología.
 *
 * @return {Mixto}
 * El valor de retorno del `método` de la `tecnología`.
 * /
conjunto de funciones de exportación (middleware, tecnología, método, argumento) {
  return tech[método](middleware.reduce(middlewareIterator(método), arg));
}

/**
 * Toma el argumento dado al jugador y llama a la versión `call` del
 * método en cada middleware de izquierda a derecha.
 *
 * Luego, llame al método pasado en la tecnología y devuelva el resultado sin cambios
 * de vuelta al jugador, a través del middleware, esta vez de derecha a izquierda.
 *
 * @param {Objeto[]} software intermedio
 * Una matriz de instancias de middleware.
 *
 * @param {Tecnología} tecnología
 * La tecnología actual.
 *
 * Método @param {cadena}
 * Un nombre de método.
 *
 * @param {Mixto} argumento
 * El valor a establecer en la tecnología.
 *
 * @return {Mixto}
 * El valor de retorno del `método` de la `tecnología`, independientemente de la
 * valores devueltos de middlewares.
 * /
función de exportación mediar (middleware, tecnología, método, arg = null) {
  const callMethod = 'llamar' + toTitleCase(método);
  const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  const terminado = middlewareValue === TERMINATOR;
  // obsoleto. En su lugar, el valor de retorno `nulo` debería devolver TERMINATOR a
  // evitar confusiones si un método técnico realmente devuelve nulo.
  const returnValue = terminado? nulo: tecnología [método] (middlewareValue);

  executeRight(middleware, método, returnValue, terminado);

  devolver valor de retorno;
}

/**
 * Enumeración de captadores permitidos donde las claves son nombres de métodos.
 *
 * @type {Objeto}
 * /
export const allowGetters = {
  amortiguado: 1,
  tiempo actual: 1,
  duración: 1,
  apagado: 1,
  jugó: 1,
  pausado: 1,
  buscable: 1,
  volumen: 1,
  terminó: 1
};

/**
 * Enumeración de setters permitidos donde las claves son nombres de métodos.
 *
 * @type {Objeto}
 * /
exportar const permitidoSetters = {
  establecer la hora actual: 1,
  establecer silenciado: 1,
  establecerVolumen: 1
};

/**
 * Enumeración de mediadores permitidos donde las claves son nombres de métodos.
 *
 * @type {Objeto}
 * /
export const allowMediators = {
  jugar: 1,
  pausa: 1
};

función middlewareIterator(método) {
  retorno (valor, mw) => {
    // si el middleware anterior terminó, pasar la terminación
    si (valor === TERMINADOR) {
      volver TERMINADOR;
    }

    if (mw[método]) {
      return mw[método](valor);
    }

    valor de retorno;
  };
}

función ejecutarDerecho(mws, método, valor, terminado) {
  para (sea i = mws.length - 1; i > = 0; i--) {
    const mw = mws[i];

    if (mw[método]) {
      mw[método](terminado, valor);
    }
  }
}

/**
 * Borrar la memoria caché de middleware para un jugador.
 *
 * @param {Jugador} jugador
 * Una instancia de {@link Player}.
 * /
función de exportación clearCacheForPlayer (jugador) {
  middlewareInstances[player.id()] = null;
}

/**
 * {
 * [IdJugador]: [[mwFactory, mwInstance], ...]
 * }
 *
 * @privado
 * /
función getOrCreateFactory(jugador, mwFactory) {
  const mws = middlewareInstances[player.id()];
  sea mw = nulo;

  if (mws === indefinido || mws === nulo) {
    mw = mwFactory(jugador);
    middlewareInstances[player.id()] = [[mwFactory, mw]];
    volver mw;
  }

  para (sea i = 0; i < mws.longitud; i++) {
    constante [mwf, mwi] = mws[i];

    if (mwf !== mwFábrica) {
      continuar;
    }

    mw = mwi;
  }

  si (mw === nulo) {
    mw = mwFactory(jugador);
    mws.push([mwFábrica, mw]);
  }

  volver mw;
}

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

  // si mwFactory es una cadena, entonces estamos en una bifurcación en el camino
  if (tipo de mwFactory === 'cadena') {
    setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);

  // si tenemos un mwFactory, llamalo con el jugador para obtener el mw,
  // luego llamar al método setSource de mw
  } si no (mwFactory) {
    const mw = getOrCreateFactory(jugador, mwFactory);

    // si setSource no está presente, seleccione implícitamente este middleware
    if (!mw.setFuente) {
      acc.pulsar(mw);
      return setSourceHelper(src, mwrest, next, player, acc, lastRun);
    }

    mw.setSource(asignar({}, src), función(err, _src) {

      // algo sucedió, pruebe el siguiente middleware en el nivel actual
      // asegúrese de usar el antiguo src
      si (err) {
        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
      }

      // lo hemos logrado, ahora debemos profundizar más
      acc.pulsar(mw);

      // si es del mismo tipo, continúa hacia abajo en la cadena actual
      // de lo contrario, queremos bajar por la nueva cadena
      setSourceHelper(
        _src,
        src.tipo === _src.tipo ? mwrest : middleware[_src.type],
        próximo,
        jugador,
        según,
        última carrera
      );
    });

  } else if (mwrest.longitud) {
    setSourceHelper(src, mwrest, next, player, acc, lastRun);
  } más si (última Ejecución) {
    siguiente(fuente, acc);
  } else {
    setSourceHelper(src, middlewares['*'], next, player, acc, true);
  }
}