/**
 * @파일 이벤트.js. 이벤트 시스템(John Resig - JS Ninja의 비밀 http://jsninja.com/)
 * (원본 버전은 완전히 사용할 수 없었기 때문에 몇 가지 사항을 수정하고 클로저 컴파일러와 호환되도록 했습니다.)
 * 이것은 jQuery의 이벤트와 매우 유사하게 작동해야 하지만 책 버전을 기반으로 합니다.
 * jquery만큼 강력하므로 약간의 차이가 있을 수 있습니다.
 *
 * @파일 이벤트.js
 * @module 이벤트
 */
'./dom-data'에서 DomData 가져오기;
import * as Guid from './guid.js';
'./log.js'에서 로그 가져오기;
'글로벌/창'에서 창 가져오기;
'글로벌/문서'에서 문서 가져오기;

/**
 * 리스너 캐시 및 디스패처 정리
 *
 * @param {요소|객체} 요소
 * 정리할 요소
 *
 * @param {문자열} 유형
 * 정리할 이벤트 유형
 */
function _cleanUpEvents(요소, 유형) {
  if (!DomData.has(elem)) {
    반품;
  }
  const 데이터 = DomData.get(elem);

  // 남아있는 이벤트가 없으면 특정 유형의 이벤트를 제거합니다.
  if (data.handlers[유형].길이 === 0) {
    데이터 삭제.핸들러[유형];
    // data.handlers[유형] = null;
    // null로 설정하면 data.handlers에서 오류가 발생했습니다.

    // 요소에서 메타 핸들러를 제거합니다.
    if (elem.removeEventListener) {
      elem.removeEventListener(type, data.dispatcher, false);
    } 그렇지 않으면 (elem.detachEvent) {
      elem.detachEvent('on' + type, data.dispatcher);
    }
  }

  // 남은 유형이 없으면 이벤트 객체를 제거합니다.
  if (Object.getOwnPropertyNames(data.handlers).length < = 0) {
    data.handlers 삭제;
    data.dispatcher 삭제;
    데이터 삭제.비활성화;
  }

  // 데이터가 남아 있지 않으면 마지막으로 요소 데이터를 제거합니다.
  if (Object.getOwnPropertyNames(data).length === 0) {
    DomData.delete(elem);
  }
}

/**
 * 이벤트 유형 배열을 반복하고 각 유형에 대해 요청된 메서드를 호출합니다.
 *
 * @param {함수} fn
 * 우리가 사용하고자 하는 이벤트 메소드.
 *
 * @param {요소|객체} 요소
 * 리스너를 바인딩할 요소 또는 객체
 *
 * @param {문자열} 유형
 * 바인딩할 이벤트 유형.
 *
 * @param {EventTarget~EventListener} 콜백
 * 이벤트 리스너.
 */
function _handleMultipleEvents(fn, 요소, 유형, 콜백) {
  types.forEach(함수(유형) {
    // 각 유형에 대한 이벤트 메소드를 호출합니다.
    fn(요소, 유형, 콜백);
  });
}

/**
 * 표준 속성 값을 갖도록 기본 이벤트 수정
 *
 * @param {객체} 이벤트
 * 수정할 이벤트 개체입니다.
 *
 * @return {객체}
 * 고정 이벤트 개체.
 */
