/**
 * @fileOverview item
 * @author huangtonger@aliyun.com
 */

const Util = require('../util/')
const Shape = require('../shape')
const Global = require('../global')
const CACHE_BBOX = 'bboxCache'
const GLOBAL_STATE_STYLE_SUFFIX = 'StateStyle'
const NAME_STYLE = 'Style' // cache 缓存的状态属性的名字
const RESERVED_STYLES = ['fillStyle', 'strokeStyle', 'path', 'points', 'img', 'symbol']

class Item {
  constructor(cfg) {
    const defaultCfg = {
      /**
       * id
       * @type {string}
       */
      id: null,

      /**
       * 类型
       * @type {string}
       */
      type: 'item',

      /**
       * data model
       * @type {object}
       */
      model: {},

      /**
       * g group
       * @type {G.Group}
       */
      group: null,

      /**
       * is open animate
       * @type {boolean}
       */
      animate: false,

      /**
       * visible - not group visible
       * @type {boolean}
       */
      visible: true,

      /**
       * locked - lock node
       * @type {boolean}
       */
      locked: false,
      /**
       * capture event
       * @type {boolean}
       */
      event: true,
      /**
       * key shape to calculate item's bbox
       * @type object
       */
      keyShape: null,
      /**
       * item's states, such as selected or active
       * @type Array
       */
      states: []
    }
    this._cfg = Util.mix(defaultCfg, this.getDefaultCfg(), cfg)
    const group = cfg.group
    group.set('item', this)
    let id = this.get('model').id
    if (!id || id === '') {
      id = Util.uniqueId(this.get('type'))
    }
    this.set('id', id)
    group.set('id', id)
    this.init()
    this.draw()
  }

  /**
   * 是否是 Item 对象，悬空边情况下进行判定
   * @return {Boolean} 是否是 Item 对象
   */
  isItem() {
    return true
  }

  /**
   * 获取属性
   * @internal 仅内部类使用
   * @param  {String} key 属性名
   * @return {*} 属性值
   */
  get(key) {
    return this._cfg[key]
  }

  /**
   * 设置属性
   * @internal 仅内部类使用
   * @param {String|Object} key 属性名，也可以是对象
   * @param {*} val 属性值
   */
  set(key, val) {
    if (Util.isPlainObject(key)) {
      this._cfg = Util.mix({}, this._cfg, key)
    } else {
      this._cfg[key] = val
    }
  }
  /**
   * 获取默认的配置项
   * @protected 供子类复写
   * @return {Object} 配置项
   */
  getDefaultCfg() {
    return {}
  }
  /**
   * 初始化
   * @protected
   */
  init() {
    const shapeFactory = Shape.getFactory(this.get('type'))
    this.set('shapeFactory', shapeFactory)
  }
  // 根据 keyshape 计算包围盒
  _calculateBBox() {
    const keyShape = this.get('keyShape')
    const group = this.get('group')
    // 因为 group 可能会移动，所以必须通过父元素计算才能计算出正确的包围盒
    const bbox = Util.getBBox(keyShape, group)
    bbox.x = bbox.minX
    bbox.y = bbox.minY
    bbox.width = bbox.maxX - bbox.minX
    bbox.height = bbox.maxY - bbox.minY
    bbox.centerX = (bbox.minX + bbox.maxX) / 2
    bbox.centerY = (bbox.minY + bbox.maxY) / 2
    return bbox
  }

  // 绘制
  _drawInner() {
    const self = this
    const shapeFactory = self.get('shapeFactory')
    const group = self.get('group')
    const model = self.get('model')
    group.clear()

    if (!shapeFactory) {
      return
    }
    self.updatePosition(model)
    const cfg = self.getShapeCfg(model) // 可能会附加额外信息
    const shapeType = cfg.shape
    const keyShape = shapeFactory.draw(shapeType, cfg, group)
    if (keyShape) {
      keyShape.isKeyShape = true
      self.set('keyShape', keyShape)
      self.set('originStyle', this.getKeyShapeStyle())
    }
    // 防止由于用户外部修改 model 中的 shape 导致 shape 不更新
    this.set('currentShape', shapeType)
    this._resetStates(shapeFactory, shapeType)
  }

