import Node from '../core/render/node/Node'
import { CONSTANTS, initRootNodePositionMap } from '../constants/constant'
import Lru from '../utils/Lru'
import { createUid } from '../utils/index'

//  布局基类
class Base {
  //  构造函数
  constructor(renderer) {
    // 渲染实例
    this.renderer = renderer
    // 控制实例
    this.mindMap = renderer.mindMap
    // 绘图对象
    this.draw = this.mindMap.draw
    this.lineDraw = this.mindMap.lineDraw
    // 根节点
    this.root = null
    this.lru = new Lru(this.mindMap.opt.maxNodeCacheCount)
    // 当initRootNodePosition不为默认的值时，根节点的位置距默认的配置时根节点距离的差值
    this.rootNodeCenterOffset = null
  }

  //  计算节点位置
  doLayout() {
    throw new Error('【computed】方法为必要方法，需要子类进行重写！')
  }

  //  连线
  renderLine() {
    throw new Error('【renderLine】方法为必要方法，需要子类进行重写！')
  }

  //  定位展开收缩按钮
  renderExpandBtn() {
    throw new Error('【renderExpandBtn】方法为必要方法，需要子类进行重写！')
  }

  //  概要节点
  renderGeneralization() {}

  // 通过uid缓存节点
  cacheNode(uid, node) {
    // 记录本次渲染时的节点
    this.renderer.nodeCache[uid] = node
    // 缓存所有渲染过的节点
    this.lru.add(uid, node)
  }

  // 检查当前来源是否需要重新计算节点大小
  checkIsNeedResizeSources() {
    return [
      CONSTANTS.CHANGE_THEME,
      CONSTANTS.TRANSFORM_TO_NORMAL_NODE
    ].includes(this.renderer.renderSource)
  }

  // 层级类型改变
  checkIsLayerTypeChange(oldIndex, newIndex) {
    if (oldIndex >= 2 && newIndex >= 2) return false
    if (oldIndex >= 2 && newIndex < 2) return true
    if (oldIndex < 2 && newIndex >= 2) return true
  }

  // 检查是否是结构布局改变重新渲染展开收起按钮占位元素
  checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(node) {
    if (this.renderer.renderSource === CONSTANTS.CHANGE_LAYOUT) {
      node.needRerenderExpandBtnPlaceholderRect = true
    }
  }

  //  创建节点实例
  createNode(data, parent, isRoot, layerIndex) {
    // 创建节点
    const uid = data.data.uid
    let newNode = null
    // 数据上保存了节点引用，那么直接复用节点
    if (data && data._node && !this.renderer.reRender) {
      newNode = data._node
      const isLayerTypeChange = this.checkIsLayerTypeChange(
        newNode.layerIndex,
        layerIndex
      )
      newNode.reset()
      newNode.layerIndex = layerIndex
      this.cacheNode(data._node.uid, newNode)
      this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
      // 主题或主题配置改变了、节点层级改变了，需要重新渲染节点文本等情况需要重新计算节点大小和布局
      if (
        this.checkIsNeedResizeSources() ||
        isLayerTypeChange ||
        newNode.getData('resetRichText')
      ) {
        newNode.getSize()
        newNode.needLayout = true
      }
    } else if (
      (this.lru.has(uid) || this.renderer.lastNodeCache[uid]) &&
      !this.renderer.reRender
    ) {
      // 节点数据上没有节点实例
      // 但是通过uid在节点缓存池中找到了缓存的节点
      // 或者在上一次渲染缓存对象中找到了节点
      // 也可以直接复用
      newNode = this.lru.get(uid) || this.renderer.lastNodeCache[uid]
      // 保存该节点上一次的数据
      const lastData = JSON.stringify(newNode.getData())
      const isLayerTypeChange = this.checkIsLayerTypeChange(
        newNode.layerIndex,
        layerIndex
      )
      newNode.reset()
      newNode.nodeData = newNode.handleData(data || {})
      newNode.layerIndex = layerIndex
      this.cacheNode(uid, newNode)
      this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
      data._node = newNode
      // 主题或主题配置改变了需要重新计算节点大小和布局
      const isResizeSource = this.checkIsNeedResizeSources()
      // 主题或主题配置改变了、节点层级改变了，需要重新渲染节点文本，节点数据改变了等情况需要重新计算节点大小和布局
      const isNodeDataChange = lastData !== JSON.stringify(data.data)
      if (
        isResizeSource ||
        isNodeDataChange ||
        isLayerTypeChange ||
        newNode.getData('resetRichText')
      ) {
        newNode.getSize()
        newNode.needLayout = true
      }
    } else {
      // 创建新节点
      const newUid = uid || createUid()
      newNode = new Node({
        data,
        uid: newUid,
        renderer: this.renderer,
        mindMap: this.mindMap,
        draw: this.draw,
        layerIndex
      })
      // uid保存到数据上，为了节点复用
      data.data.uid = newUid
      this.cacheNode(newUid, newNode)
      // 数据关联实际节点
      data._node = newNode
      if (data.data.isActive) {
        this.renderer.addNodeToActiveList(newNode)
      }
    }
    // 如果当前节点在激活节点列表里，那么添加上激活的状态
    if (this.mindMap.renderer.findActiveNodeIndex(newNode) !== -1) {
      newNode.setData({
        isActive: true
      })
    }
    // 根节点
    if (isRoot) {
      newNode.isRoot = true
      this.root = newNode
    } else {
      // 互相收集
      newNode.parent = parent._node
      parent._node.addChildren(newNode)
    }
    return newNode
  }