내보내기 기능 fixEvent(event) {
  경우 (event.fixed_) {
    반환 이벤트;
  }

  함수 returnTrue() {
    true를 반환합니다.
  }

  함수 returnFalse() {
    거짓을 반환합니다.
  }

  // 수정이 필요한지 테스트
  // isPropagationStopped 대신 !event.stopPropagation인지 확인하는 데 사용됩니다.
  // 하지만 네이티브 이벤트는 stopPropagation에 대해 true를 반환하지만
  // isPropagationStopped와 같은 다른 예상 메서드입니다. 문제인듯
  // Javascript Ninja 코드로. 이제 모든 이벤트를 무시하고 있습니다.
  if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
    const 이전 = 이벤트 || 창.이벤트;

    이벤트 = {};
    // 값을 수정할 수 있도록 이전 개체를 복제합니다. event = {};
    // IE8은 기본 이벤트 속성을 엉망으로 만드는 것을 좋아하지 않습니다.
    // Firefox는 event.hasOwnProperty('type') 및 기타 소품에 대해 false를 반환합니다.
    // 복사를 더 어렵게 만듭니다.
    // 할 것: 이벤트 소품의 화이트리스트를 만드는 것이 가장 좋습니다.
    for (예전의 const 키) {
      // 더 이상 사용되지 않는 layerX/Y를 복사하려고 하면 Safari 6.0.3에서 경고합니다.
      // 지원 중단된 keyboardEvent.keyLocation을 복사하려고 하면 Chrome에서 경고를 표시합니다.
      // 그리고 webkitMovementX/Y
      // Lighthouse는 Event.path가 복사되면 불평합니다.
      if (키 !== '레이어X' && 키 !== '레이어Y' && 키 !== '키 위치' &&
          키 !== 'webkitMovementX' && 키 !== 'webkitMovementY' &&
          키 !== '경로') {
        // Chrome 32+는 더 이상 사용되지 않는 returnValue를 복사하려고 하면 경고하지만
        // preventDefault가 지원되지 않는 경우(IE8) 여전히 원합니다.
        if (!(키 === '반환값' && old.preventDefault)) {
          이벤트[키] = 이전[키];
        }
      }
    }

    // 이 요소에서 발생한 이벤트
    if (!이벤트.대상) {
      event.target = event.srcElement || 문서;
    }

    // 이벤트와 관련된 다른 요소를 처리합니다.
    if (!event.relatedTarget) {
      event.relatedTarget = event.fromElement === event.target ?
        event.toElement :
        이벤트.fromElement;
    }

    // 기본 브라우저 동작 중지
    event.preventDefault = 함수() {
      경우 (old.preventDefault) {
        old.preventDefault();
      }
      event.returnValue = 거짓;
      old.returnValue = 거짓;
      event.defaultPrevented = 참;
    };

    event.defaultPrevented = 거짓;

    // 이벤트 버블링 중지
    event.stopPropagation = 함수() {
      if (old.stopPropagation) {
        old.stopPropagation();
      }
      event.cancelBubble = 참;
      old.cancelBubble = 참;
      event.isPropagationStopped = returnTrue;
    };

    event.isPropagationStopped = returnFalse;

    // 이벤트가 버블링되고 다른 핸들러가 실행되는 것을 중지합니다.
    event.stopImmediatePropagation = 함수() {
      if (old.stopImmediatePropagation) {
        old.stopImmediatePropagation();
      }
      event.isImmediatePropagationStopped = returnTrue;
      event.stopPropagation();
    };

    event.isImmediatePropagationStopped = returnFalse;

    // 마우스 위치 처리
    if (event.clientX !== null && event.clientX !== 정의되지 않음) {
      const doc = 문서.문서 요소;
      const 본문 = 문서.본문;

      event.pageX = event.clientX +
        (문서 && doc.scrollLeft || 몸 && body.scrollLeft || 0) -
        (문서 && doc.clientLeft || 몸 && body.clientLeft || 0);
      이벤트.페이지Y = 이벤트.클라이언트Y +
        (문서 && doc.scrollTop || 몸 && body.scrollTop || 0) -
        (문서 && doc.clientTop || 몸 && body.client탑 || 0);
    }

    // 키 누름 처리
    event.which = event.charCode || 이벤트.키코드;

    // 마우스 클릭 버튼 수정:
    // 0 == 왼쪽; 1 == 중간; 2 == 오른쪽
    if (event.button !== null && event.button !== 정의되지 않음) {

      // 다음은 videojs-standard를 통과하지 않기 때문에 비활성화됩니다.
      // 그리고... 이런.
      /* eslint 비활성화 */
      이벤트.버튼 = (이벤트.버튼 & 1 ? 0 :
        (이벤트.버튼 & 4 ? 1 :
          (이벤트.버튼 & 2? 2: 0)));
      /* eslint 활성화 */
    }
  }

  event.fixed_ = 참;
  // 고정된 인스턴스를 반환합니다.
  반환 이벤트;
}

/**
 * 패시브 이벤트 리스너 지원 여부
 */
let _supportsPassive;

const supportsPassive = function() {
  if (typeof _supportsPassive !== '부울') {
    _supportsPassive = 거짓;
    {
      const opts = Object.defineProperty({}, 'passive', {
        얻다() {
          _supportsPassive = 참;
        }
      });

      window.addEventListener('테스트', null, opts);
      window.removeEventListener('테스트', null, opts);
    } 잡기 (e) {
      // 무시
    }
  }

  return _supportsPassive;
};

/**
 * 크롬이 수동적일 것으로 예상되는 터치 이벤트
 */
const passiveEvents = [
  '터치스타트',
  '터치무브'
];

/**
 * 요소에 이벤트 리스너 추가
 * 핸들러 기능을 별도의 캐시 객체에 저장
 * 요소의 이벤트에 일반 핸들러를 추가합니다.
 * 요소에 대한 고유 ID(guid)와 함께.
 *
 * @param {요소|객체} 요소
 * 리스너를 바인딩할 요소 또는 객체
 *
 * @param {문자열|문자열[]} 유형
 * 바인딩할 이벤트 유형.
 *
 * @param {EventTarget~EventListener} fn
 * 이벤트 리스너.
 */
