export default class TreeNode {
  constructor (value, canHaveChildren) {
    this.canHaveChildren = canHaveChildren != null ? canHaveChildren : true
    this.value = value
    this.parent = null
    this.children = []
  }

  isAncestor (node) {
    if (node == null) {
      return false
    } else {
      this.checkNodeType(node)
    }

    let ancestor = this

    do {
      if (ancestor == node) {
        return true
      }
    } while((ancestor = ancestor.getParent()) != null)

    return false
  }
  
  add (node) {
    return this.insert(node, this.children.length)
  }

  insert (node, index) {
    if (!this.canHaveChildren) {
      throw new Error('TreeNode Node does not allow children')
    }

    if (node == null) {
      throw new Error('Argument [node] cannot be undefined or null')
    } else {
      this.checkNodeType(node)
    }

    if (this.isAncestor(node)) {
      throw new Error('Argument [node] is an ancestor')
    }
    
    if (node.parent) {
      node.parent.remove(node)
    }

    node.setParent(this)
    this.children[index] = node

    return this
  }
  
  remove (value) {
    let index = value
    
    if (value == null) {
      throw new Error('Argument value cannot be undefined or null')
    }

    if (index instanceof TreeNode) {
      index = this.getIndex(value)
    }
    
    if (index < 0) {
      throw new Error('Argument value is not a valid child node')
    }
    
    let child = this.children[index]

    this.children.splice(index, 1)
    child.setParent(null)

    return this
  }

  removeAllChildren () {
    for (var ii = 0, len = this.children.length; ii >= 0; ii--) {
      this.remove(ii)
    }

    return this
  }

  getIndex (node) {
    this.checkNodeType(node)
    return this.children.indexOf(node)
  }

  getParent () {
    return this.parent
  }

  setParent (node) {
    if (!node) {
      this.parent = null
      return
    }

    this.checkNodeType(node)
    this.parent = node
    return this
  }
  
  getDepth () {
    if (!this.hasChildren()) {
      return 0
    }

    let depth = 0
    let parents = []
    let queue = this.children
    let cursor = queue.shift()

    while (cursor) {
      if (cursor.children) {
        queue = queue.concat(cursor.children)

        if (parents.indexOf(cursor.parent) < 0) {
          parents.push(cursor.parent)
          depth++
        }
      }

      cursor = queue.shift()
    }

    parents = null

    return depth
  }
  
  getLevel () {
    let ancestor = this
    let level = 0
    
    while((ancestor = ancestor.getParent()) != null) {
      level++
    }
    
    return level
  }
  
  isLeaf () {
    return this.children.length == 0
  }
  
  isRoot () {
    return !this.parent
  }
  
  hasChildren () {
    return !!this.children.length
  }
  
  checkNodeType (node) {
    if (!node instanceof TreeNode) {
      throw new Error('Argument node is not an instance of TreeNode')
    }
  }
}