<template>
  <div class="demo-split">
    <Split v-model="split3" min="600" max="200">
      <div slot="left" class="demo-split-pane no-padding">
        <Split v-model="split4" mode="vertical" min="150px" max="150px">
          <div slot="top" class="demo-split-pane">
            <Row>
              <i-col span="24">
                <FormulaEdit
                  ref="formulaEdit"
                  :height="split4"
                  @TokenChange="OnTokenChange"
                  :functions="funcData"
                  :constants="constData"
                  :value="value"
                  :functionList="funcData"
                  @error='OnFormulaError'
                />
              </i-col>
            </Row>
            <Row>
              <i-col span="12">
                <ButtonGroup size="small">
                  <Button @click="clear">清空</Button>
                  <Button @click="checkFormula">检查</Button>
                </ButtonGroup>
              </i-col>
              <i-col id="error-message" span="12" style="text-align: right;">
                <Poptip v-show="formulaError" :content="String(formulaError)" word-wrap placement="bottom">
                  <Alert type="error" show-icon>'检查语法有误'</Alert>
                </Poptip>
              </i-col>
            </Row>
          </div>
          <div slot="bottom" class="demo-split-pane no-padding">
              <Split v-model="split5">
                <div slot="left" class="demo-split-pane">
                  <div id="function-list" :style="{'--bottom-height': bottomHeight}">
                    <tse-tree ref="functionTree" :data="currentFuncData" :editable="editable"
                              @on-select-change="OnFunctionChange" @on-function-edit="OnFunctionEdit">
                    </tse-tree>
                  </div>
                </div>
                <div slot="right" class="demo-split-pane">
                  <textarea id="description" :style="{'--bottom-height': bottomHeight}" aria-multiline="true" readonly v-model="desc"></textarea>
                </div>
              </Split>
          </div>
        </Split>
      </div>
      <div slot="right" class="demo-split-pane">
        <ParamForm ref="paramForm" id="formula_params" :fData='selectedFunction.params' v-show="paramFormShow"
                      :editor="editor"  @ParamFormulaClick="OnParamFormulaClick"
                      :start="start" :length="length" @paramChange="OnParamChange"></ParamForm>
      </div>
    </Split>
  </div>
</template>
<script>
import create from '../utils/create'
import FormulaEdit from './formula/FormulaEdit.vue'
import { util } from './formula/formulaUtil'
import {
  constantjs,
  getParamFromStr,
  resolution,
  isSplitejs,
  string
} from './formula/formulaAnalysis'
import ParamForm from './formula/paramForm'
import { Tree } from 'view-design'
import _ from 'lodash'

const _arrayJoinToStr = (array) => {
  let list = []
  for (let i = 0; i < array.length; i++) {
    if (array[i] instanceof Array) {
      list.push(_arrayJoinToStr(array[i]))
    } else if (typeof array[i] === 'string' && !isSplitejs(array[i])) {
      list.push('"' + array[i] + '"')
    } else {
      list.push(array[i])
    }
  }
  return list
}

const iteratorTree = (tree, opt = o => o, parent) => {
  if (Array.isArray(tree)) {
    return tree.map(item => iteratorTree(item, opt, parent))
  } else if (tree.children) {
    tree.children = tree.children.map(item => iteratorTree(item, opt, tree))
  }
  return opt(tree, parent)
}