내보내기 기능 on(elem, type, fn) {
  if (Array.isArray(유형)) {
    return _handleMultipleEvents(on, 요소, 유형, fn);
  }

  if (!DomData.has(elem)) {
    DomData.set(elem, {});
  }

  const 데이터 = DomData.get(elem);

  // 모든 핸들러 데이터를 저장할 장소가 필요합니다.
  if (!data.handlers) {
    데이터 핸들러 = {};
  }

  if (!data.handlers[유형]) {
    data.handlers[유형] = [];
  }

  if (!fn.guid) {
    fn.guid = Guid.newGUID();
  }

  data.handlers[유형].push(fn);

  if (!data.dispatcher) {
    데이터.비활성화 = 거짓;

    data.dispatcher = 함수(이벤트, 해시) {

      if (데이터.비활성화) {
        반품;
      }

      이벤트 = 고정 이벤트(이벤트);

      const 핸들러 = data.handlers[event.type];

      if (핸들러) {
        // 핸들러를 복사하여 프로세스 중에 핸들러가 추가/제거되더라도 모든 것을 버리지 않습니다.
        const handlersCopy = handlers.slice(0);

        for (let m = 0, n = handlersCopy.length; m < N; m++) {
          if (event.isImmediatePropagationStopped()) {
            부서지다;
          } else {
            {
              handlersCopy[m].call(elem, event, hash);
            } 잡기 (e) {
              log.error(e);
            }
          }
        }
      }
    };
  }

  if (data.handlers[유형].길이 === 1) {
    if (elem.addEventListener) {
      let 옵션 = 거짓;

      if (supportsPassive() &&
        passiveEvents.indexOf(유형) > -1) {
        옵션 = {수동: 참};
      }
      elem.addEventListener(유형, data.dispatcher, 옵션);
    } 그렇지 않으면 (elem.attachEvent) {
      elem.attachEvent('on' + type, data.dispatcher);
    }
  }
}

/**
 * 요소에서 이벤트 리스너 제거
 *
 * @param {요소|객체} 요소
 * 리스너를 제거할 객체.
 *
 * @param {문자열|문자열[]} [유형]
 * 제거할 리스너 유형. 요소에서 모든 이벤트를 제거하려면 포함하지 마십시오.
 *
 * @param {EventTarget~EventListener} [fn]
 * 제거할 특정 리스너. 이벤트에 대한 리스너를 제거하려면 포함하지 마십시오.
 * 유형.
 */
내보내기 기능 해제(요소, 유형, fn) {
  // 필요하지 않은 경우 getElData를 통해 캐시 객체를 추가하고 싶지 않음
  if (!DomData.has(elem)) {
    반품;
  }

  const 데이터 = DomData.get(elem);

  // 이벤트가 없으면 바인딩 해제할 항목이 없습니다.
  if (!data.handlers) {
    반품;
  }

  if (Array.isArray(유형)) {
    return _handleMultipleEvents(off, 요소, 유형, fn);
  }

  // 유틸리티 함수
  const removeType = 함수(el, t) {
    data.handlers[t] = [];
    _cleanUpEvents(el, t);
  };

  // 바인딩된 모든 이벤트를 제거하고 있습니까?
  if (유형 === 정의되지 않음) {
    for (data.handlers의 const t) {
      if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
        removeType(요소, t);
      }
    }
    반품;
  }

  const 핸들러 = data.handlers[유형];

  // 핸들러가 없으면 바인딩 해제할 항목이 없습니다.
  if (!핸들러) {
    반품;
  }

  // 리스너가 제공되지 않은 경우 유형에 대한 모든 리스너를 제거합니다.
  경우 (!fn) {
    removeType(요소, 유형);
    반품;
  }

  // 단일 핸들러만 제거합니다.
  경우 (fn.guid) {
    에 대한 (n = 0; n < 핸들러.길이; n++) {
      if (핸들러[n].guid === fn.guid) {
        handlers.splice(n--, 1);
      }
    }
  }

  _cleanUpEvents(요소, 유형);
}

/**
 * 요소에 대한 이벤트 트리거
 *
 * @param {요소|객체} 요소
 * 이벤트를 트리거하는 요소
 *
 * @param {EventTarget~Event|문자열} 이벤트
 * 문자열(유형) 또는 유형 속성이 있는 이벤트 객체
 *
 * @param {객체} [해시]
 * 이벤트와 함께 전달할 데이터 해시
 *
 * @return {부울|정의되지 않음}
 * defaultPrevented의 반대인 경우 defaultPrevented를 반환합니다.
 * 방지. 그렇지 않으면 `정의되지 않음`을 반환합니다.
 */
