/**
 * @파일 플러그인.js
 */
'./mixins/evented'에서 이벤트 가져오기;
'./mixins/stateful'에서 상태 저장 가져오기;
import * as Events from './utils/events';
'./utils/log'에서 로그 가져오기;
'./player'에서 플레이어 가져오기;

/**
 * 기본 플러그인 이름.
 *
 * @사적인
 * @끊임없는
 * @유형 {문자열}
 */
const BASE_PLUGIN_NAME = '플러그인';

/**
 * 플레이어의 활성 플러그인 캐시가 저장되는 키입니다.
 *
 * @사적인
 * @끊임없는
 * @유형 {문자열}
 */
const PLUGIN_CACHE_KEY = 'activePlugins_';

/**
 * 등록된 플러그인을 비공개 공간에 보관합니다.
 *
 * @사적인
 * @type {객체}
 */
const pluginStorage = {};

/**
 * 플러그인 등록 여부를 알려줍니다.
 *
 * @사적인
 * @param {문자열} 이름
 * 플러그인의 이름입니다.
 *
 * @return {부울}
 * 플러그인 등록 여부.
 */
const pluginExists = (이름) => pluginStorage.hasOwnProperty(이름);

/**
 * 등록된 단일 플러그인을 이름으로 가져옵니다.
 *
 * @사적인
 * @param {문자열} 이름
 * 플러그인의 이름입니다.
 *
 * @return {함수|정의되지 않음}
 * 플러그인(또는 정의되지 않음).
 */
const getPlugin = (이름) => 플러그인 존재(이름) ? pluginStorage[이름] : 정의되지 않음;

/**
 * 플레이어에서 플러그인을 "활성"으로 표시합니다.
 *
 * 또한 플레이어에 활성 플러그인을 추적하기 위한 개체가 있는지 확인합니다.
 *
 * @사적인
 * @param {플레이어} 플레이어
 * Video.js 플레이어 인스턴스.
 *
 * @param {문자열} 이름
 * 플러그인의 이름입니다.
 */
const markPluginAsActive = (플레이어, 이름) => {
  플레이어[PLUGIN_CACHE_KEY] = 플레이어[PLUGIN_CACHE_KEY] || {};
  플레이어[PLUGIN_CACHE_KEY][이름] = 참;
};

/**
 * 한 쌍의 플러그인 설정 이벤트를 트리거합니다.
 *
 * @사적인
 * @param {플레이어} 플레이어
 * Video.js 플레이어 인스턴스.
 *
 * @param {Plugin~PluginEventHash} 해시
 * 플러그인 이벤트 해시.
 *
 * @param {부울} [이전]
 * 참이면 이벤트 이름 앞에 "before"를 붙입니다. 다시 말해서,
 * 이것을 사용하여 "pluginsetup" 대신 "beforepluginsetup"을 트리거합니다.
 */
const triggerSetupEvent = (플레이어, 해시, 이전) => {
  const eventName = (before ? 'before' : '') + 'pluginsetup';

  player.trigger(eventName, hash);
  player.trigger(eventName + ':' + hash.name, hash);
};

/**
 * 기본 플러그인 기능을 취하고 표시하는 래퍼 기능을 반환합니다.
 * 플러그인이 활성화된 플레이어에서.
 *
 * @사적인
 * @param {문자열} 이름
 * 플러그인의 이름입니다.
 *
 * @param {함수} 플러그인
 * 기본 플러그인.
 *
 * @return {함수}
 * 주어진 플러그인에 대한 래퍼 함수.
 */
const createBasicPlugin = function(이름, 플러그인) {
  const basicPluginWrapper = 함수() {

    // 플레이어에서 "beforepluginsetup" 및 "pluginsetup" 이벤트를 트리거합니다.
    // 무관하지만 해시가 제공된 해시와 일치하기를 원합니다.
    // 고급 플러그인용.
    //
    // 여기서 직관에 반할 가능성이 있는 유일한 것은
    // "pluginsetup" 이벤트는 `plugin` 함수에 의해 반환된 값입니다.
    triggerSetupEvent(this, {name, plugin, instance: null}, true);

    const 인스턴스 = plugin.apply(this, 인수);

    markPluginAsActive(this, name);
    triggerSetupEvent(this, {이름, 플러그인, 인스턴스});

    반환 인스턴스;
  };

  Object.keys(플러그인).forEach(function(prop) {
    basicPluginWrapper[prop] = 플러그인[prop];
  });

  basicPluginWrapper를 반환합니다.
};