  // 格式化节点位置
  formatPosition(value, size, nodeSize) {
    if (typeof value === 'number') {
      return value
    } else if (initRootNodePositionMap[value] !== undefined) {
      return size * initRootNodePositionMap[value]
    } else if (/^\d\d*%$/.test(value)) {
      return (Number.parseFloat(value) / 100) * size
    } else {
      return (size - nodeSize) / 2
    }
  }

  // 规范initRootNodePosition配置
  formatInitRootNodePosition(pos) {
    const { CENTER } = CONSTANTS.INIT_ROOT_NODE_POSITION
    if (!pos || !Array.isArray(pos) || pos.length < 2) {
      pos = [CENTER, CENTER]
    }
    return pos
  }

  //  定位节点到画布中间
  setNodeCenter(node, position) {
    let { initRootNodePosition } = this.mindMap.opt
    initRootNodePosition = this.formatInitRootNodePosition(
      position || initRootNodePosition
    )
    node.left = this.formatPosition(
      initRootNodePosition[0],
      this.mindMap.width,
      node.width
    )
    node.top = this.formatPosition(
      initRootNodePosition[1],
      this.mindMap.height,
      node.height
    )
  }

  // 当initRootNodePosition配置不为默认的['center','center']时，计算当前配置和默认配置情况下，根节点位置的差值
  getRootCenterOffset(width, height) {
    // 因为根节点的大小不会影响这个差值，所以计算一次就足够了
    if (this.rootNodeCenterOffset) return this.rootNodeCenterOffset
    let { initRootNodePosition } = this.mindMap.opt
    const { CENTER } = CONSTANTS.INIT_ROOT_NODE_POSITION
    initRootNodePosition = this.formatInitRootNodePosition(initRootNodePosition)
    if (
      initRootNodePosition[0] === CENTER &&
      initRootNodePosition[1] === CENTER
    ) {
      // 如果initRootNodePosition是默认的，那么不需要计算
      this.rootNodeCenterOffset = {
        x: 0,
        y: 0
      }
    } else {
      // 否则需要计算当前配置和默认配置的差值
      const tmpNode = {
        width: width,
        height: height
      }
      const tmpNode2 = {
        width: width,
        height: height
      }
      this.setNodeCenter(tmpNode, [CENTER, CENTER])
      this.setNodeCenter(tmpNode2)
      this.rootNodeCenterOffset = {
        x: tmpNode2.left - tmpNode.left,
        y: tmpNode2.top - tmpNode.top
      }
    }
    return this.rootNodeCenterOffset
  }

  //  更新子节点属性
  updateChildren(children, prop, offset) {
    children.forEach(item => {
      item[prop] += offset
      if (item.children && item.children.length && !item.hasCustomPosition()) {
        // 适配自定义位置
        this.updateChildren(item.children, prop, offset)
      }
    })
  }