  getKeyShapeStyle() {
    const keyShape = this.getKeyShape()
    if (keyShape) {
      const styles = {}
      Util.each(keyShape.attr(), (val, key) => {
        if (RESERVED_STYLES.indexOf(key) < 0) {
          styles[key] = val
        }
      })
      return styles
    }
  }

  _resetStates(shapeFactory, shapeType) {
    const self = this
    const states = self.get('states')
    Util.each(states, state => {
      shapeFactory.setState(shapeType, state, true, self)
    })
  }

  /**
   * 获取当前元素的所有状态
   * @return {Array} 元素的所有状态
   */
  getStates() {
    return this.get('states')
  }

  /**
   * 当前元素是否处于某状态
   * @param {String} state 状态名
   * @return {Boolean} 是否处于某状态
   */
  hasState(state) {
    return this.get('states').indexOf(state) >= 0
  }

  getStateStyle(state) {
    const self = this
    // Global.nodeStateStyle
    const globalStyle = Global[self.getType() + GLOBAL_STATE_STYLE_SUFFIX][state]
    const styles = this.get('styles')
    const defaultStyle = styles && styles[state]
    // 状态名 + style（activeStyle) 存储在 item 中，如果 item 中不存在这些信息，则使用默认的样式
    const fieldName = state + NAME_STYLE
    return Util.mix({}, globalStyle, defaultStyle, self.get(fieldName))
  }

  getOriginStyle() {
    return this.get('originStyle')
  }

  getCurrentStatesStyle() {
    const self = this
    const originStyle = Util.mix({}, self.getOriginStyle())
    Util.each(self.getStates(), state => {
      Util.mix(originStyle, self.getStateStyle(state))
    })
    return originStyle
  }

  /**
   * 更改元素状态， visible 不属于这个范畴
   * @internal 仅提供内部类 graph 使用
   * @param {String} state 状态名
   * @param {Boolean} enable 节点状态值
   */
  setState(state, enable) {
    const states = this.get('states')
    const shapeFactory = this.get('shapeFactory')
    const index = states.indexOf(state)
    if (enable) {
      if (index > -1) {
        return
      }
      states.push(state)
    } else if (index > -1) {
      states.splice(index, 1)
    }
    if (shapeFactory) {
      const model = this.get('model')
      shapeFactory.setState(model.shape, state, enable, this)
    }
  }

  clearStates(states) {
    const self = this
    const originStates = self.getStates()
    const shapeFactory = self.get('shapeFactory')
    const shape = self.get('model').shape
    if (!states) {
      self.set('states', [])
      shapeFactory.setState(shape, originStates[0], false, self)
      return
    }
    if (Util.isString(states)) {
      states = [states]
    }
    const newStates = originStates.filter(state => {
      shapeFactory.setState(shape, state, false, self)
      if (states.indexOf(state) >= 0) {
        return false
      }
      return true
    })
    self.set('states', newStates)
  }

  /**
   * 节点的图形容器
   * @return {G.Group} 图形容器
   */
  getContainer() {
    return this.get('group')
  }

  /**
   * 节点的关键形状，用于计算节点大小，连线截距等
   * @return {G.Shape} 关键形状
   */
  getKeyShape() {
    return this.get('keyShape')
  }

  /**
   * 节点数据模型
   * @return {Object} 数据模型
   */
  getModel() {
    return this.get('model')
  }

  /**
   * 节点类型
   * @return {string} 节点的类型
   */
  getType() {
    return this.get('type')
  }

  /**
   * 渲染前的逻辑，提供给子类复写
   * @protected
   */
  beforeDraw() {

  }
  /**
   * 渲染后的逻辑，提供给子类复写
   * @protected
   */
  afterDraw() {

  }

  getShapeCfg(model) {
    const styles = this.get('styles')
    if (styles && styles.default) {
      // merge graph的item样式与数据模型中的样式
      const newModel = Util.mix({}, model)
      newModel.style = Util.mix({}, styles.default, model.style)
      return newModel
    }
    return model
  }

  /**
   * 刷新一般用于处理几种情况
   * 1. item model 在外部被改变
   * 2. 边的节点位置发生改变，需要重新计算边
   *
   * 因为数据从外部被修改无法判断一些属性是否被修改，直接走位置和 shape 的更新
   */
  refresh() {
    const model = this.get('model')
    // 更新元素位置
    this.updatePosition(model)
    // 更新元素内容，样式
    this.updateShape()
    // 做一些更新之后的操作
    this.afterUpdate()
    // 清除缓存
    this.clearCache()
  }

