'use strict';

Object.defineProperty(exports, '__esModule', {
    value: true
});

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var _selectorsRoot = require('./selectors/root');

var _selectorsRoot2 = _interopRequireDefault(_selectorsRoot);

var _selectorsSelector = require('./selectors/selector');

var _selectorsSelector2 = _interopRequireDefault(_selectorsSelector);

var _selectorsClassName = require('./selectors/className');

var _selectorsClassName2 = _interopRequireDefault(_selectorsClassName);

var _selectorsComment = require('./selectors/comment');

var _selectorsComment2 = _interopRequireDefault(_selectorsComment);

var _selectorsId = require('./selectors/id');

var _selectorsId2 = _interopRequireDefault(_selectorsId);

var _selectorsTag = require('./selectors/tag');

var _selectorsTag2 = _interopRequireDefault(_selectorsTag);

var _selectorsString = require('./selectors/string');

var _selectorsString2 = _interopRequireDefault(_selectorsString);

var _selectorsPseudo = require('./selectors/pseudo');

var _selectorsPseudo2 = _interopRequireDefault(_selectorsPseudo);

var _selectorsAttribute = require('./selectors/attribute');

var _selectorsAttribute2 = _interopRequireDefault(_selectorsAttribute);

var _selectorsUniversal = require('./selectors/universal');

var _selectorsUniversal2 = _interopRequireDefault(_selectorsUniversal);

var _selectorsCombinator = require('./selectors/combinator');

var _selectorsCombinator2 = _interopRequireDefault(_selectorsCombinator);

var _sortAscending = require('./sortAscending');

var _sortAscending2 = _interopRequireDefault(_sortAscending);

var _tokenize = require('./tokenize');

var _tokenize2 = _interopRequireDefault(_tokenize);

var _flatten = require('flatten');

var _flatten2 = _interopRequireDefault(_flatten);

var _indexesOf = require('indexes-of');

var _indexesOf2 = _interopRequireDefault(_indexesOf);

var _uniq = require('uniq');

var _uniq2 = _interopRequireDefault(_uniq);