  //  更新子节点多个属性
  updateChildrenPro(children, props) {
    children.forEach(item => {
      Object.keys(props).forEach(prop => {
        item[prop] += props[prop]
      })
      if (item.children && item.children.length && !item.hasCustomPosition()) {
        // 适配自定义位置
        this.updateChildrenPro(item.children, props)
      }
    })
  }

  //  递归计算节点的宽度
  getNodeAreaWidth(node, withGeneralization = false) {
    let widthArr = []
    let totalGeneralizationNodeWidth = 0
    let loop = (node, width) => {
      if (withGeneralization && node.checkHasGeneralization()) {
        totalGeneralizationNodeWidth += node._generalizationNodeWidth
      }
      if (node.children.length) {
        width += node.width / 2
        node.children.forEach(item => {
          loop(item, width)
        })
      } else {
        width += node.width
        widthArr.push(width)
      }
    }
    loop(node, 0)
    return Math.max(...widthArr) + totalGeneralizationNodeWidth
  }

  //  二次贝塞尔曲线
  quadraticCurvePath(x1, y1, x2, y2) {
    let cx = x1 + (x2 - x1) * 0.2
    let cy = y1 + (y2 - y1) * 0.8
    return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
  }

  //  三次贝塞尔曲线
  cubicBezierPath(x1, y1, x2, y2) {
    let cx1 = x1 + (x2 - x1) / 2
    let cy1 = y1
    let cx2 = cx1
    let cy2 = y2
    return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
  }

  // 根据a,b两个点的位置，计算去除圆角大小后的新的b点
  computeNewPoint(a, b, radius = 0) {
    // x坐标相同
    if (a[0] === b[0]) {
      // b在a下方
      if (b[1] > a[1]) {
        return [b[0], b[1] - radius]
      } else {
        // b在a上方
        return [b[0], b[1] + radius]
      }
    } else if (a[1] === b[1]) {
      // y坐标相同
      // b在a右边
      if (b[0] > a[0]) {
        return [b[0] - radius, b[1]]
      } else {
        return [b[0] + radius, b[1]]
      }
    }
  }

  // 创建一段折线路径
  // 最后一个拐角支持圆角
  createFoldLine(list) {
    const { lineRadius } = this.mindMap.themeConfig
    const len = list.length
    let path = ''
    let radiusPath = ''
    if (len >= 3 && lineRadius > 0) {
      const start = list[len - 3]
      const center = list[len - 2]
      const end = list[len - 1]
      // 如果三点在一条直线，那么不用处理
      const isOneLine =
        (start[0] === center[0] && center[0] === end[0]) ||
        (start[1] === center[1] && center[1] === end[1])
      if (!isOneLine) {
        const cStart = this.computeNewPoint(start, center, lineRadius)
        const cEnd = this.computeNewPoint(end, center, lineRadius)
        radiusPath = `Q ${center[0]},${center[1]} ${cEnd[0]},${cEnd[1]}`
        list.splice(len - 2, 1, cStart, radiusPath)
      }
    }
    list.forEach((item, index) => {
      if (typeof item === 'string') {
        path += item
      } else {
        const [x, y] = item
        if (index === 0) {
          path += `M ${x},${y}`
        } else {
          path += `L ${x},${y}`
        }
      }
    })
    return path
  }

  //   获取节点的marginX
  getMarginX(layerIndex) {
    const { themeConfig, opt } = this.mindMap
    const { second, node } = themeConfig
    const hoverRectPadding = opt.hoverRectPadding * 2
    return layerIndex === 1
      ? second.marginX + hoverRectPadding
      : node.marginX + hoverRectPadding
  }

  //  获取节点的marginY
  getMarginY(layerIndex) {
    const { themeConfig, opt } = this.mindMap
    const { second, node } = themeConfig
    const hoverRectPadding = opt.hoverRectPadding * 2
    return layerIndex === 1
      ? second.marginY + hoverRectPadding
      : node.marginY + hoverRectPadding
  }

  //  获取节点包括概要在内的宽度
  getNodeWidthWithGeneralization(node) {
    return Math.max(
      node.width,
      node.checkHasGeneralization() ? node._generalizationNodeWidth : 0
    )
  }

