module.exports =
class DOMElementPool {
  constructor () {
    this.managedElements = new Set()
    this.freeElementsByTagName = new Map()
    this.freedElements = new Set()
  }

  clear () {
    this.managedElements.clear()
    this.freedElements.clear()
    this.freeElementsByTagName.clear()
  }

  buildElement (tagName, className) {
    const elements = this.freeElementsByTagName.get(tagName)
    let element = elements ? elements.pop() : null
    if (element) {
      for (let dataId in element.dataset) { delete element.dataset[dataId] }
      element.removeAttribute('style')
      if (className) {
        element.className = className
      } else {
        element.removeAttribute('class')
      }
      while (element.firstChild) {
        element.removeChild(element.firstChild)
      }
      this.freedElements.delete(element)
    } else {
      element = document.createElement(tagName)
      if (className) {
        element.className = className
      }
      this.managedElements.add(element)
    }
    return element
  }

  buildText (textContent) {
    const elements = this.freeElementsByTagName.get('#text')
    let element = elements ? elements.pop() : null
    if (element) {
      element.textContent = textContent
      this.freedElements.delete(element)
    } else {
      element = document.createTextNode(textContent)
      this.managedElements.add(element)
    }
    return element
  }

  freeElementAndDescendants (element) {
    this.free(element)
    element.remove()
  }

  freeDescendants (element) {
    while (element.firstChild) {
      this.free(element.firstChild)
      element.removeChild(element.firstChild)
    }
  }

  free (element) {
    if (element == null) { throw new Error('The element cannot be null or undefined.') }
    if (!this.managedElements.has(element)) return
    if (this.freedElements.has(element)) {
      atom.assert(false, 'The element has already been freed!', {
        content: element instanceof window.Text ? element.textContent : element.outerHTML
      })
      return
    }

    const tagName = element.nodeName.toLowerCase()
    let elements = this.freeElementsByTagName.get(tagName)
    if (!elements) {
      elements = []
      this.freeElementsByTagName.set(tagName, elements)
    }
    elements.push(element)
    this.freedElements.add(element)

    for (let i = element.childNodes.length - 1; i >= 0; i--) {
      const descendant = element.childNodes[i]
      this.free(descendant)
    }
  }
}