export default create({
  name: 'formula',
  components: {
    FormulaEdit,
    Tree,
    ParamForm
  },
  props: {
    value: {
      type: String,
      default: ''
    },
    functionList: {
      type: Array,
      default: []
    },
    constant: {
      type: Object,
      default: {}
    },
    editable: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      split3: '520px',
      split4: '150px',
      split5: '240px',
      desc: '',
      modal6: false,
      loading: true,
      funcData: this.functionList,
      constData: this.constant.macro,
      ResultText: '',
      selectedFunction: { params: [] },
      editor: undefined,
      start: 0,
      length: 0,
      formulaName: '',
      formulaError: undefined,
      paramFormShow: true,
      contextData: {},
      functionEdit: false,
      tokenChanging: false,
      loaded: false,
      buttonProps: {
        type: 'default',
        size: 'mini'
      },
      functionEditing: false
    }
  },
  computed: {
    bottomHeight () {
      return (465 - parseInt(this.split4)) + 'px'
    },
    funcEditWidth () {
      return parseInt(this.split3)
    },
    currentFuncData: {
      get () {
        if (this.contentMenu && !this.loaded) {
          this.loaded = true
          let ret = iteratorTree(this.funcData, (item, parent) => {
            if (item.calc) {
              item.calc = String(item.calc).replace(/(?<=function\s*)(calc)(?=\s*\()/i, '')
            }
            return item
          })
          return ret
        } else {
          return this.funcData
        }
      },
      set (val) {
        this.funcData = val
      }
    },
    funcNameList () {
      let retFuncList = []
      iteratorTree(this.currentFuncData, (item) => {
        if (item.calc) retFuncList.push(item.name)
        return item
      })
      return retFuncList
    }
  },
  methods: {
    // 切换编辑器中函数高亮显示
    OnTokenChange (event) {
      this.tokenChanging = true
      // 切换函数树中选中节点
      let tokenString = event.selectedText
      if (tokenString) {
        let _tree = util.TreeFilter(this.currentFuncData, [tokenString])
        this.funcData = _tree.nodes
        if (_tree.selectedNodes) {
          this.OnFunctionChange(this.currentFuncData, _tree.selectedNodes[0])
        }
      } else {
        this.currentFuncData = iteratorTree(this.currentFuncData, (item) => {
          item.selected = false
          return item
        })
        this.selectedFunction.params = []
        return
      }

      // 更新参数控件
      if (event.params && event.params.groups) {
        this.paramFormShow = true
        let tokenParams = event.params.groups[0]
        this.formulaName = tokenString
        this.start = event.params.start
        this.length = event.params.length
        if (this.selectedFunction && tokenParams[tokenString]) {
          this.selectedFunction.params = this.selectedFunction.params.map(
            (data, index) => {
              let value = tokenParams[tokenString][index]
              if (value instanceof Array) {
                if (String(value).indexOf('[object Object]') < 0) {
                  if (constantjs(JSON.stringify(value))) {
                    value = _arrayJoinToStr(value)
                    if (typeof value === 'object') {
                      value = value.join('')
                      if (/Array(<?>)?/.test(data.dataType)) value = '[' + value + ']'
                    }
                  } else if (typeof value[0] === 'string') {
                    value[0] = '"' + value[0] + '"'
                  }
                }
              }
              data.value = value
              return data
            }
          )
        }
      } else {
        this.paramFormShow = false
      }
      this.tokenChanging = false
    },
    // 增加表达式
    newFormula () {
      this.selectedFunction.params.forEach((param, index) => {
        let obj = _.cloneDeep(param)
        obj.value = ''
        this.$set(this.selectedFunction.params, index, obj)
      })

      this.$refs.formulaEdit.clearMarks()
      if (this.selectedFunction.name) {
        this.formulaName = this.selectedFunction.name
      }
    },
    clear () {
      this.$refs.formulaEdit.getEditor().setValue('')
      this.$refs.formulaEdit.getEditor().refresh()
    },
    // 点击函数树节点的事件
    OnFunctionChange (nodes, data) {
      if (!data) return
      if (data.children) {
        if (!data.editing) data.expand = !data.expand
      } else {
        this.desc = data.desription
        data.params = data.params.map((currentValue, index, arr) => {
          currentValue = Object.assign({}, currentValue, { value: '', status: true })
          if (currentValue.control && currentValue.control.options) {
            currentValue.control.options = currentValue.control.options.map((item, index, arr) => {
              if (item instanceof Object) {
                return item
              } else {
                return { value: item, label: item }
              }
            })
          }
          return currentValue
        })
        if (this.tokenChanging) this.selectedFunction = _.cloneDeep(data)
      }
    },
    // 在参数界面中点击参数中的函数链接以便快速切换编辑器中的函数
    OnParamFormulaClick (event) {
      let pos = 0
      let line = 0
      let cm = this.editor
      this.formulaName = event.formulaName
      this.start = event.start
      for (let i = 0; i < cm.lineCount(); i++) {
        let lineStr = cm.getLine(i)
        if (pos + lineStr.length + 1 <= this.start) {
          line++
          pos += lineStr.length + 1
        } else {
          break
        }
      }
      let postion = {
        line: line,
        ch: this.start - pos + this.formulaName.length
      }
      cm.setCursor(postion)
      cm.focus()
    },
    // 检查公式
    checkFormula () {
      let checkStr = 'check(' + this.editor.getValue() + ')'
      let result = resolution(checkStr, 0)
      if (!result.error) {
        this.$Message.success('检查语法成功')
      } else {
        this.$Message.error('检查语法有误')
      }
    },
    // 参数值改变
    OnParamChange ({ paramIndex, value, params, funcName }) {
      let cm = this.editor
      let c = cm.getCursor()
      let funcStr = cm.getValue().substr(this.start, this.length)
      let currentParams = getParamFromStr(funcStr)
      let docStr = cm.getValue()
      let curStart = 0
      let curEnd = 0
      let startPos = 0
      let endPos = 0
      let val = value ? String(value) : ''
      if (params[paramIndex].dataType === 'string' && !string('' + val, 0)) {
        val = '"' + val + '"'
      }
      if (currentParams[paramIndex]) {
        let str = currentParams[paramIndex].str
        curStart = currentParams[paramIndex].start
        curEnd = currentParams[paramIndex].end
        let pos = 0
        while (pos < str.length) {
          if (/\s/.test(str.substr(pos, 1))) {
            curStart++; pos++
          } else {
            break
          }
        }
        startPos = this.start + curStart
        endPos = this.start + curEnd
        docStr = docStr.substring(0, startPos) + val + docStr.substr(endPos)
      } else if (currentParams[paramIndex - 1]) {
        curStart = currentParams[paramIndex - 1].end
        startPos = this.start + curStart
        endPos = this.start + curStart
        docStr = docStr.substring(0, startPos) + ',' + val + docStr.substr(endPos)
      } else {
        startPos = this.start + curStart
        endPos = this.start + curEnd
        docStr = docStr.substring(0, startPos) + ',' + val + docStr.substr(endPos)
      }
      cm.setValue(docStr)
      cm.setCursor(c)
      if (this.formulaError) cm.focus()
    },
    OnFormulaError (error) {
      // if (error) console.log(error)
      this.formulaError = error
      this.$emit('on-error', error)
    },
    getValue () {
      return this.$refs.formulaEdit.getValue()
    },
    setValue (val) {
      this.$refs.formulaEdit.setValue(val)
    },
    OnFunctionEdit (node, data) {
      this.functionEditing = true
      this.contextData = data
    }
  },
  mounted () {
    this.$nextTick(() => {
      this.editor = this.$refs.formulaEdit.getEditor()
      this.split5 = '324px'
    })
  }
})
</script>
<style scoped lang="scss">
@import './formula/index.scss';
</style>