내보내기 기능 트리거(요소, 이벤트, 해시) {
  // 요소 데이터와 부모에 대한 참조를 가져옵니다(버블링용).
  // 모든 부모에 대해 캐시할 데이터 객체를 추가하고 싶지는 않습니다.
  // 따라서 hasElData를 먼저 확인합니다.
  const elemData = DomData.has(elem) ? DomData.get(elem) : {};
  const 부모 = elem.parentNode || elem.ownerDocument;
  // 유형 = 이벤트.유형 || 이벤트,
  // 핸들러;

  // 이벤트 이름이 문자열로 전달된 경우 이벤트를 생성합니다.
  if (이벤트 유형 === '문자열') {
    이벤트 = {유형: 이벤트, 대상: 요소};
  } 그렇지 않으면 (!event.target) {
    event.target = 요소;
  }

  // 이벤트 속성을 정규화합니다.
  이벤트 = 고정 이벤트(이벤트);

  // 전달된 요소에 디스패처가 있으면 설정된 핸들러를 실행합니다.
  if (elemData.dispatcher) {
    elemData.dispatcher.call(요소, 이벤트, 해시);
  }

  // 명시적으로 중지되거나 이벤트가 버블링되지 않는 한(예: 미디어 이벤트)
  // 이 함수를 재귀적으로 호출하여 이벤트를 DOM 위로 버블링합니다.
  만약 (부모 && !event.isPropagationStopped() && event.bubbles === true) {
    trigger.call(null, 부모, 이벤트, 해시);

  // DOM의 맨 위에 있는 경우 비활성화되지 않는 한 기본 작업을 트리거합니다.
  } 그렇지 않으면 (!부모 && !event.default방지됨 && 이벤트.대상 && 이벤트.대상[이벤트.유형]) {
    if (!DomData.has(event.target)) {
      DomData.set(event.target, {});
    }
    const targetData = DomData.get(event.target);

    // 대상에 이 이벤트에 대한 기본 동작이 있는지 확인합니다.
    if (이벤트.대상[이벤트.유형]) {
      // 이미 핸들러를 실행했기 때문에 대상에서 이벤트 디스패치를 일시적으로 비활성화합니다.
      targetData.disabled = 참;
      // 기본 동작을 실행합니다.
      if (typeof event.target[event.type] === '함수') {
        이벤트.대상[이벤트.유형]();
      }
      // 이벤트 디스패치를 다시 활성화합니다.
      targetData.disabled = 거짓;
    }
  }

  // false를 반환하여 기본값이 차단된 경우 트리거러에 알립니다.
  반환 !event.defaultPrevented;
}

/**
 * 이벤트에 대해 리스너를 한 번만 트리거합니다.
 *
 * @param {요소|객체} 요소
 * 바인딩할 요소 또는 객체.
 *
 * @param {문자열|문자열[]} 유형
 * 행사명/종류
 *
 * @param {이벤트~이벤트리스너} fn
 * 이벤트 리스너 기능
 */
내보내기 기능 one(elem, type, fn) {
  if (Array.isArray(유형)) {
    return _handleMultipleEvents(one, elem, type, fn);
  }
  const func = 함수() {
    off(요소, 유형, 기능);
    fn.apply(this, 인수);
  };

  // 원래 함수의 ID를 사용하여 제거할 수 있도록 새 함수에 guid를 복사합니다.
  func.guid = fn.guid = fn.guid || Guid.newGUID();
  on(요소, 유형, 기능);
}

/**
 * 리스너를 한 번만 트리거한 다음 모두 끄면 끕니다.
 * 구성된 이벤트
 *
 * @param {요소|객체} 요소
 * 바인딩할 요소 또는 객체.
 *
 * @param {문자열|문자열[]} 유형
 * 행사명/종류
 *
 * @param {이벤트~이벤트리스너} fn
 * 이벤트 리스너 기능
 */
내보내기 기능 any(elem, type, fn) {
  const func = 함수() {
    off(요소, 유형, 기능);
    fn.apply(this, 인수);
  };

  // 원래 함수의 ID를 사용하여 제거할 수 있도록 새 함수에 guid를 복사합니다.
  func.guid = fn.guid = fn.guid || Guid.newGUID();

  // 여러 번 켜지만 모든 것에 대해 한 번만 꺼짐
  on(요소, 유형, 기능);
}