const {Point} = require('text-buffer')

module.exports = class TokenizedBufferIterator {
  constructor (tokenizedBuffer) {
    this.tokenizedBuffer = tokenizedBuffer
    this.openTags = null
    this.closeTags = null
    this.containingTags = null
  }

  seek (position) {
    this.openTags = []
    this.closeTags = []
    this.tagIndex = null

    const currentLine = this.tokenizedBuffer.tokenizedLineForRow(position.row)
    this.currentTags = currentLine.tags
    this.currentLineOpenTags = currentLine.openScopes
    this.currentLineLength = currentLine.text.length
    this.containingTags = this.currentLineOpenTags.map((id) => this.scopeForId(id))

    let currentColumn = 0
    for (let [index, tag] of this.currentTags.entries()) {
      if (tag >= 0) {
        if (currentColumn >= position.column) {
          this.tagIndex = index
          break
        } else {
          currentColumn += tag
          while (this.closeTags.length > 0) {
            this.closeTags.shift()
            this.containingTags.pop()
          }
          while (this.openTags.length > 0) {
            const openTag = this.openTags.shift()
            this.containingTags.push(openTag)
          }
        }
      } else {
        const scopeName = this.scopeForId(tag)
        if (tag % 2 === 0) {
          if (this.openTags.length > 0) {
            if (currentColumn >= position.column) {
              this.tagIndex = index
              break
            } else {
              while (this.closeTags.length > 0) {
                this.closeTags.shift()
                this.containingTags.pop()
              }
              while (this.openTags.length > 0) {
                const openTag = this.openTags.shift()
                this.containingTags.push(openTag)
              }
            }
          }
          this.closeTags.push(scopeName)
        } else {
          this.openTags.push(scopeName)
        }
      }
    }

    if (this.tagIndex == null) {
      this.tagIndex = this.currentTags.length
    }
    this.position = Point(position.row, Math.min(this.currentLineLength, currentColumn))
    return this.containingTags.slice()
  }

  moveToSuccessor () {
    for (let tag of this.closeTags) { // eslint-disable-line no-unused-vars
      this.containingTags.pop()
    }
    for (let tag of this.openTags) {
      this.containingTags.push(tag)
    }
    this.openTags = []
    this.closeTags = []
    while (true) {
      if (this.tagIndex === this.currentTags.length) {
        if (this.isAtTagBoundary()) {
          break
        } else if (this.shouldMoveToNextLine) {
          this.moveToNextLine()
          this.openTags = this.currentLineOpenTags.map((id) => this.scopeForId(id))
          this.shouldMoveToNextLine = false
        } else if (this.nextLineHasMismatchedContainingTags()) {
          this.closeTags = this.containingTags.slice().reverse()
          this.containingTags = []
          this.shouldMoveToNextLine = true
        } else if (!this.moveToNextLine()) {
          return false
        }
      } else {
        const tag = this.currentTags[this.tagIndex]
        if (tag >= 0) {
          if (this.isAtTagBoundary()) {
            break
          } else {
            this.position = Point(this.position.row, Math.min(
              this.currentLineLength,
              this.position.column + this.currentTags[this.tagIndex]
            ))
          }
        } else {
          const scopeName = this.scopeForId(tag)
          if (tag % 2 === 0) {
            if (this.openTags.length > 0) {
              break
            } else {
              this.closeTags.push(scopeName)
            }
          } else {
            this.openTags.push(scopeName)
          }
        }
        this.tagIndex++
      }
    }
    return true
  }

  getPosition () {
    return this.position
  }

  getCloseTags () {
    return this.closeTags.slice()
  }

  getOpenTags () {
    return this.openTags.slice()
  }

  nextLineHasMismatchedContainingTags () {
    const line = this.tokenizedBuffer.tokenizedLineForRow(this.position.row + 1)
    if (line == null) {
      return false
    } else {
      return (
        this.containingTags.length !== line.openScopes.length ||
        this.containingTags.some((tag, i) => tag !== this.scopeForId(line.openScopes[i]))
      )
    }
  }

  moveToNextLine () {
    this.position = Point(this.position.row + 1, 0)
    const tokenizedLine = this.tokenizedBuffer.tokenizedLineForRow(this.position.row)
    if (tokenizedLine == null) {
      return false
    } else {
      this.currentTags = tokenizedLine.tags
      this.currentLineLength = tokenizedLine.text.length
      this.currentLineOpenTags = tokenizedLine.openScopes
      this.tagIndex = 0
      return true
    }
  }

  isAtTagBoundary () {
    return this.closeTags.length > 0 || this.openTags.length > 0
  }

  scopeForId (id) {
    const scope = this.tokenizedBuffer.grammar.scopeForId(id)
    if (scope) {
      return `syntax--${scope.replace(/\./g, '.syntax--')}`
    } else {
      return null
    }
  }
}
