(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(['exports', './tokenizer', './token', './node-factory'], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports, require('./tokenizer'), require('./token'), require('./node-factory'));
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports, global.tokenizer, global.token, global.nodeFactory);
    global.parser = mod.exports;
  }
})(this, function (exports, _tokenizer, _token, _nodeFactory) {
  'use strict';

  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.Parser = undefined;

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

  var Parser = function () {
    /**
     * Create a Parser instance. When creating a Parser instance, a specialized
     * NodeFactory can be supplied to implement streaming analysis and
     * manipulation of the CSS AST.
     */

    function Parser() {
      var nodeFactory = arguments.length <= 0 || arguments[0] === undefined ? new _nodeFactory.NodeFactory() : arguments[0];

      _classCallCheck(this, Parser);

      this.nodeFactory = nodeFactory;
    }

    /**
     * Parse CSS and generate an AST.
     * @param {string} cssText The CSS to parse.
     * @return {object} A CSS AST containing nodes that correspond to those
     * generated by the Parser's NodeFactory.
     */


    Parser.prototype.parse = function parse(cssText) {
      return this.parseStylesheet(new _tokenizer.Tokenizer(cssText));
    };

    Parser.prototype.parseStylesheet = function parseStylesheet(tokenizer) {
      return this.nodeFactory.stylesheet(this.parseRules(tokenizer));
    };

    Parser.prototype.parseRules = function parseRules(tokenizer) {
      var rules = [];

      while (tokenizer.currentToken) {
        var rule = this.parseRule(tokenizer);

        if (rule) {
          rules.push(rule);
        }
      }

      return rules;
    };

    Parser.prototype.parseRule = function parseRule(tokenizer) {
      // Trim leading whitespace:
      if (tokenizer.currentToken.is(_token.Token.type.whitespace)) {
        tokenizer.advance();
        return null;
      } else if (tokenizer.currentToken.is(_token.Token.type.comment)) {
        return this.parseComment(tokenizer);
      } else if (tokenizer.currentToken.is(_token.Token.type.word)) {
        return this.parseDeclarationOrRuleset(tokenizer);
      } else if (tokenizer.currentToken.is(_token.Token.type.propertyBoundary)) {
        return this.parseUnknown(tokenizer);
      } else if (tokenizer.currentToken.is(_token.Token.type.at)) {
        return this.parseAtRule(tokenizer);
      } else {
        return this.parseUnknown(tokenizer);
      }
    };

    Parser.prototype.parseComment = function parseComment(tokenizer) {
      return this.nodeFactory.comment(tokenizer.slice(tokenizer.advance()));
    };

    Parser.prototype.parseUnknown = function parseUnknown(tokenizer) {
      var start = tokenizer.advance();
      var end = void 0;

      while (tokenizer.currentToken && tokenizer.currentToken.is(_token.Token.type.boundary)) {
        end = tokenizer.advance();
      }

      return this.nodeFactory.discarded(tokenizer.slice(start, end));
    };

    Parser.prototype.parseAtRule = function parseAtRule(tokenizer) {
      var name = '';
      var rulelist = null;
      var parametersStart = null;
      var parametersEnd = null;

      while (tokenizer.currentToken) {
        if (tokenizer.currentToken.is(_token.Token.type.whitespace)) {
          tokenizer.advance();
        } else if (!name && tokenizer.currentToken.is(_token.Token.type.at)) {
          // Discard the @:
          tokenizer.advance();
          var start = tokenizer.currentToken;
          var end = void 0;

          while (tokenizer.currentToken && tokenizer.currentToken.is(_token.Token.type.word)) {
            end = tokenizer.advance();
          }
          name = tokenizer.slice(start, end);
        } else if (tokenizer.currentToken.is(_token.Token.type.openBrace)) {
          rulelist = this.parseRulelist(tokenizer);
          break;
        } else if (tokenizer.currentToken.is(_token.Token.type.propertyBoundary)) {
          tokenizer.advance();
          break;
        } else {
          if (parametersStart == null) {
            parametersStart = tokenizer.advance();
          } else {
            parametersEnd = tokenizer.advance();
          }
        }
      }

      return this.nodeFactory.atRule(name, parametersStart ? tokenizer.slice(parametersStart, parametersEnd) : '', rulelist);
    };

    Parser.prototype.parseRulelist = function parseRulelist(tokenizer) {
      var rules = [];

      // Take the opening { boundary:
      tokenizer.advance();

      while (tokenizer.currentToken) {
        if (tokenizer.currentToken.is(_token.Token.type.closeBrace)) {
          tokenizer.advance();
          break;
        } else {
          var rule = this.parseRule(tokenizer);
          if (rule) {
            rules.push(rule);
          }
        }
      }

      return this.nodeFactory.rulelist(rules);
    };

    Parser.prototype.parseDeclarationOrRuleset = function parseDeclarationOrRuleset(tokenizer) {
      var ruleStart = null;
      var ruleEnd = null;
      var colon = null;

      while (tokenizer.currentToken) {
        if (tokenizer.currentToken.is(_token.Token.type.whitespace)) {
          tokenizer.advance();
        } else if (tokenizer.currentToken.is(_token.Token.type.openParenthesis)) {
          while (tokenizer.currentToken && !tokenizer.currentToken.is(_token.Token.type.closeParenthesis)) {
            tokenizer.advance();
          }
        } else if (tokenizer.currentToken.is(_token.Token.type.openBrace) || tokenizer.currentToken.is(_token.Token.type.propertyBoundary)) {
          break;
        } else {
          if (tokenizer.currentToken.is(_token.Token.type.colon)) {
            colon = tokenizer.currentToken;
          }

          if (!ruleStart) {
            ruleStart = tokenizer.advance();
            ruleEnd = ruleStart;
          } else {
            ruleEnd = tokenizer.advance();
          }
        }
      }

      // A ruleset never contains or ends with a semi-colon.
      if (tokenizer.currentToken.is(_token.Token.type.propertyBoundary)) {
        var declarationName = tokenizer.slice(ruleStart, colon ? colon.previous : ruleEnd);
        // TODO(cdata): is .trim() bad for performance?
        var expressionValue = colon && tokenizer.slice(colon.next, ruleEnd).trim();

        if (tokenizer.currentToken.is(_token.Token.type.semicolon)) {
          tokenizer.advance();
        }

        return this.nodeFactory.declaration(declarationName, expressionValue && this.nodeFactory.expression(expressionValue));
        // This is the case for a mixin-like structure:
      } else if (colon && colon === ruleEnd) {
          var rulelist = this.parseRulelist(tokenizer);

          if (tokenizer.currentToken.is(_token.Token.type.semicolon)) {
            tokenizer.advance();
          }

          return this.nodeFactory.declaration(tokenizer.slice(ruleStart, ruleEnd.previous), rulelist);
          // Otherwise, this is a ruleset:
        } else {
            return this.nodeFactory.ruleset(tokenizer.slice(ruleStart, ruleEnd), this.parseRulelist(tokenizer));
          }
    };

    return Parser;
  }();

  exports.Parser = Parser;
});