  /**
   * 将更新应用到 model 上，刷新属性
   * @internal 仅提供给 Graph 使用，外部直接调用 graph.update 接口
   * @param  {Object} cfg       配置项，可以是增量信息
   */
  update(cfg) {
    const model = this.get('model')
    const originPosition = { x: model.x, y: model.y }
    // 直接将更新合到原数据模型上，可以保证用户在外部修改源数据然后刷新时的样式符合期待。
    Util.mix(model, cfg)
    const onlyMove = this._isOnlyMove(cfg)
    // 仅仅移动位置时，既不更新，也不重绘
    if (onlyMove) {
      this.updatePosition(model)
    } else {
      // 如果 x,y 有变化，先重置位置
      if (originPosition.x !== model.x || originPosition.y !== model.y) {
        this.updatePosition(model)
      }
      this.updateShape()
    }
    this.afterUpdate()
    this.clearCache()
  }

  /**
   * 更新元素内容，样式
   */
  updateShape() {
    const shapeFactory = this.get('shapeFactory')
    const model = this.get('model')
    const shape = model.shape
    // 判定是否允许更新
    // 1. 注册的节点允许更新
    // 2. 更新后的 shape 等于原先的 shape
    if (shapeFactory.shouldUpdate(shape) && shape === this.get('currentShape')) {
      const updateCfg = this.getShapeCfg(model)
      shapeFactory.update(shape, updateCfg, this)
    } else {
      // 如果不满足上面两种状态，重新绘制
      this.draw()
    }
    this.set('originStyle', this.getKeyShapeStyle())
    // 更新后重置节点状态
    this._resetStates(shapeFactory, shape)
  }

  /**
   * 更新位置，避免整体重绘
   * @param {object} cfg 待更新数据
   */
  updatePosition(cfg) {
    const model = this.get('model')

    const x = Util.isNil(cfg.x) ? model.x : cfg.x
    const y = Util.isNil(cfg.y) ? model.y : cfg.y

    const group = this.get('group')
    if (Util.isNil(x) || Util.isNil(y)) {
      return
    }
    group.resetMatrix()
    group.translate(x, y)
    model.x = x
    model.y = y
    this.clearCache() // 位置更新后需要清除缓存
  }

  /**
   * 更新后做一些工作
   * @protected
   */
  afterUpdate() {

  }

  /**
   * 更新/刷新等操作后，清除 cache
   */
  clearCache() {
    this.set(CACHE_BBOX, null)
  }

  /**
   * 绘制元素
   */
  draw() {
    this.beforeDraw()
    this._drawInner()
    this.afterDraw()
  }

  /**
   * 获取元素的包围盒
   * @return {Object} 包含 x,y,width,height, centerX, centerY
   */
  getBBox() {
    let bbox = this.get(CACHE_BBOX)
    if (!bbox) { // 计算 bbox 开销有些大，缓存
      bbox = this._calculateBBox()
      this.set(CACHE_BBOX, bbox)
    }
    return bbox
  }

  /**
   * 将元素放到最前面
   */
  toFront() {
    this.get('group').toFront()
  }

  /**
   * 将元素放到最后面
   */
  toBack() {
    this.get('group').toBack()
  }

  /**
   * 显示元素
   */
  show() {
    this.changeVisibility(true)
  }

  /**
   * 隐藏元素
   */
  hide() {
    this.changeVisibility(false)
  }

  /**
   * 更改是否显示
   * @param  {Boolean} visible 是否显示
   */
  changeVisibility(visible) {
    const group = this.get('group')
    if (visible) {
      group.show()
    } else {
      group.hide()
    }
    this.set('visible', visible)
  }

  /**
   * 是否拾取及出发该元素的交互事件
   * @param {Boolean} enable 标识位
   */
  enableCapture(enable) {
    const group = this.get('group')
    group && group.attr('capture', enable)
  }

  isVisible() {
    return this.get('visible')
  }
  /**
   * 析构函数
   */
  destroy() {
    if (!this.destroyed) {
      const animate = this.get('animate')
      const group = this.get('group')
      if (animate) {
        group.stopAnimate()
      }
      group.remove()
      this._cfg = null
      this.destroyed = true
    }
  }
}

module.exports = Item