  //  获取节点包括概要在内的高度
  getNodeHeightWithGeneralization(node) {
    return Math.max(
      node.height,
      node.checkHasGeneralization() ? node._generalizationNodeHeight : 0
    )
  }

  //  获取节点的边界值
  /**
   * dir：生长方向，h（水平）、v（垂直）
   * isLeft：是否向左生长
   */
  getNodeBoundaries(node, dir) {
    let { generalizationLineMargin, generalizationNodeMargin } =
      this.mindMap.themeConfig
    let walk = root => {
      let _left = Infinity
      let _right = -Infinity
      let _top = Infinity
      let _bottom = -Infinity
      if (root.children && root.children.length > 0) {
        root.children.forEach(child => {
          let { left, right, top, bottom } = walk(child)
          // 概要内容的宽度
          let generalizationWidth =
            child.checkHasGeneralization() && child.getData('expand')
              ? child._generalizationNodeWidth + generalizationNodeMargin
              : 0
          // 概要内容的高度
          let generalizationHeight =
            child.checkHasGeneralization() && child.getData('expand')
              ? child._generalizationNodeHeight + generalizationNodeMargin
              : 0
          if (left - (dir === 'h' ? generalizationWidth : 0) < _left) {
            _left = left - (dir === 'h' ? generalizationWidth : 0)
          }
          if (right + (dir === 'h' ? generalizationWidth : 0) > _right) {
            _right = right + (dir === 'h' ? generalizationWidth : 0)
          }
          if (top < _top) {
            _top = top
          }
          if (bottom + (dir === 'v' ? generalizationHeight : 0) > _bottom) {
            _bottom = bottom + (dir === 'v' ? generalizationHeight : 0)
          }
        })
      }
      let cur = {
        left: root.left,
        right: root.left + root.width,
        top: root.top,
        bottom: root.top + root.height
      }
      return {
        left: cur.left < _left ? cur.left : _left,
        right: cur.right > _right ? cur.right : _right,
        top: cur.top < _top ? cur.top : _top,
        bottom: cur.bottom > _bottom ? cur.bottom : _bottom
      }
    }
    let { left, right, top, bottom } = walk(node)
    return {
      left,
      right,
      top,
      bottom,
      generalizationLineMargin,
      generalizationNodeMargin
    }
  }

  // 获取指定索引区间的子节点的边界范围
  getChildrenBoundaries(node, dir, startIndex = 0, endIndex) {
    let { generalizationLineMargin, generalizationNodeMargin } =
      this.mindMap.themeConfig
    const children = node.children.slice(startIndex, endIndex + 1)
    let left = Infinity
    let right = -Infinity
    let top = Infinity
    let bottom = -Infinity
    children.forEach(item => {
      const cur = this.getNodeBoundaries(item, dir)
      left = cur.left < left ? cur.left : left
      right = cur.right > right ? cur.right : right
      top = cur.top < top ? cur.top : top
      bottom = cur.bottom > bottom ? cur.bottom : bottom
    })
    return {
      left,
      right,
      top,
      bottom,
      generalizationLineMargin,
      generalizationNodeMargin
    }
  }

  // 获取节点概要的渲染边界
  getNodeGeneralizationRenderBoundaries(item, dir) {
    let res = null
    // 区间
    if (item.range) {
      res = this.getChildrenBoundaries(
        item.node,
        dir,
        item.range[0],
        item.range[1]
      )
    } else {
      // 整体概要
      res = this.getNodeBoundaries(item.node, dir)
    }
    return res
  }

  // 获取节点实际存在几个子节点
  getNodeActChildrenLength(node) {
    return node.nodeData.children && node.nodeData.children.length
  }

  // 设置连线样式
  setLineStyle(style, line, path, childNode) {
    line.plot(this.transformPath(path))
    style && style(line, childNode, true)
  }

  // 转换路径，可以转换成特殊风格的线条样式
  transformPath(path) {
    const { customTransformNodeLinePath } = this.mindMap.opt
    if (customTransformNodeLinePath) {
      return customTransformNodeLinePath(path)
    } else {
      return path
    }
  }
}

export default Base
