    import { defaultValue } from '../util'
    import * as Util from '../util/EventUtil'
    import { ProxyEvent } from './event/index'

    /**
     * 事件基类,所有可接收或者发送的对象继承于此
     * @class Evented
     * @moduleEX BaseModule
     * @example
     *  * ```js
     * map.on('click', function(e) {
     * 	alert(e.latlng);
     * } );
     * ```
     * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
     *
     * ```js
     * function onClick(e) { ... }
     *
     * map.on('click', onClick);
     * map.off('click', onClick);
     * ```
     */
    export default class Evented {
      constructor() {
        return this._createEventedProxy()
      }

      /**
       * @private
       * @description: 创建代理对象
       * @return {*}
       */
      _createEventedProxy() {
        this._decorateParents = []
        this._decorateProperties = {}
        const that = this
        return new Proxy(this, {
          set(target, key, value, receiver) {
            if (that._decorateProperties[key]) {
              const decorateProperty = that._decorateProperties[key]
              const { type, createInstance } = decorateProperty
              // 如果类型不一致，重新初始化构建对象
              if (!(value instanceof type)) {
                value = createInstance(value)
              }
              // 如果属于事件基类且没有事件父级，设置事件父级
              if (
                value instanceof Evented &&
                Array.isArray(value._decorateParents) &&
                value._decorateParents.findIndex((v) => v.instance === that) === -1
              ) {
                value._decorateParents.push({
                  instance: that,
                  key
                })
              }
            }
            // 处理设置值重复情况，不触发事件
            if (target[key] === value) {
              return true
            }
            // set eventData响应式刷新内容
            const eventData = {
              target,
              key,
              value,
              oldValue: Reflect.get(target, key),
              receiver
            }
            const result = Reflect.set(target, key, value, receiver)
            // 私有变量不进行响应式更新
            if (/^_[^_].*/.test(key)) {
              return result
            }
            that.decorateEventFire('set', eventData)
            return result // 返回设置结果
          },
          get(target, key, receiver) {
            const result = Reflect.get(target, key, receiver)
            return result
          }
        })
      }

      decorateEventFire(eventName, eventData) {
        this._decorateEventFire(this, this, [], eventName, eventData, eventData)
      }

      /**
       * @private
       * @description: 发送响应式事件
       * @param {*} target 发送源
       * @param {*} sourceTarget 触发源
       * @param {*} firePath 事件路径
       * @param {*} eventName 事件名称
       * @param {*} eventData 事件主体
       * @param {*} sourceEventData 源事件主体
       * @return {*}
       */
      _decorateEventFire(
        target,
        sourceTarget,
        firePath,
        eventName,
        eventData,
        sourceEventData
      ) {
        const { key, value, oldValue } = eventData
        const eventPath = firePath.concat()
        eventPath.unshift(key)
        const currentEventData = {
          key,
          value,
          oldValue
        }
        // 响应式发送事件
        const proxyEvent = new ProxyEvent({
          eventName,
          target,
          eventData: currentEventData,
          eventPath: eventPath.concat(),
          sourceTarget,
          sourceEventData
        })
        target.fire(eventName, proxyEvent)
        // 向父节点传递
        const parents = target._decorateParents || []
        parents.forEach((detail) => {
          const { instance, key } = detail
          let currentKey = key
          if (instance && instance.type === 'collection') {
            currentKey = `${target.id}`
          }
          const parentEventData = {
            key: currentKey,
            value: instance[currentKey] || target,
            oldValue: instance[currentKey] || target
          }
          this._decorateEventFire(
            instance,
            sourceTarget,
            eventPath,
            eventName,
            parentEventData,
            sourceEventData
          )
        })
      }

      /**
       * @private
       * @description: 响应式属性传递方法定义
       * @param {String} key
       * @param {any} value
       * @param {Object} type
       * @param {Function} createInstance
       * @return {Object} 实体
       */
      _decorate(key, value, type, createInstance) {
        createInstance = defaultValue(createInstance, (v) => v)
        let instance = value
        if (!(value instanceof type)) {
          instance = createInstance(defaultValue(value, {}))
        }
        this._decorateProperties[key] = {
          type,
          createInstance
        }
        return instance
      }

      /**
       * @function  Evented.prototype.on
       * @description 注册一个新的监听事件;<br/>
       * 示例如下：<br/>
       * <a href='#event1'>[1、注册一个事件]</a><br/>
       * <a href='#event2'>[2、一次注册多个事件 - 同一个回调函数]</a><br/>
       * <a href='#event3'>[3、一次注册多个事件 - 分别指回调应函数]</a><br/>
       * <a href='#event4'>[4、当types为字符串时 - 指定上下文]</a><br/>
       * <a href='#event5'>[5、当types为对象时 - 指定上下文]</a><br/>
       * @param {String | Object} [types = null] 事件类型<br/>
       * 当types为字符串时，可以定义单个或多个事件，单个事件："click"，多个事件：以空格分割:"click double-click"；<br/>
       * 当types为对象时，使用如下方式指定事件：{'click': onClickFun, 'mouse-move': onMouseMoveFun}
       * @param {Function} [fn = null] 事件回调函数
       * @param {Object} [context = null] 事件回调函数的this关键字将指向的对象
       * @return {Object} 当前实例
       *
       * @example <caption><h5 id='event1'>注册一个事件</h5></caption>
       * // 初始化一个点击事件回调函数
       * const clickFunction = function (event) {
       *   console.log("点击事件:", event)
       * }
       * // 调用MapView或SceneView的on方法注册一个点击事件
       * view.on('click', clickFunction)
       *
       * @example <caption><h5 id='event2'>一次注册多个事件 - 同一个回调函数</h5></caption>
       * // 初始化一个事件回调函数
       * const eventFunction = function (event) {
       *   console.log("事件:", event)
       * }
       *
       * // 调用MapView或SceneView的on方法注册多个事件
       * // 多个事件类型使用同一个回调函数
       * view.on('click right-click-down', eventFunction)
       *
       * @example <caption><h5 id='event3'>一次注册多个事件 - 分别指回调应函数</h5></caption>
       * // 初始化一个左键点击事件回调函数
       * const clickFunction = function (event) {
       *   console.log("click事件:", event)
       * }
       *
       * // 初始化一个右键按下事件回调函数
       * const rightClickFunction = function (event) {
       *   console.log("right-click-down事件:", event)
       * }
       *
       * // 调用MapView或SceneView的on方法注册多个事件
       * // 每一个事件类型，使用单独的回调函数
       * // 注意使用此种方式，一种类型的事件仅能指定一个回调函数
       * view.on({
       *   "click": clickFunction,
       *   "right-click-down": rightClickFunction
       * })
       *
       * @example <caption><h5 id='event4'>指定上下文 - types类型为字符串</h5></caption>
       * // 初始化一个点击事件回调函数
       * const clickFunction = function (event) {
       *   console.log("点击事件:", event)
       *   console.log("上下文对象:", this)
       * }
       * // 调用MapView或SceneView的on方法注册一个点击事件
       * // 指定view为回调函数的上下文对象
       * view.on('click', clickFunction, view)
       *
       * @example <caption><h5 id='event5'>指定上下文 - types类型为对象</h5></caption>
       * // 初始化一个点击事件回调函数
       * const clickFunction = function (event) {
       *   console.log("点击事件:", event)
       *   console.log("上下文对象:", this)
       * }
       * // 调用MapView或SceneView的on方法注册一个点击事件
       * // 指定view为回调函数的上下文对象
       * view.on({
       *   "click": clickFunction,
       *   "right-click-down": clickFunction
       * }, view)
       */
      on(types, fn, context) {
        // types can be a map of types/handlers
        if (typeof types === 'object') {
          for (const type in types) {
            // we don't process space-separated Evented here for performance;
            // it's a hot path since Layer uses the on(obj) syntax
            this._on(type, types[type], fn)
          }
        } else {
          // types can be a string of space-separated words
          types = Util.splitWords(types)

          for (let i = 0, len = types.length; i < len; i++) {
            this._on(types[i], fn, context)
          }
        }

        return this
      }

      /**
       * @function  Evented.prototype.off
       * @description 移除事件<br/>
       * 示例如下:<br/>
       * <a href='#off1'>[1、移除一个事件的指定回调函数]</a><br/>
       * <a href='#off2'>[2、移除一个事件的所有回调函数]</a><br/>
       * <a href='#off3'>[3、移除多个事件的同一个指定的回调函数]</a><br/>
       * <a href='#off4'>[4、移除多个指定事件的回调函数]</a><br/>
       * <a href='#off5'>[5、删除时指定上下文 - types类型为字符串]</a><br/>
       * <a href='#off6'>[6、删除时指定上下文 - types类型为对象]</a><br/>
       * @param {string} [types] 移除指定事件类型上绑定的回调函数<br/>
       * 当类型为字符串时，可以移除单个或多个事件类型绑定的回调函数，单个事件："click"，多个事件：以空格分割:"click double-click"；<br/>
       * 当types为对象时，使用如下方式移除事件：{'click': onClickFun, 'mouse-move': onMouseMoveFun}
       * @param {Function} [fn] 事件回调函数，当types为字符串，且不指定要删除的回调函数时，删除该事件上的所有回调函数
       * @param {Object} [context] 事件回调函数的this关键字将指向的对象
       * @return {Object} 当前实例
       *
       * @example <caption><h5 id='off1'>移除一个事件的指定回调函数</h5></caption>
       * // 一个事件的回调函数
       * const clickFunction = function (event) {
       *   console.log("点击事件:", event)
       * }
       * // 调用MapView或SceneView的off方法移除一个事件的回调函数
       * view.off('click', clickFunction)
       *
       * @example <caption><h5 id='off2'>移除一个事件的所有回调函数</h5></caption>
       * // 一个事件的回调函数1
       * const clickFunction1 = function (event) {
       *   console.log("点击事件1:", event)
       * }
       *
       * // 一个事件的回调函数2
       * const clickFunction2 = function (event) {
       *   console.log("点击事件2:", event)
       * }
       *
       * // 调用MapView或SceneView的off方法移除一个事件的所有回调函数
       * // 不指定回调函数，则移除该事件上的所有绑定的回调函数
       * view.off('click')
       *
       * @example <caption><h5 id='off3'>移除多个事件的同一个指定的回调函数</h5></caption>
       * // 多个事件的同一个回调函数
       * const eventFunction = function (event) {
       *   console.log("事件:", event)
       * }
       * // 调用MapView或SceneView的off方法移除多个事件的同一个指定的回调函数
       * view.off('click double-click', eventFunction)
       *
       * @example <caption><h5 id='off4'>移除多个指定事件的回调函数</h5></caption>
       * // 一个事件的回调函数
       * const clickFunction = function (event) {
       *   console.log("click事件:", event)
       * }
       * // 调用MapView或SceneView的off方法移除多个指定事件的回调函数
       * view.off({
       *    // 移除click事件上一个指定的函数
       *   "click": clickFunction,
       *   // 移除double-click上所有指定的函数
       *   "double-click": undefined
       * })
       *
       * @example <caption><h5 id='off5'>删除时指定上下文 - types类型为字符串</h5></caption>
       * // 一个事件的回调函数
       * const clickFunction = function (event) {
       *   console.log("点击事件:", event)
       * }
       * // 调用MapView或SceneView的off方法移除一个事件的回调函数
       * view.off('click', clickFunction, view)
       * // 调用MapView或SceneView的off方法移除一个事件的所有回调函数
       * view.off('click', undefined, view)
       *
       * @example <caption><h5 id='off6'>删除时指定上下文 - types类型为对象</h5></caption>
       * // 一个事件的回调函数
       * const clickFunction = function (event) {
       *   console.log("click事件:", event)
       * }
       * // 调用MapView或SceneView的off方法移除多个指定事件的回调函数
       * view.off({
       *    // 移除click事件上一个指定的函数
       *   "click": clickFunction,
       *   // 移除double-click上所有指定的函数
       *   "double-click": undefined
       * }, view)
       */
      off(types, fn, context) {
        if (!types) {
          // clear all listeners if called without arguments
          delete this._Evented
        } else if (typeof types === 'object') {
          for (const type in types) {
            this._off(type, types[type], fn)
          }
        } else {
          types = Util.splitWords(types)

          for (let i = 0, len = types.length; i < len; i++) {
            this._off(types[i], fn, context)
          }
        }

        return this
      }

      // attach listener (without syntactic sugar now)
      _on(type, fn, context) {
        this._Evented = this._Evented || {}

        /* get/init listeners for type */
        let typeListeners = this._Evented[type]
        if (!typeListeners) {
          typeListeners = []
          this._Evented[type] = typeListeners
        }

        if (context === this) {
          // Less memory footprint.
          context = undefined
        }
        const newListener = { fn, ctx: context }
        const listeners = typeListeners

        // check if fn already there
        for (let i = 0, len = listeners.length; i < len; i++) {
          if (listeners[i].fn === fn && listeners[i].ctx === context) {
            return
          }
        }

        listeners.push(newListener)
      }

      _off(type, fn, context) {
        let listeners
        let i
        let len

        if (!this._Evented) {
          return
        }

        listeners = this._Evented[type]

        if (!listeners) {
          return
        }

        if (!fn) {
          // Set all removed listeners to noop so they are not called if remove happens in fire
          for (i = 0, len = listeners.length; i < len; i++) {
            listeners[i].fn = Util.falseFn
          }
          // clear all listeners for a type if function isn't specified
          delete this._Evented[type]
          return
        }

        if (context === this) {
          context = undefined
        }

        if (listeners) {
          // find fn and remove it
          for (i = 0, len = listeners.length; i < len; i++) {
            const l = listeners[i]
            if (l.ctx !== context) {
              // eslint-disable-next-line no-continue
              continue
            }
            if (l.fn === fn) {
              // set the removed listener to noop so that's not called if remove happens in fire
              l.fn = Util.falseFn

              if (this._firingCount) {
                /* copy array in case Evented are being fired */
                // eslint-disable-next-line no-multi-assign
                this._Evented[type] = listeners = listeners.slice()
              }
              listeners.splice(i, 1)

              return
            }
          }
        }
      }

      /**
       * @function  Evented.prototype.fire
       * @private
       * @description: 激发指定类型的事件
       * @param {string} [type] 事件类型
       * @param {Object} [data] 待传递的数据
       * @param {Boolean} [propagate] 是否传播到父级
       * @return {Object} 当前实例
       */
      fire(type, data, propagate) {
        if (!this.listens(type, propagate)) {
          return this
        }

        const event = data

        if (event) {
          event.type = type
          event.target = this
          event.sourceTarget = (data && data.sourceTarget) || this
        }

        if (this._Evented) {
          const listeners = this._Evented[type]

          if (listeners) {
            this._firingCount = this._firingCount + 1 || 1
            for (let i = 0, len = listeners.length; i < len; i++) {
              const l = listeners[i]
              l.fn.call(l.ctx || this, event)
            }

            this._firingCount--
          }
        }

        if (propagate) {
          // propagate the event to parents (set with addEventParent)
          this._propagateEvent(event)
        }
        return this
      }

      /**
       * @function  Evented.prototype.listens
       * @private
       * @description: 验证是否监听此事件
       * @param {string} [type] 事件类型
       * @param {Boolean} [propagate] 验证父级监听此事件
       * @return {Boolean} 当前实例
       */
      listens(type, propagate) {
        const listeners = this._Evented && this._Evented[type]
        if (listeners && listeners.length) {
          return true
        }

        if (propagate) {
          // also check parents for listeners if event propagates
          for (const id in this._eventParents) {
            if (this._eventParents[id].listens(type, propagate)) {
              return true
            }
          }
        }
        return false
      }

      /**
       * @function  Evented.prototype.once
       * @private
       * @description: 事件监听，仅触发一次
       * @param {string} [types] 将侦听器函数（fn）添加到对象的特定事件类型中。您还可以传递几个空格分隔的类型（例如“click dblclick”），也可以传入例如 {click: onClick, mousemove: onMouseMove}。
       * @param {Function} [fn] 侦听器函数
       * @param {Object} [context] 指定侦听器的上下文（this关键字将指向的对象）
       * @return {Object} 当前实例
       */
      once(types, fn, context) {
        const self = this
        if (typeof types === 'object') {
          for (const type in types) {
            this.once(type, types[type], fn)
          }
          return this
        }

        const handler = Util.bind(function () {
          self.off(types, fn, context).off(types, handler, context)
        }, this)

        // add a listener that's executed once and removed after that
        return this.on(types, fn, context).on(types, handler, context)
      }

      /**
       * @private
       * @function  Evented.prototype.addEventParent
       * @description: 添加事件父级-将接收传播事件的Evented
       * @param {Evented} [obj] 事件父级对象
       * @return {Object} 当前实例
       */
      addEventParent(obj) {
        this._eventParents = this._eventParents || {}
        this._eventParents[Util.stamp(obj)] = obj
        return this
      }

      /**
       * @function  Evented.prototype.removeEventParent
       * @private
       * @description: 移除事件父级-将接收传播事件的Evented
       * @param {Evented} [obj] 事件父级对象
       * @return {Object} 当前实例
       */
      removeEventParent(obj) {
        if (this._eventParents) {
          delete this._eventParents[Util.stamp(obj)]
        }
        return this
      }

      _propagateEvent(e) {
        for (const id in this._eventParents) {
          this._eventParents[id].fire(
            e.type,
            Util.extend(
              {
                layer: e.target,
                propagatedFrom: e.target
              },
              e
            ),
            true
          )
        }
      }
    }

    // aliases; we should ditch those eventually

    // @method addEventListener(…): this
    // Alias to [`on(…)`](#evented-on)
    Evented.addEventListener = Evented.on

    // @method removeEventListener(…): this
    // Alias to [`off(…)`](#evented-off)

    // @method clearAllEventListeners(…): this
    // Alias to [`off()`](#evented-off)
    // eslint-disable-next-line no-multi-assign
    Evented.removeEventListener = Evented.clearAllEventListeners = Evented.off

    // @method addOneTimeEventListener(…): this
    // Alias to [`once(…)`](#evented-once)
    Evented.addOneTimeEventListener = Evented.once

    // @method fireEvent(…): this
    // Alias to [`fire(…)`](#evented-fire)
    Evented.fireEvent = Evented.fire

    // @method hasEventListeners(…): Boolean
    // Alias to [`listens(…)`](#evented-listens)
    Evented.hasEventListeners = Evented.listens