/**
 * @file 미들웨어.js
 * @module 미들웨어
 */
import { assign } from '../utils/obj.js';
'../utils/string-cases.js'에서 {toTitleCase} 가져오기;

const 미들웨어 = {};
const middlewareInstances = {};

내보내기 const TERMINATOR = {};

/**
 * 미들웨어 객체는 다음과 같은 메서드가 있는 일반 JavaScript 객체입니다.
 * 허용 목록에 있는 {@link Tech} 방법과 일치
 * {@link module:middleware.allowedGetters|getters},
 * {@link module:middleware.allowedSetters|세터}, 그리고
 * {@link module:middleware.allowedMediators|중재자}.
 *
 * @typedef {객체} MiddlewareObject
 */

/**
 * 다음을 반환해야 하는 미들웨어 팩토리 함수
 * {@link module:middleware~MiddlewareObject|미들웨어객체}.
 *
 * 이 공장은 필요할 때 각 플레이어에 대해 플레이어와 함께 호출됩니다.
 * 인수로 전달되었습니다.
 *
 * @callback 미들웨어팩토리
 * @param {플레이어} 플레이어
 * Video.js 플레이어.
 */

/**
 * 플레이어가 팩토리 함수를 통해 사용해야 하는 미들웨어 정의
 * 미들웨어 객체를 반환합니다.
 *
 * @param {문자열} 유형
 * 일치시킬 MIME 유형 또는 모든 MIME 유형의 경우 `"*"`.
 *
 * @param {MiddlewareFactory} 미들웨어
 * 실행될 미들웨어 팩토리 기능
 * 일치 유형.
 */
내보내기 기능 사용(유형, 미들웨어) {
  미들웨어[유형] = 미들웨어[유형] || [];
  미들웨어[유형].push(미들웨어);
}

/**
 * 유형별 미들웨어(또는 모든 미들웨어)를 가져옵니다.
 *
 * @param {문자열} 유형
 * 일치시킬 MIME 유형 또는 모든 MIME 유형의 경우 `"*"`.
 *
 * @return {함수[]|정의되지 않음}
 * 미들웨어의 배열 또는 존재하지 않는 경우 '정의되지 않음'.
 */
내보내기 기능 getMiddleware(유형) {
  경우 (유형) {
    return 미들웨어[유형];
  }

  미들웨어 반환;
}

/**
 * 미들웨어를 사용하여 비동기적으로 소스를 설정합니다.
 * 미들웨어를 일치시키고 각각에 `setSource`를 호출하여
 * 매번 이전에 반환된 값.
 *
 * @param {플레이어} 플레이어
 * {@link Player} 인스턴스.
 *
 * @param {Tech~SourceObject} src
 * 소스 객체.
 *
 * @param {함수}
 * 실행할 다음 미들웨어.
 */
내보내기 기능 setSource(플레이어, src, 다음) {
  player.setTimeout(() => setSourceHelper(src, 미들웨어[src.type], 다음, 플레이어), 1);
}

/**
 * 기술이 설정되면 각 미들웨어의 `setTech` 메소드에 기술을 전달합니다.
 *
 * @param {Object[]} 미들웨어
 * 미들웨어 인스턴스의 배열.
 *
 * @param {기술} 기술
 * Video.js 기술.
 */
내보내기 기능 setTech(middleware, tech) {
  미들웨어.forEach((mw) => mw.setTech && mw.setTech(기술));
}

/**
 * 각 미들웨어를 통해 먼저 기술에서 getter를 호출합니다.
 * 플레이어의 오른쪽에서 왼쪽으로.
 *
 * @param {Object[]} 미들웨어
 * 미들웨어 인스턴스의 배열.
 *
 * @param {기술} 기술
 * 현재 기술.
 *
 * @param {문자열} 메서드
 * 메서드 이름.
 *
 * @return {혼합}
 * 미들웨어가 가로챈 후 기술의 최종 값입니다.
 */
내보내기 기능 get(middleware, tech, method) {
  return middleware.reduceRight(middlewareIterator(방법), 기술[방법]());
}

/**
 * 플레이어에게 주어진 인수를 취하고 각각의 setter 메서드를 호출합니다.
 * 미들웨어는 왼쪽에서 오른쪽으로 기술입니다.
 *
 * @param {Object[]} 미들웨어
 * 미들웨어 인스턴스의 배열.
 *
 * @param {기술} 기술
 * 현재 기술.
 *
 * @param {문자열} 메서드
 * 메서드 이름.
 *
 * @param {혼합} arg
 * 기술에 설정할 값입니다.
 *
 * @return {혼합}
 * `tech`의 `method` 반환 값.
 */
내보내기 기능 세트(미들웨어, 기술, 방법, arg) {
  return tech[method](middleware.reduce(middlewareIterator(method), arg));
}

/**
 * 플레이어에게 주어진 인수를 받아서 `call` 버전의
 * 왼쪽에서 오른쪽으로 각 미들웨어의 메소드.
 *
 * 그런 다음 기술에서 전달된 메서드를 호출하고 변경되지 않은 결과를 반환합니다.
 * 이번에는 오른쪽에서 왼쪽으로 미들웨어를 통해 플레이어로 돌아갑니다.
 *
 * @param {Object[]} 미들웨어
 * 미들웨어 인스턴스의 배열.
 *
 * @param {기술} 기술
 * 현재 기술.
 *
 * @param {문자열} 메서드
 * 메서드 이름.
 *
 * @param {혼합} arg
 * 기술에 설정할 값입니다.
 *
 * @return {혼합}
 * `tech`의 `method` 반환 값은
 * 미들웨어의 반환 값.
 */
