text: import { assign } from '../utils/obj.js';
  import toTitleCase from '../utils/to-title-case.js';
  
  const middlewares = {};
  const middlewareInstances = {};
  
  export const TERMINATOR = {};
  
  export function use(type, middleware) {
    middlewares[type] = middlewares[type] || [];
    middlewares[type].push(middleware);
  }
  
  export function getMiddleware(type) {
    if (type) {
      return middlewares[type];
    }
  
    return middlewares;
  }
  
  export function setSource(player, src, next) {
    player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
  }
  
  export function setTech(middleware, tech) {
    middleware.forEach((mw) => mw.setTech && mw.setTech(tech));
  }
  
  /**
   * Calls a getter on the tech first, through each middleware
   * from right to left to the player.
   */
  export function get(middleware, tech, method) {
    return middleware.reduceRight(middlewareIterator(method), tech[method]());
  }
  
  /**
   * Takes the argument given to the player and calls the setter method on each
   * middlware from left to right to the tech.
   */
  export function set(middleware, tech, method, arg) {
    return tech[method](middleware.reduce(middlewareIterator(method), arg));
  }
  
  /**
   * Takes the argument given to the player and calls the `call` version of the method
   * on each middleware from left to right.
   * Then, call the passed in method on the tech and return the result unchanged
   * back to the player, through middleware, this time from right to left.
   */
  export function mediate(middleware, tech, method, arg = null) {
    const callMethod = 'call' + toTitleCase(method);
    const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
    const terminated = middlewareValue === TERMINATOR;
    const returnValue = terminated ? null : tech[method](middlewareValue);
  
    executeRight(middleware, method, returnValue, terminated);
  
    return returnValue;
  }
  
  export const allowedGetters = {
    buffered: 1,
    currentTime: 1,
    duration: 1,
    seekable: 1,
    played: 1,
    paused: 1
  };
  
  export const allowedSetters = {
    setCurrentTime: 1
  };
  
  export const allowedMediators = {
    play: 1,
    pause: 1
  };
  
  function middlewareIterator(method) {
    return (value, mw) => {
      // if the previous middleware terminated, pass along the termination
      if (value === TERMINATOR) {
        return TERMINATOR;
      }
  
      if (mw[method]) {
        return mw[method](value);
      }
  
      return value;
    };
  }
  
  function executeRight(mws, method, value, terminated) {
    for (let i = mws.length - 1; i >= 0; i--) {
      const mw = mws[i];
  
      if (mw[method]) {
        mw[method](terminated, value);
      }
    }
  }
  
  export function clearCacheForPlayer(player) {
    middlewareInstances[player.id()] = null;
  }
  
  /**
   * {
   *  [playerId]: [[mwFactory, mwInstance], ...]
   * }
   */
  function getOrCreateFactory(player, mwFactory) {
    const mws = middlewareInstances[player.id()];
    let mw = null;
  
    if (mws === undefined || mws === null) {
      mw = mwFactory(player);
      middlewareInstances[player.id()] = [[mwFactory, mw]];
      return mw;
    }
  
    for (let i = 0; i < mws.length; i++) {
      const [mwf, mwi] = mws[i];
  
      if (mwf !== mwFactory) {
        continue;
      }
  
      mw = mwi;
    }
  
    if (mw === null) {
      mw = mwFactory(player);
      mws.push([mwFactory, mw]);
    }
  
    return mw;
  }
  
  function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
    const [mwFactory, ...mwrest] = middleware;
  
    // if mwFactory is a string, then we're at a fork in the road
    if (typeof mwFactory === 'string') {
      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
  
    // if we have an mwFactory, call it with the player to get the mw,
    // then call the mw's setSource method
    } else if (mwFactory) {
      const mw = getOrCreateFactory(player, mwFactory);
  
      // if setSource isn't present, implicitly select this middleware
      if (!mw.setSource) {
        acc.push(mw);
        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
      }
  
      mw.setSource(assign({}, src), function(err, _src) {
  
        // something happened, try the next middleware on the current level
        // make sure to use the old src
        if (err) {
          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
        }
  
        // we've succeeded, now we need to go deeper
        acc.push(mw);
  
        // if it's the same type, continue down the current chain
        // otherwise, we want to go down the new chain
        setSourceHelper(_src,
            src.type === _src.type ? mwrest : middlewares[_src.type],
            next,
            player,
            acc,
            lastRun);
      });
  
    } else if (mwrest.length) {
      setSourceHelper(src, mwrest, next, player, acc, lastRun);
    } else if (lastRun) {
      next(src, acc);
    } else {
      setSourceHelper(src, middlewares['*'], next, player, acc, true);
    }
  }
  