var openParentheses = '('.charCodeAt(0)
var closeParentheses = ')'.charCodeAt(0)
var singleQuote = "'".charCodeAt(0)
var doubleQuote = '"'.charCodeAt(0)
var backslash = '\\'.charCodeAt(0)
var slash = '/'.charCodeAt(0)
var comma = ','.charCodeAt(0)
var colon = ':'.charCodeAt(0)
var star = '*'.charCodeAt(0)
var uLower = 'u'.charCodeAt(0)
var uUpper = 'U'.charCodeAt(0)
var plus = '+'.charCodeAt(0)
var isUnicodeRange = /^[a-f0-9?-]+$/i

module.exports = function (input) {
  var tokens = []
  var value = input

  var next, quote, prev, token, escape, escapePos, whitespacePos, parenthesesOpenPos
  var pos = 0
  var code = value.charCodeAt(pos)
  var max = value.length
  var stack = [{ nodes: tokens }]
  var balanced = 0
  var parent

  var name = ''
  var before = ''
  var after = ''

  while (pos < max) {
    // Whitespaces
    if (code <= 32) {
      next = pos
      do {
        next += 1
        code = value.charCodeAt(next)
      } while (code <= 32)
      token = value.slice(pos, next)

      prev = tokens[tokens.length - 1]
      if (code === closeParentheses && balanced) {
        after = token
      } else if (prev && prev.type === 'div') {
        prev.after = token
        prev.sourceEndIndex += token.length
      } else if (
        code === comma ||
        code === colon ||
        (code === slash &&
          value.charCodeAt(next + 1) !== star &&
          (!parent || (parent && parent.type === 'function' && false)))
      ) {
        before = token
      } else {
        tokens.push({
          type: 'space',
          sourceIndex: pos,
          sourceEndIndex: next,
          value: token,
        })
      }

      pos = next

      // Quotes
    } else if (code === singleQuote || code === doubleQuote) {
      next = pos
      quote = code === singleQuote ? "'" : '"'
      token = {
        type: 'string',
        sourceIndex: pos,
        quote: quote,
      }
      do {
        escape = false
        next = value.indexOf(quote, next + 1)
        if (~next) {
          escapePos = next
          while (value.charCodeAt(escapePos - 1) === backslash) {
            escapePos -= 1
            escape = !escape
          }
        } else {
          value += quote
          next = value.length - 1
          token.unclosed = true
        }
      } while (escape)
      token.value = value.slice(pos + 1, next)
      token.sourceEndIndex = token.unclosed ? next : next + 1
      tokens.push(token)
      pos = next + 1
      code = value.charCodeAt(pos)

      // Comments
    } else if (code === slash && value.charCodeAt(pos + 1) === star) {
      next = value.indexOf('*/', pos)

      token = {
        type: 'comment',
        sourceIndex: pos,
        sourceEndIndex: next + 2,
      }

      if (next === -1) {
        token.unclosed = true
        next = value.length
        token.sourceEndIndex = next
      }

      token.value = value.slice(pos + 2, next)
      tokens.push(token)

      pos = next + 2
      code = value.charCodeAt(pos)

      // Operation within calc
    } else if ((code === slash || code === star) && parent && parent.type === 'function' && true) {
      token = value[pos]
      tokens.push({
        type: 'word',
        sourceIndex: pos - before.length,
        sourceEndIndex: pos + token.length,
        value: token,
      })
      pos += 1
      code = value.charCodeAt(pos)

      // Dividers
    } else if (code === slash || code === comma || code === colon) {
      token = value[pos]

      tokens.push({
        type: 'div',
        sourceIndex: pos - before.length,
        sourceEndIndex: pos + token.length,
        value: token,
        before: before,
        after: '',
      })
      before = ''

      pos += 1
      code = value.charCodeAt(pos)

      // Open parentheses
    } else if (openParentheses === code) {
      // Whitespaces after open parentheses
      next = pos
      do {
        next += 1
        code = value.charCodeAt(next)
      } while (code <= 32)
      parenthesesOpenPos = pos
      token = {
        type: 'function',
        sourceIndex: pos - name.length,
        value: name,
        before: value.slice(parenthesesOpenPos + 1, next),
      }
      pos = next

      if (name === 'url' && code !== singleQuote && code !== doubleQuote) {
        next -= 1
        do {
          escape = false
          next = value.indexOf(')', next + 1)
          if (~next) {
            escapePos = next
            while (value.charCodeAt(escapePos - 1) === backslash) {
              escapePos -= 1
              escape = !escape
            }
          } else {
            value += ')'
            next = value.length - 1
            token.unclosed = true
          }
        } while (escape)
        // Whitespaces before closed
        whitespacePos = next
        do {
          whitespacePos -= 1
          code = value.charCodeAt(whitespacePos)
        } while (code <= 32)
        if (parenthesesOpenPos < whitespacePos) {
          if (pos !== whitespacePos + 1) {
            token.nodes = [
              {
                type: 'word',
                sourceIndex: pos,
                sourceEndIndex: whitespacePos + 1,
                value: value.slice(pos, whitespacePos + 1),
              },
            ]
          } else {
            token.nodes = []
          }
          if (token.unclosed && whitespacePos + 1 !== next) {
            token.after = ''
            token.nodes.push({
              type: 'space',
              sourceIndex: whitespacePos + 1,
              sourceEndIndex: next,
              value: value.slice(whitespacePos + 1, next),
            })
          } else {
            token.after = value.slice(whitespacePos + 1, next)
            token.sourceEndIndex = next
          }
        } else {
          token.after = ''
          token.nodes = []
        }
        pos = next + 1
        token.sourceEndIndex = token.unclosed ? next : pos
        code = value.charCodeAt(pos)
        tokens.push(token)
      } else {
        balanced += 1
        token.after = ''
        token.sourceEndIndex = pos + 1
        tokens.push(token)
        stack.push(token)
        tokens = token.nodes = []
        parent = token
      }
      name = ''

      // Close parentheses
    } else if (closeParentheses === code && balanced) {
      pos += 1
      code = value.charCodeAt(pos)

      parent.after = after
      parent.sourceEndIndex += after.length
      after = ''
      balanced -= 1
      stack[stack.length - 1].sourceEndIndex = pos
      stack.pop()
      parent = stack[balanced]
      tokens = parent.nodes

      // Words
    } else {
      next = pos
      do {
        if (code === backslash) {
          next += 1
        }
        next += 1
        code = value.charCodeAt(next)
      } while (
        next < max &&
        !(
          code <= 32 ||
          code === singleQuote ||
          code === doubleQuote ||
          code === comma ||
          code === colon ||
          code === slash ||
          code === openParentheses ||
          (code === star && parent && parent.type === 'function' && true) ||
          (code === slash && parent.type === 'function' && true) ||
          (code === closeParentheses && balanced)
        )
      )
      token = value.slice(pos, next)

      if (openParentheses === code) {
        name = token
      } else if (
        (uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) &&
        plus === token.charCodeAt(1) &&
        isUnicodeRange.test(token.slice(2))
      ) {
        tokens.push({
          type: 'unicode-range',
          sourceIndex: pos,
          sourceEndIndex: next,
          value: token,
        })
      } else {
        tokens.push({
          type: 'word',
          sourceIndex: pos,
          sourceEndIndex: next,
          value: token,
        })
      }

      pos = next
    }
  }

  for (pos = stack.length - 1; pos; pos -= 1) {
    stack[pos].unclosed = true
    stack[pos].sourceEndIndex = value.length
  }

  return stack[0].nodes
}
