<template>
  <codemirror :style="{ '--height': currentHeight}" ref="myCm" v-model="code" :options="cmOptions"
      @changes="OnChange"
      @cursorActivity="OnSelectionChange"
      />
</template>

<script>
import CodeMirror from 'codemirror'
import { codemirror } from 'vue-codemirror'
import { builder } from './webFormula'
import 'codemirror/theme/ambiance.css'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/hint/show-hint'
import 'codemirror/addon/edit/matchbrackets.js'
import { resolution } from './formulaAnalysis'

// const init_code = 'IF(2>1, QT1(), QT2())'
// const initCode = 'IF(2>1,1+SUM([1,ABS(111),5]),SUM([1,2,"3"])) + LJFSE("1001","2201",2013,12) + SYB("0001")'
// const init_code = 'IF(SUM([1,3,4]) <= SUM([2]), SUM([SYB("0001", 2012, 12), LJFSE("1001","2201",2013,12)]), ABS(1-SUM(-100,2,3,40)))'

const $ = (arg) => {
  let node = document.getElementById('tmpNewDiv')
  if (node) {
    document.removeChild(node)
  }
  var objE = document.createElement('div')
  objE.setAttribute('id', 'tmpNewDiv')
  objE.innerHTML = arg
  return objE.childNodes
}