/**
 * 플러그인 하위 클래스를 가져오고 생성을 위한 팩토리 함수를 반환합니다.
 * 인스턴스.
 *
 * 이 팩토리 함수는 요청된 인스턴스로 자신을 대체합니다.
 * 플러그인의 하위 클래스.
 *
 * @사적인
 * @param {문자열} 이름
 * 플러그인의 이름입니다.
 *
 * @param {플러그인} PluginSubClass
 * 고급 플러그인.
 *
 * @return {함수}
 */
const createPluginFactory = (이름, PluginSubClass) => {

  // 플러그인 프로토타입에 `name` 속성을 추가하여 각 플러그인이
  // 자신을 이름으로 참조합니다.
  PluginSubClass.prototype.name = 이름;

  반환 함수(...인수) {
    triggerSetupEvent(이것, {이름, 플러그인: PluginSubClass, 인스턴스: null}, true);

    const instance = new PluginSubClass(...[this, ...args]);

    // 플러그인은 현재 인스턴스를 반환하는 함수로 대체됩니다.
    이[이름] = () => 사례;

    triggerSetupEvent(this, instance.getEventHash());

    반환 인스턴스;
  };
};

/**
 * 모든 고급 플러그인의 상위 클래스.
 *
 * @mixes 모듈:evented~EventedMixin
 * @mixes 모듈:stateful~StatefulMixin
 * @fires Player#beforepluginsetup
 * @fires Player#beforepluginsetup:$name
 * @fires Player#pluginsetup
 * @fires Player#pluginsetup:$name
 * @listens Player#dispose
 * @throws {오류}
 * 기본 {@link Plugin} 클래스를 인스턴스화하려는 경우
 * 하위 클래스를 통하지 않고 직접.
 */