var Parser = (function () {
    function Parser(input) {
        _classCallCheck(this, Parser);

        this.input = input;
        this.position = 0;
        this.root = new _selectorsRoot2['default']();

        var selectors = new _selectorsSelector2['default']();
        this.root.append(selectors);

        this.current = selectors;
        this.tokens = (0, _tokenize2['default'])(input);

        return this.loop();
    }

    _createClass(Parser, [{
        key: 'attribute',
        value: function attribute() {
            var attribute = '';
            var attr = undefined;
            var startingToken = this.currToken;
            this.position++;
            while (this.position < this.tokens.length && this.currToken[0] !== ']') {
                attribute += this.tokens[this.position][1];
                this.position++;
            }
            if (this.position === this.tokens.length && ! ~attribute.indexOf(']')) {
                this.error('Expected a closing square bracket.');
            }
            var parts = attribute.split(/((?:[*~^$|]?)=)/);
            var namespace = parts[0].split(/(\|)/g);
            var attributeProps = {
                operator: parts[1],
                value: parts[2],
                source: {
                    start: {
                        line: startingToken[2],
                        column: startingToken[3]
                    },
                    end: {
                        line: this.currToken[2],
                        column: this.currToken[3]
                    }
                },
                sourceIndex: startingToken[4]
            };
            if (namespace.length > 1) {
                if (namespace[0] === '') {
                    namespace[0] = true;
                }
                attributeProps.attribute = namespace[2];
                attributeProps.namespace = namespace[0];
            } else {
                attributeProps.attribute = parts[0];
            }
            attr = new _selectorsAttribute2['default'](attributeProps);

            if (parts[2]) {
                var insensitive = parts[2].split(/(\s+i\s*?)$/);
                attr.value = insensitive[0];
                if (insensitive[1]) {
                    attr.insensitive = true;
                    attr.raw.insensitive = insensitive[1];
                }
            }
            this.newNode(attr);
            this.position++;
        }
    }, {
        key: 'combinator',
        value: function combinator() {
            if (this.currToken[1] === '|') {
                return this.namespace();
            }
            var combinator = new _selectorsCombinator2['default']({
                value: '',
                source: {
                    start: {
                        line: this.currToken[2],
                        column: this.currToken[3]
                    },
                    end: {
                        line: this.currToken[2],
                        column: this.currToken[3]
                    }
                },
                sourceIndex: this.currToken[4]
            });
            while (this.position < this.tokens.length && this.currToken && (this.currToken[0] === 'space' || this.currToken[0] === 'combinator')) {
                if (this.nextToken && this.nextToken[0] === 'combinator') {
                    combinator.spaces.before = this.currToken[1];
                    combinator.source.start.line = this.nextToken[2];
                    combinator.source.start.column = this.nextToken[3];
                    combinator.source.end.column = this.nextToken[3];
                    combinator.source.end.line = this.nextToken[2];
                    combinator.sourceIndex = this.nextToken[4];
                } else if (this.prevToken && this.prevToken[0] === 'combinator') {
                    combinator.spaces.after = this.currToken[1];
                } else if (this.currToken[0] === 'space' || this.currToken[0] === 'combinator') {
                    combinator.value = this.currToken[1];
                }
                this.position++;
            }
            return this.newNode(combinator);
        }
    }, {
        key: 'comma',
        value: function comma() {
            if (this.position === this.tokens.length - 1) {
                this.root.trailingComma = true;
                this.position++;
                return;
            }
            var selectors = new _selectorsSelector2['default']();
            this.current.parent.append(selectors);
            this.current = selectors;
            this.position++;
        }
    }, {
        key: 'comment',
        value: function comment() {
            var comment = new _selectorsComment2['default']({
                value: this.currToken[1],
                source: {
                    start: {
                        line: this.currToken[2],
                        column: this.currToken[3]
                    },
                    end: {
                        line: this.currToken[4],
                        column: this.currToken[5]
                    }
                },
                sourceIndex: this.currToken[6]
            });
            this.newNode(comment);
            this.position++;
        }
    }, {
        key: 'error',
        value: function error(message) {
            throw new this.input.error(message);
        }
    }, {
        key: 'namespace',
        value: function namespace() {
            var before = this.prevToken && this.prevToken[1] || true;
            if (this.nextToken[0] === 'word') {
                this.position++;
                return this.word(before);
            } else if (this.nextToken[0] === '*') {
                this.position++;
                return this.universal(before);
            }
        }
    }, {
        key: 'parentheses',
        value: function parentheses() {
            var last = this.current.last;
            if (last && last.type === 'pseudo') {
                var selector = new _selectorsSelector2['default']();
                var cache = this.current;
                last.append(selector);
                this.current = selector;
                var balanced = 1;
                this.position++;
                while (this.position < this.tokens.length && balanced) {
                    if (this.currToken[0] === '(') {
                        balanced++;
                    }
                    if (this.currToken[0] === ')') {
                        balanced--;
                    }
                    if (balanced) {
                        this.parse();
                    } else {
                        selector.parent.source.end.line = this.currToken[2];
                        selector.parent.source.end.column = this.currToken[3];
                        this.position++;
                    }
                }
                if (balanced) {
                    this.error('Expected closing parenthesis.');
                }
                this.current = cache;
            } else {
                var balanced = 1;
                this.position++;
                last.value += '(';
                while (this.position < this.tokens.length && balanced) {
                    if (this.currToken[0] === '(') {
                        balanced++;
                    }
                    if (this.currToken[0] === ')') {
                        balanced--;
                    }
                    last.value += this.currToken[1];
                    this.position++;
                }
                if (balanced) {
                    this.error('Expected closing parenthesis.');
                }
            }
        }
    }, {
        key: 'pseudo',
        value: function pseudo() {
            var _this = this;

            var pseudoStr = '';
            var startingToken = this.currToken;
            while (this.currToken && this.currToken[0] === ':') {
                pseudoStr += this.currToken[1];
                this.position++;
            }
            if (!this.currToken) {
                return this.error('Expected pseudo-class or pseudo-element');
            }
            if (this.currToken[0] === 'word') {
                (function () {
                    var pseudo = undefined;
                    _this.splitWord(false, function (first, length) {
                        pseudoStr += first;
                        pseudo = new _selectorsPseudo2['default']({
                            value: pseudoStr,
                            source: {
                                start: {
                                    line: startingToken[2],
                                    column: startingToken[3]
                                },
                                end: {
                                    line: _this.currToken[4],
                                    column: _this.currToken[5]
                                }
                            },
                            sourceIndex: startingToken[4]
                        });
                        _this.newNode(pseudo);
                        if (length > 1 && _this.nextToken && _this.nextToken[0] === '(') {
                            _this.error('Misplaced parenthesis.');
                        }
                    });
                })();
            } else {
                this.error('Unexpected "' + this.currToken[0] + '" found.');
            }
        }
    }, {
        key: 'space',
        value: function space() {
            var token = this.currToken;
            // Handle space before and after the selector
            if (this.position === 0 || this.prevToken[0] === ',' || this.prevToken[0] === '(') {
                this.spaces = token[1];
                this.position++;
            } else if (this.position === this.tokens.length - 1 || this.nextToken[0] === ',' || this.nextToken[0] === ')') {
                this.current.last.spaces.after = token[1];
                this.position++;
            } else {
                this.combinator();
            }
        }
    }, {
        key: 'string',
        value: function string() {
            var token = this.currToken;
            this.newNode(new _selectorsString2['default']({
                value: this.currToken[1],
                source: {
                    start: {
                        line: token[2],
                        column: token[3]
                    },
                    end: {
                        line: token[4],
                        column: token[5]
                    }
                },
                sourceIndex: token[6]
            }));
            this.position++;
        }
    }, {
        key: 'universal',
        value: function universal(namespace) {
            var nextToken = this.nextToken;
            if (nextToken && nextToken[1] === '|') {
                this.position++;
                return this.namespace();
            }
            this.newNode(new _selectorsUniversal2['default']({
                value: this.currToken[1],
                source: {
                    start: {
                        line: this.currToken[2],
                        column: this.currToken[3]
                    },
                    end: {
                        line: this.currToken[2],
                        column: this.currToken[3]
                    }
                },
                sourceIndex: this.currToken[4]
            }), namespace);
            this.position++;
        }
    }, {
        key: 'splitWord',
        value: function splitWord(namespace, firstCallback) {
            var _this2 = this;

            var nextToken = this.nextToken;
            var word = this.currToken[1];
            while (nextToken && nextToken[0] === 'word') {
                this.position++;
                var current = this.currToken[1];
                word += current;
                if (current.lastIndexOf('\\') === current.length - 1) {
                    var next = this.nextToken;
                    if (next && next[0] === 'space') {
                        word += next[1];
                        this.position++;
                    }
                }
                nextToken = this.nextToken;
            }
            var hasClass = (0, _indexesOf2['default'])(word, '.');
            var hasId = (0, _indexesOf2['default'])(word, '#');
            var indices = (0, _sortAscending2['default'])((0, _uniq2['default'])((0, _flatten2['default'])([[0], hasClass, hasId])));
            indices.forEach(function (ind, i) {
                var index = indices[i + 1] || word.length;
                var value = word.slice(ind, index);
                if (i === 0 && firstCallback) {
                    return firstCallback.call(_this2, value, indices.length);
                }
                var node = undefined;
                if (~hasClass.indexOf(ind)) {
                    node = new _selectorsClassName2['default']({
                        value: value.slice(1),
                        source: {
                            start: {
                                line: _this2.currToken[2],
                                column: _this2.currToken[3] + ind
                            },
                            end: {
                                line: _this2.currToken[4],
                                column: _this2.currToken[3] + (index - 1)
                            }
                        },
                        sourceIndex: _this2.currToken[6] + indices[i]
                    });
                } else if (~hasId.indexOf(ind)) {
                    node = new _selectorsId2['default']({
                        value: value.slice(1),
                        source: {
                            start: {
                                line: _this2.currToken[2],
                                column: _this2.currToken[3] + ind
                            },
                            end: {
                                line: _this2.currToken[4],
                                column: _this2.currToken[3] + (index - 1)
                            }
                        },
                        sourceIndex: _this2.currToken[6] + indices[i]
                    });
                } else {
                    node = new _selectorsTag2['default']({
                        value: value,
                        source: {
                            start: {
                                line: _this2.currToken[2],
                                column: _this2.currToken[3] + ind
                            },
                            end: {
                                line: _this2.currToken[4],
                                column: _this2.currToken[3] + (index - 1)
                            }
                        },
                        sourceIndex: _this2.currToken[6] + indices[i]
                    });
                }
                _this2.newNode(node, namespace);
            });
            this.position++;
        }
    }, {
        key: 'word',
        value: function word(namespace) {
            var nextToken = this.nextToken;
            if (nextToken && nextToken[1] === '|') {
                this.position++;
                return this.namespace();
            }
            return this.splitWord(namespace);
        }
    }, {
        key: 'loop',
        value: function loop() {
            while (this.position < this.tokens.length) {
                this.parse();
            }
            return this.root;
        }
    }, {
        key: 'parse',
        value: function parse() {
            switch (this.currToken[0]) {
                case 'space':
                    this.space();
                    break;
                case 'comment':
                    this.comment();
                    break;
                case '(':
                    this.parentheses();
                    break;
                case '[':
                    this.attribute();
                    break;
                case 'at-word':
                case 'word':
                    this.word();
                    break;
                case ':':
                    this.pseudo();
                    break;
                case ',':
                    this.comma();
                    break;
                case '*':
                    this.universal();
                    break;
                case 'combinator':
                    this.combinator();
                    break;
                case 'string':
                    this.string();
                    break;
            }
        }

        /**
         * Helpers
         */

    }, {
        key: 'newNode',
        value: function newNode(node, namespace) {
            if (namespace) {
                node.namespace = namespace;
            }
            if (this.spaces) {
                node.spaces.before = this.spaces;
                this.spaces = '';
            }
            return this.current.append(node);
        }
    }, {
        key: 'currToken',
        get: function get() {
            return this.tokens[this.position];
        }
    }, {
        key: 'nextToken',
        get: function get() {
            return this.tokens[this.position + 1];
        }
    }, {
        key: 'prevToken',
        get: function get() {
            return this.tokens[this.position - 1];
        }
    }]);

    return Parser;
})();

exports['default'] = Parser;
module.exports = exports['default'];