"use strict";

var singleQuote = "'".charCodeAt(0),
    doubleQuote = "\"".charCodeAt(0),
    backslash = "\\".charCodeAt(0),
    slash = "/".charCodeAt(0),
    newline = "\n".charCodeAt(0),
    space = " ".charCodeAt(0),
    feed = "\f".charCodeAt(0),
    tab = "\t".charCodeAt(0),
    cr = "\r".charCodeAt(0),
    openBracket = "(".charCodeAt(0),
    closeBracket = ")".charCodeAt(0),
    openCurly = "{".charCodeAt(0),
    closeCurly = "}".charCodeAt(0),
    semicolon = ";".charCodeAt(0),
    asterisk = "*".charCodeAt(0),
    colon = ":".charCodeAt(0),
    at = "@".charCodeAt(0),
    atEnd = /[ \n\t\r\{\(\)'"\\/]/g,
    wordEnd = /[ \n\t\r\(\)\{\}:;@!'"\\]|\/(?=\*)/g,
    badBracket = /.[\\\/\("'\n]/;

module.exports = function (input) {
    var tokens = [];
    var css = input.css.valueOf();

    var code, next, quote, lines, last, content, escape, nextLine, nextOffset, escaped, escapePos, bad;

    var length = css.length;
    var offset = -1;
    var line = 1;
    var pos = 0;

    var unclosed = function unclosed(what, end) {
        if (input.safe) {
            css += end;
            next = css.length - 1;
        } else {
            throw input.error("Unclosed " + what, line, pos - offset);
        }
    };

    while (pos < length) {
        code = css.charCodeAt(pos);

        if (code == newline) {
            offset = pos;
            line += 1;
        }

        switch (code) {
            case newline:
            case space:
            case tab:
            case cr:
            case feed:
                next = pos;
                do {
                    next += 1;
                    code = css.charCodeAt(next);
                    if (code == newline) {
                        offset = next;
                        line += 1;
                    }
                } while (code == space || code == newline || code == tab || code == cr || code == feed);

                tokens.push(["space", css.slice(pos, next)]);
                pos = next - 1;
                break;

            case openCurly:
                tokens.push(["{", "{", line, pos - offset]);
                break;

            case closeCurly:
                tokens.push(["}", "}", line, pos - offset]);
                break;

            case colon:
                tokens.push([":", ":", line, pos - offset]);
                break;

            case semicolon:
                tokens.push([";", ";", line, pos - offset]);
                break;

            case openBracket:
                next = css.indexOf(")", pos + 1);
                content = css.slice(pos, next + 1);

                if (next == -1 || badBracket.test(content)) {
                    tokens.push(["(", "(", line, pos - offset]);
                } else {
                    tokens.push(["brackets", content, line, pos - offset, line, next - offset]);
                    pos = next;
                }

                break;

            case closeBracket:
                tokens.push([")", ")", line, pos - offset]);
                break;

            case singleQuote:
            case doubleQuote:
                quote = code == singleQuote ? "'" : "\"";
                next = pos;
                do {
                    escaped = false;
                    next = css.indexOf(quote, next + 1);
                    if (next == -1) unclosed("quote", quote);
                    escapePos = next;
                    while (css.charCodeAt(escapePos - 1) == backslash) {
                        escapePos -= 1;
                        escaped = !escaped;
                    }
                } while (escaped);

                tokens.push(["string", css.slice(pos, next + 1), line, pos - offset, line, next - offset]);
                pos = next;
                break;

            case at:
                atEnd.lastIndex = pos + 1;
                atEnd.test(css);
                if (atEnd.lastIndex === 0) {
                    next = css.length - 1;
                } else {
                    next = atEnd.lastIndex - 2;
                }
                tokens.push(["at-word", css.slice(pos, next + 1), line, pos - offset, line, next - offset]);
                pos = next;
                break;

            case backslash:
                next = pos;
                escape = true;
                while (css.charCodeAt(next + 1) == backslash) {
                    next += 1;
                    escape = !escape;
                }
                code = css.charCodeAt(next + 1);
                if (escape && (code != slash && code != space && code != newline && code != tab && code != cr && code != feed)) {
                    next += 1;
                }
                tokens.push(["word", css.slice(pos, next + 1), line, pos - offset, line, next - offset]);
                pos = next;
                break;

            default:
                if (code == slash && css.charCodeAt(pos + 1) == asterisk) {
                    next = css.indexOf("*/", pos + 2) + 1;
                    if (next === 0) unclosed("comment", "*/");

                    content = css.slice(pos, next + 1);
                    lines = content.split("\n");
                    last = lines.length - 1;

                    if (last > 0) {
                        nextLine = line + last;
                        nextOffset = next - lines[last].length;
                    } else {
                        nextLine = line;
                        nextOffset = offset;
                    }

                    tokens.push(["comment", content, line, pos - offset, nextLine, next - nextOffset]);

                    offset = nextOffset;
                    line = nextLine;
                    pos = next;
                } else {
                    wordEnd.lastIndex = pos + 1;
                    wordEnd.test(css);
                    if (wordEnd.lastIndex === 0) {
                        next = css.length - 1;
                    } else {
                        next = wordEnd.lastIndex - 2;
                    }

                    tokens.push(["word", css.slice(pos, next + 1), line, pos - offset, line, next - offset]);
                    pos = next;
                }

                break;
        }

        pos++;
    }

    return tokens;
};