클래스 플러그인 {

  /**
   * 이 클래스의 인스턴스를 만듭니다.
   *
   * 하위 클래스는 플러그인이 제대로 초기화되도록 `super`를 호출해야 합니다.
   *
   * @param {플레이어} 플레이어
   * Video.js 플레이어 인스턴스.
   */
  생성자(플레이어) {
    if (this.constructor === 플러그인) {
      throw new Error('플러그인은 하위 클래스여야 합니다. 직접 인스턴스화할 수 없습니다.');
    }

    this.player = 플레이어;

    if (!this.log) {
      this.log = this.player.log.createLogger(this.name);
    }

    // 이 객체를 이벤트로 만들지만 추가된 `trigger` 메서드를 제거하여
    // 대신 프로토타입 버전을 사용합니다.
    이벤트(이);
    this.trigger 삭제;

    stateful(this, this.constructor.defaultState);
    markPluginAsActive(player, this.name);

    // dispose 메서드를 자동 바인딩하여 리스너로 사용하고 바인딩을 해제할 수 있도록 합니다.
    // 나중에 쉽게.
    this.dispose = this.dispose.bind(this);

    // 플레이어가 삭제되면 플러그인을 삭제합니다.
    player.on('dispose', this.dispose);
  }

  /**
   * 설정된 플러그인 버전 가져오기 < 플러그인 이름> .버전
   */
  버전() {
    this.constructor.VERSION을 반환합니다.
  }

  /**
   * 플러그인에 의해 트리거되는 각 이벤트에는 다음과 같은 추가 데이터 해시가 포함됩니다.
   * 기존 속성.
   *
   * 이것은 해당 객체를 반환하거나 기존 해시를 변경합니다.
   *
   * @param {객체} [해시={}]
   * 이벤트 해시로 사용할 객체.
   *
   * @return {Plugin~PluginEventHash}
   * 제공된 속성이 혼합된 이벤트 해시 개체입니다.
   */
  getEventHash(해시 = {}) {
    hash.name = this.name;
    hash.plugin = this.constructor;
    해시.인스턴스 = this;
    반환 해시;
  }

  /**
   * 플러그인 개체에서 이벤트를 트리거하고 재정의합니다.
   * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
   *
   * @param {문자열|객체} 이벤트
   * type 속성이 있는 이벤트 유형 또는 객체.
   *
   * @param {객체} [해시={}]
   * 병합할 추가 데이터 해시
   * {@link Plugin~PluginEventHash|PluginEventHash}.
   *
   * @return {부울}
   * 불이행 방지 여부.
   */
  트리거(이벤트, 해시 = {}) {
    return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash));
  }

  /**
   * 플러그인에서 "statechanged" 이벤트를 처리합니다. 기본적으로 작동하지 않음, 재정의
   * 서브클래싱.
   *
   * @추상적인
   * @param {이벤트} 전자
   * "statechanged" 이벤트에서 제공하는 이벤트 개체입니다.
   *
   * @param {Object} e.changes
   * "statechanged"로 발생한 변경 사항을 설명하는 객체
   * 이벤트.
   */
  handleStateChanged(e) {}

  /**
   * 플러그인을 폐기합니다.
   *
   * 하위 클래스는 원하는 경우 이를 재정의할 수 있지만 안전을 위해
   * "dispose" 이벤트를 구독하는 것이 가장 좋습니다.
   *
   * @fires 플러그인#dispose
   */
  폐기() {
    const {이름, 플레이어} = this;

    /**
     * 고급 플러그인이 곧 폐기될 것이라는 신호입니다.
     *
     * @event 플러그인#dispose
     * @type {이벤트대상~이벤트}
     */
    this.trigger('처리');
    this.off();
    player.off('dispose', this.dispose);

    // 정리를 통해 메모리 누수 원인을 제거합니다.
    // 플레이어와 플러그인 인스턴스 사이의 참조 및 무효화
    // 플러그인의 상태 및 메서드를 throw하는 함수로 대체합니다.
    플레이어[PLUGIN_CACHE_KEY][이름] = 거짓;
    this.player = this.state = null;

    // 마지막으로 플레이어의 플러그인 이름을 새 팩토리로 바꿉니다.
    // 함수를 사용하여 플러그인을 다시 설정할 수 있습니다.
    player[이름] = createPluginFactory(이름, pluginStorage[이름]);
  }

  /**
   * 플러그인이 기본 플러그인인지 결정합니다(예: `Plugin`의 하위 클래스가 아님).
   *
   * @param {string|Function} 플러그인
   * 문자열인 경우 플러그인 이름과 일치합니다. 함수라면,
   * 직접 테스트했습니다.
   *
   * @return {부울}
   * 플러그인이 기본 플러그인인지 여부.
   */
  정적 isBasic(플러그인) {
    const p = (플러그인 유형 === '문자열') ? getPlugin(플러그인) : 플러그인;

    return typeof p === '함수' && !Plugin.prototype.isPrototypeOf(p.prototype);
  }

  /**
   * Video.js 플러그인을 등록합니다.
   *
   * @param {문자열} 이름
   * 등록할 플러그인의 이름. 문자열이어야 하며
   * 기존 플러그인 또는 `Player`의 메서드와 일치하지 않아야 합니다.
   * 프로토타입.
   *
   * @param {함수} 플러그인
   * `Plugin`의 하위 클래스 또는 기본 플러그인의 기능.
   *
   * @return {함수}
   * 고급 플러그인의 경우 해당 플러그인의 공장 기능. 을 위한
   * 기본 플러그인, 플러그인을 초기화하는 래퍼 함수.
   */
  static registerPlugin(이름, 플러그인) {
    if (이름 유형 !== '문자열') {
      throw new Error(`잘못된 플러그인 이름, "${name}"은 문자열이어야 합니다. ${typeof name}이었습니다.`);
    }

    if (pluginExists(이름)) {
      log.warn(`"${name}" 플러그인이 이미 존재합니다. 플러그인 재등록을 피하고 싶을 수도 있습니다!`);
    } 그렇지 않으면 (Player.prototype.hasOwnProperty(이름)) {
      throw new Error(`잘못된 플러그인 이름, "${name}", 기존 플레이어 메서드와 이름을 공유할 수 없습니다!`);
    }

    if (플러그인 유형 !== '함수') {
      throw new Error(`"${name}"에 대한 불법 플러그인, 함수여야 합니다. ${typeof plugin}이었습니다.`);
    }

    pluginStorage[이름] = 플러그인;

    // 모든 하위 클래스 플러그인에 대한 플레이어 프로토타입 메서드를 추가합니다(단,
    // 기본 플러그인 클래스).
    if (이름 !== BASE_PLUGIN_NAME) {
      if (Plugin.isBasic(플러그인)) {
        Player.prototype[name] = createBasicPlugin(이름, 플러그인);
      } else {
        Player.prototype[name] = createPluginFactory(이름, 플러그인);
      }
    }

    반환 플러그인;
  }

  /**
   * Video.js 플러그인 등록을 취소합니다.
   *
   * @param {문자열} 이름
   * 등록을 해제할 플러그인의 이름입니다. 다음 문자열이어야 합니다.
   * 기존 플러그인과 일치합니다.
   *
   * @throws {오류}
   * 기본 플러그인 등록 해제를 시도한 경우.
   */
  정적 deregisterPlugin(이름) {
    if (이름 === BASE_PLUGIN_NAME) {
      throw new Error('기본 플러그인 등록을 취소할 수 없습니다.');
    }
    if (pluginExists(이름)) {
      플러그인 저장소[이름] 삭제;
      삭제 Player.prototype[이름];
    }
  }

  /**
   * 여러 Video.js 플러그인을 포함하는 개체를 가져옵니다.
   *
   * @param {배열} [이름]
   * 제공된 경우 플러그인 이름의 배열이어야 합니다. 기본값은 _all_
   * 플러그인 이름.
   *
   * @return {객체|정의되지 않음}
   * 플러그인 이름과 관련된 플러그인을 포함하는 개체 또는
   * 일치하는 플러그인이 존재하지 않는 경우 `정의되지 않음`).
   */
  정적 getPlugins(이름 = Object.keys(pluginStorage)) {
    결과를 보자;

    이름.forEach(이름 => {
      const 플러그인 = getPlugin(이름);

      if (플러그인) {
        결과 = 결과 || {};
        결과[이름] = 플러그인;
      }
    });

    반환 결과;
  }

  /**
   * 가능한 경우 플러그인 버전을 가져옵니다.
   *
   * @param {문자열} 이름
   * 플러그인의 이름입니다.
   *
   * @return {문자열}
   * 플러그인의 버전 또는 빈 문자열.
   */
  정적 getPluginVersion(이름) {
    const 플러그인 = getPlugin(이름);

    반환 플러그인 && 플러그인.버전 || '';
  }
}

