"use strict";
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;
};