export default {
  components: {
    codemirror
  },
  props: {
    functions: Array,
    constants: Array,
    height: String,
    value: String
  },
  data () {
    return {
      code: this.value,
      cmOptions: {
        mode: 'webFormula',
        theme: 'ambiance',
        lineNumbers: true,
        lineWrapping: true,
        extraKeys: { 'Alt-Enter': 'autocomplete' },
        matchBrackets: true,
        autofocus: true,
        functionsRegex: /[\s\S]*/,
        memberRegex: /[\s\S]*/,
        functionList: [],
        memberList: []
      }
    }
  },
  computed: {
    currentHeight () {
      return (parseInt(this.height) - 45) + 'px'
    }
  },
  methods: {
    insertBarcket (editor) {
      var c = editor.getCursor()
      editor.replaceSelection('(')
      var d = editor.getCursor()
      editor.markText(c, d, {
        handleMouseEvents: !0,
        atomic: !0,
        replaceWith: $('<span class="cm-bracket CodeMirror-matchingbracket">(</span>')[0]
      })
      var c1 = editor.getCursor()
      editor.replaceSelection(')')
      var d1 = editor.getCursor()
      editor.markText(c1, d1, {
        handleMouseEvents: !0,
        atomic: !0,
        replaceWith: $('<span class="cm-bracket CodeMirror-matchingbracket">)</span>')[0]
      })
      editor.setCursor(d)
      editor.focus()
    },
    OnChange (cm, event) {
      this.checkFormula(cm)
      if (event[0].origin !== 'complete') {
        cm.showHint({
          hint: CodeMirror.hint.webFormula,
          completeSingle: false
        })
      } else if (this.functionList.indexOf(event[0].text[0]) > -1) {
        this.insertBarcket(cm)
      }
    },
    setToken (cm, cur, token) {
      this.token = token
      let from = CodeMirror.Pos(cur.line, token.start); let to = CodeMirror.Pos(cur.line, token.end)

      let fnMark = $('<span class="cm-' + token.type + ' CodeMirror-selected">' + token.string + '</span>')
      token.line = cur.line

      this.markText = cm.markText(from, to, {
        handleMouseEvents: !0,
        // selectLeft: !0,
        // selectRight: !0,
        atomic: !0,
        // autofocus: !0,
        replacedWith: fnMark[0]
      })
    },
    OnSelectionChange (cm) {
      if (this.markText) this.markText.clear()
      let cur = cm.getCursor()
      let token = cm.getTokenAt(cm.getCursor())
      let params
      let tokenString
      if (token.type === 'function' || token.type === 'member') {
        this.setToken(cm, cur, token)
        tokenString = token.string
        params = this.getParams(cm, token)
      } else {
        let pos = this.getAllPostion(cm)
        let { cur, token } = this.getFunctionToken(cm, pos)
        if (token && (token.type === 'function' || token.type === 'member')) {
          this.setToken(cm, cur, token)
          tokenString = token.string
          params = this.getParams(cm, token)
        }
      }
      if (params && tokenString) {
        this.$emit('TokenChange', { 'selectedText': tokenString, 'params': params })
      } else {
        this.$emit('TokenChange', {})
      }
    },
    getParams (cm, token) {
      this.checkFormula(cm)
      let docStr = cm.getValue()
      let start = token.start
      for (let i = 0; i < token.line; i++) {
        start += cm.getLine(i).length + 1
      }
      let result = resolution(docStr.replace(this.memberRegex, '0'), start)
      if (!result.error) {
        return result
      }
    },
    checkFormula (cm) {
      let checkStr = 'check(' + cm.getValue().replace(this.memberRegex, '0') + ')'
      let result = resolution(checkStr, 0)
      if (!result.error) {
        this.$emit('error', undefined)
        return true
      } else {
        this.$emit('error', result.error)
      }
    },
    getEditor () {
      return this.$refs.myCm.codemirror
    },
    clearMarks () {
      if (this.markText) this.markText.clear()
    },
    getCursor () {
      return this.$refs.myCm.codemirror.getCursor()
    },
    getAllPostion (cm) {
      let cur = cm.getCursor()
      let pos = 0
      for (let i = 0; i < cur.line; i++) {
        let lineStr = cm.getLine(i)
        pos += lineStr.length
      }
      pos += cur.ch
      return pos
    },
    getFunctionToken (cm, start) {
      let docStr = cm.getDoc().getValue()
      let stack = 0
      let i
      for (i = start - 1; i > 0; i--) {
        let char = docStr.substr(i, 1)
        if (char === ')' && docStr.substr(i - 1, 1) !== '\\') {
          stack--
        } else if (char === '(' && docStr.substr(i - 1, 1) !== '\\') {
          stack++
        }
        if (stack > 0) break
      }
      let pos = 0; let line = 0
      start = i - 1
      for (let i = 0; i < cm.lineCount(); i++) {
        let lineStr = cm.getLine(i)
        if (pos + lineStr.length + 1 <= start) {
          line++
          pos += lineStr.length + 1
        } else {
          break
        }
      }
      let postion = { line: line, ch: start - pos }
      return { cur: postion, token: cm.getTokenAt(postion) }
    },
    getValue () {
      return this.$refs.myCm.codemirror.getValue()
    },
    setValue (val) {
      this.$refs.myCm.codemirror.setValue(val)
    }
  },
  created () {
    builder.init(this.functions, this.constants)
    builder.doRegister()
    this.functionsRegex = builder.functionsRegex
    this.memberRegex = builder.memberRegex
    this.functionList = builder.functionList
    this.memberList = builder.memberList
  },
  mounted () {
    setTimeout(() => {
      let cm = this.$refs.myCm
      cm.refresh()
      this.$nextTick(() => {
        this.OnSelectionChange(cm.codemirror)
      })
    }, 20)
  }
}
</script>
<style scoped lang="scss">

.vue-codemirror {
  ::v-deep .CodeMirror {
    height: var(--height);
    word-wrap: break-word;
  }
  ::v-deep .CodeMirror-hints {
    z-index: 99999;
  }
  ::v-deep .cm-function {
    color: #f90;
  }
  ::v-deep .cm-function.cm-selected {
    color:deeppink;
  }

  ::v-deep .cm-arithmetic {
    color:#2d8cf0
  }

  ::v-deep .CodeMirror-selected {
    background: #eaeaea;
    color:dimgrey
  }

  ::v-deep .cm-bracket.CodeMirror-matchingbracket {
    color:hotpink!important
  }

  ::v-deep .cm-quot {
    color:lawngreen
  }
}

</style>