/**
 * 존재하는 경우 이름으로 플러그인을 가져옵니다.
 *
 * @정적
 * @method getPlugin
 * @memberOf 플러그인
 * @param {문자열} 이름
 * 플러그인의 이름입니다.
 *
 * @returns {함수|정의되지 않음}
 * 플러그인(또는 `정의되지 않음`).
 */
Plugin.getPlugin = getPlugin;

/**
 * 등록된 기본 플러그인 클래스의 이름입니다.
 *
 * @유형 {문자열}
 */
Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;

Plugin.registerPlugin(BASE_PLUGIN_NAME, 플러그인);

/**
 * player.js에 문서화됨
 *
 * @무시하다
 */
Player.prototype.usingPlugin = 함수(이름) {
  반환 !!이[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][이름] === 참;
};

/**
 * player.js에 문서화됨
 *
 * @무시하다
 */
Player.prototype.hasPlugin = 함수(이름) {
  return !!pluginExists(이름);
};

기본 플러그인 내보내기;

/**
 * 플러그인이 플레이어에 설정되려고 한다는 신호입니다.
 *
 * @event Player#beforepluginsetup
 * @type {Plugin~PluginEventHash}
 */

/**
 * 플러그인이 플레이어에 설치될 예정임을 이름으로 알립니다. 이름
 *는 플러그인의 이름입니다.
 *
 * @event Player#beforepluginsetup:$name
 * @type {Plugin~PluginEventHash}
 */

/**
 * 플러그인이 방금 플레이어에 설정되었음을 알립니다.
 *
 * @event Player#pluginsetup
 * @type {Plugin~PluginEventHash}
 */

/**
 * 플러그인이 방금 플레이어에 설정되었음을 이름으로 알립니다. 이름
 *는 플러그인의 이름입니다.
 *
 * @event Player#pluginsetup:$name
 * @type {Plugin~PluginEventHash}
 */

/**
 * @typedef {객체} Plugin~PluginEventHash
 *
 * @property {문자열} 인스턴스
 * 기본 플러그인의 경우 플러그인 함수의 반환 값입니다. 을 위한
 * 고급 플러그인, 이벤트가 발생하는 플러그인 인스턴스.
 *
 * @property {문자열} 이름
 * 플러그인의 이름입니다.
 *
 * @property {문자열} 플러그인
 * 기본 플러그인의 경우 플러그인 기능. 고급 플러그인의 경우
 * 플러그인 클래스/생성자.
 */