내보내기 기능 mediate(middleware, tech, method, arg = null) {
  const callMethod = '호출' + toTitleCase(방법);
  const middlewareValue = 미들웨어.리듀스(middlewareIterator(callMethod), arg);
  const 종료됨 = middlewareValue === TERMINATOR;
  // 사용되지 않습니다. `null` 반환 값은 대신 TERMINATOR를 반환해야 합니다.
  // techs 메서드가 실제로 null을 반환하는 경우 혼동을 방지합니다.
  const returnValue = 종료됨 ? null : 기술[방법](middlewareValue);

  executeRight(미들웨어, 메소드, returnValue, 종료됨);

  반환 값 반환;
}

/**
 * 키가 메서드 이름인 허용된 게터의 열거.
 *
 * @type {객체}
 */
내보내기 const allowedGetters = {
  버퍼링됨: 1,
  현재 시간: 1,
  지속: 1,
  음소거: 1,
  재생: 1,
  일시중지: 1,
  검색 가능: 1,
  용량: 1,
  종료: 1
};

/**
 * 키가 메소드 이름인 허용된 세터의 열거.
 *
 * @type {객체}
 */
내보내기 const allowedSetters = {
  setCurrentTime: 1,
  음소거 설정: 1,
  볼륨 설정: 1
};

/**
 * 키가 메소드 이름인 허용된 중재자의 열거.
 *
 * @type {객체}
 */
내보내기 const allowedMediators = {
  놀다: 1,
  정지시키다: 1
};

함수 middlewareIterator(방법) {
  반환 (값, mw) => {
    // 이전 미들웨어가 종료된 경우 종료를 전달합니다.
    if (값 === 종결자) {
      반환 터미네이터;
    }

    if (mw[방법]) {
      return mw[방법](값);
    }

    반환 값;
  };
}

function executeRight(mws, 메서드, 값, 종료됨) {
  for (let i = mws.length - 1; i > = 0; 나--) {
    const mw = mws[i];

    if (mw[방법]) {
      mw[방법](종료, 값);
    }
  }
}

/**
 * 플레이어의 미들웨어 캐시를 지웁니다.
 *
 * @param {플레이어} 플레이어
 * {@link Player} 인스턴스.
 */
내보내기 기능 clearCacheForPlayer(player) {
  middlewareInstances[player.id()] = null;
}

/**
 * {
 * [playerId]: [[mwFactory, mwInstance], ...]
 * }
 *
 * @사적인
 */
함수 getOrCreateFactory(플레이어, mwFactory) {
  const mws = 미들웨어 인스턴스[player.id()];
  mw = null로 하자;

  if (mws === 정의되지 않음 || mws === null) {
    mw = mwFactory(플레이어);
    middlewareInstances[player.id()] = [[mwFactory, mw]];
    반환 mw;
  }

  에 대한 (하자 i = 0; i < mws.길이; i++) {
    const [mwf, mwi] = mws[i];

    if (mwf !== mwFactory) {
      계속하다;
    }

    mw = mwi;
  }

  경우 (mw === null) {
    mw = mwFactory(플레이어);
    mws.push([mwFactory, mw]);
  }

  반환 mw;
}

function setSourceHelper(src = {}, 미들웨어 = [], 다음, 플레이어, acc = [], lastRun = false) {
  const [mwFactory, ...mwrest] = 미들웨어;

  // mwFactory가 문자열이면 갈림길에 있는 것입니다.
  if (mwFactory 유형 === '문자열') {
    setSourceHelper(src, 미들웨어[mwFactory], 다음, 플레이어, acc, lastRun);

  // mwFactory가 있으면 플레이어와 함께 호출하여 mw를 얻습니다.
  // 그런 다음 mw의 setSource 메서드를 호출합니다.
  } 그렇지 않으면 (mwFactory) {
    const mw = getOrCreateFactory(player, mwFactory);

    // setSource가 없으면 암시적으로 이 미들웨어를 선택합니다.
    if (!mw.setSource) {
      acc.push(mw);
      return setSourceHelper(src, mwrest, next, player, acc, lastRun);
    }

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

      // 문제 발생, 현재 레벨에서 다음 미들웨어 시도
      // 이전 src를 사용해야 합니다.
      경우 (오류) {
        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
      }

      // 성공했습니다. 이제 더 깊이 들어가야 합니다.
      acc.push(mw);

      // 같은 유형이면 현재 체인을 계속 진행합니다.
      // 그렇지 않으면 새 체인으로 내려가고 싶습니다.
      setSourceHelper(
        _src,
        src.type === _src.type ? mwrest : 미들웨어[_src.type],
        다음,
        플레이어,
        예,
        lastRun
      );
    });

  } 그렇지 않으면 (mwrest.length) {
    setSourceHelper(src, mwrest, next, 플레이어, acc, lastRun);
  } 그렇지 않으면 (마지막 실행) {
    다음(src, acc);
  } else {
    setSourceHelper(src, middlewares['*'], next, player, acc, true);
  }
}