"use strict";

var _classProps = function (child, staticProps, instanceProps) {
  if (staticProps) Object.defineProperties(child, staticProps);
  if (instanceProps) Object.defineProperties(child.prototype, instanceProps);
};

var Declaration = require("./declaration");
var tokenize = require("./tokenize");
var Comment = require("./comment");
var AtRule = require("./at-rule");
var Root = require("./root");
var Rule = require("./rule");

// CSS parser
var Parser = (function () {
  var Parser = function Parser(input) {
    this.input = input;

    this.pos = 0;
    this.root = new Root();
    this.current = this.root;
    this.spaces = "";
    this.semicolon = false;

    if (this.input.map) this.root.prevMap = this.input.map;
  };

  _classProps(Parser, null, {
    tokenize: {
      writable: true,
      value: function () {
        this.tokens = tokenize(this.input);
      }
    },
    loop: {
      writable: true,
      value: function () {
        var token;
        while (this.pos < this.tokens.length) {
          token = this.tokens[this.pos];

          switch (token[0]) {
            case "word":
            case ":":
              this.word(token);
              break;

            case "}":
              this.end(token);
              break;

            case "comment":
              this.comment(token);
              break;

            case "at-word":
              this.atrule(token);
              break;

            case "{":
              this.emptyRule(token);
              break;

            default:
              this.spaces += token[1];
              break;
          }

          this.pos += 1;
        }
        this.endFile();
      }
    },
    comment: {
      writable: true,
      value: function (token) {
        var node = new Comment();
        this.init(node, token[2], token[3]);
        node.source.end = { line: token[4], column: token[5] };

        var text = token[1].slice(2, -2);
        if (text.match(/^\s*$/)) {
          node.left = text;
          node.text = "";
          node.right = "";
        } else {
          var match = text.match(/^(\s*)([^]*[^\s])(\s*)$/);
          node.left = match[1];
          node.text = match[2];
          node.right = match[3];
        }
      }
    },
    emptyRule: {
      writable: true,
      value: function (token) {
        var node = new Rule();
        this.init(node, token[2], token[3]);
        node.between = "";
        node.selector = "";
        this.current = node;
      }
    },
    word: {
      writable: true,
      value: function () {
        var token;
        var end = false;
        var type = null;
        var colon = false;
        var bracket = null;
        var brackets = 0;

        var start = this.pos;
        this.pos += 1;
        while (this.pos < this.tokens.length) {
          token = this.tokens[this.pos];
          if (!token) {
            end = true;
            break;
          }

          type = token[0];
          if (type == "(") {
            if (!bracket) bracket = token;
            brackets += 1;
          } else if (type == ")") {
            brackets -= 1;
            if (brackets === 0) bracket = null;
          } else if (brackets === 0) {
            if (type == ";") {
              if (colon) {
                this.decl(this.tokens.slice(start, this.pos + 1));
                return;
              } else {
                break;
              }
            } else if (type == "{") {
              this.rule(this.tokens.slice(start, this.pos + 1));
              return;
            } else if (type == "}") {
              this.pos -= 1;
              end = true;
              break;
            } else if (type == "at-word") {
              this.pos -= 1;
              break;
            } else {
              if (type == ":") colon = true;
            }
          }

          this.pos += 1;
        }

        if (brackets > 0 && !this.input.safe) {
          this.input.error("Unclosed bracket", bracket[2], bracket[3]);
        }

        if (end && colon) {
          while (this.pos > start) {
            token = this.tokens[this.pos][0];
            if (token != "space" && token != "comment") break;
            this.pos -= 1;
          }
          this.decl(this.tokens.slice(start, this.pos + 1));
          return;
        }

        if (this.input.safe) {
          var buffer = this.tokens.slice(start, this.pos + 1);
          this.spaces += buffer.map(function (i) {
            return i[1];
          }).join("");
        } else {
          token = this.tokens[start];
          this.input.error("Unknown word", token[2], token[3]);
        }
      }
    },
    rule: {
      writable: true,
      value: function (tokens) {
        tokens.pop();

        var node = new Rule();
        this.init(node, tokens[0][2], tokens[0][3]);

        node.between = this.spacesFromEnd(tokens);
        this.raw(node, "selector", tokens);
        this.current = node;
      }
    },
    decl: {
      writable: true,
      value: function (tokens) {
        var node = new Declaration();
        this.init(node);

        var last = tokens[tokens.length - 1];
        if (last[0] == ";") {
          this.semicolon = true;
          tokens.pop();
        }
        if (last[4]) {
          node.source.end = { line: last[4], column: last[5] };
        } else {
          node.source.end = { line: last[2], column: last[3] };
        }

        while (tokens[0][0] != "word") {
          node.before += tokens.shift()[1];
        }
        node.source.start = { line: tokens[0][2], column: tokens[0][3] };

        node.prop = tokens.shift()[1];
        node.between = "";

        var token;
        while (tokens.length) {
          token = tokens.shift();

          if (token[0] == ":") {
            node.between += token[1];
            break;
          } else if (token[0] != "space" && token[0] != "comment") {
            this.unknownWord(node, token, tokens);
          } else {
            node.between += token[1];
          }
        }

        if (node.prop[0] == "_" || node.prop[0] == "*") {
          node.before += node.prop[0];
          node.prop = node.prop.slice(1);
        }
        node.between += this.spacesFromStart(tokens);

        if (this.input.safe) this.checkMissedSemicolon(tokens);

        for (var i = tokens.length - 1; i > 0; i--) {
          token = tokens[i];
          if (token[1] == "!important") {
            node.important = true;
            var string = this.stringFrom(tokens, i);
            string = this.spacesFromEnd(tokens) + string;
            if (string != " !important") node._important = string;
            break;
          } else if (token[0] != "space" && token[0] != "comment") {
            break;
          }
        }

        this.raw(node, "value", tokens);

        if (node.value.indexOf(":") != -1 && !this.input.safe) {
          this.checkMissedSemicolon(tokens);
        }
      }
    },
    atrule: {
      writable: true,
      value: function (token) {
        var node = new AtRule();
        node.name = token[1].slice(1);
        if (node.name === "") {
          if (this.input.safe) {
            node.name = "";
          } else {
            this.input.error("At-rule without name", token[2], token[3]);
          }
        }
        this.init(node, token[2], token[3]);

        var next;
        var last = false;
        var open = false;
        var params = [];
        while (true) {
          this.pos += 1;
          token = this.tokens[this.pos];

          if (!token) {
            last = true;
            break;
          } else if (token[0] == ";") {
            node.source.end = { line: token[2], column: token[3] };
            this.semicolon = true;
            break;
          } else if (token[0] == "{") {
            open = true;
            break;
          } else {
            params.push(token);
          }
        }

        node.between = this.spacesFromEnd(params);
        if (params.length) {
          node.afterName = this.spacesFromStart(params);
          this.raw(node, "params", params);
          if (last) {
            token = params[params.length - 1];
            node.source.end = { line: token[4], column: token[5] };
            this.spaces = node.between;
            node.between = "";
          }
        } else {
          node.afterName = "";
          node.params = "";
        }

        if (open) {
          node.childs = [];
          this.current = node;
        }
      }
    },
    end: {
      writable: true,
      value: function (token) {
        if (this.semicolon) {
          this.current.semicolon = true;
          this.semicolon = false;
        }
        this.current.after = (this.current.after || "") + this.spaces;
        this.spaces = "";

        if (this.current.parent) {
          this.current.source.end = { line: token[2], column: token[3] };
          this.current = this.current.parent;
        } else if (!this.input.safe) {
          this.input.error("Unexpected }", token[2], token[3]);
        } else {
          this.current.after += "}";
        }
      }
    },
    endFile: {
      writable: true,
      value: function () {
        if (this.current.parent && !this.input.safe) {
          var pos = this.current.source.start;
          this.input.error("Unclosed block", pos.line, pos.column);
        }

        if (this.semicolon) this.current.semicolon = true;
        this.current.after = (this.current.after || "") + this.spaces;

        while (this.current.parent) {
          this.current = this.current.parent;
          this.current.after = "";
        }
      }
    },
    unknownWord: {
      writable: true,
      value: function (node, token) {
        if (this.input.safe) {
          node.source.start = { line: token[2], column: token[3] };
          node.before += node.prop + node.between;
          node.prop = token[1];
          node.between = "";
        } else {
          this.input.error("Unknown word", token[2], token[3]);
        }
      }
    },
    checkMissedSemicolon: {
      writable: true,
      value: function (tokens) {
        var colon = false;
        var brackets = 0;
        for (var i = 0; i < tokens.length; i++) {
          token = tokens[i][0];
          if (token == "(") {
            brackets += 1;
          } else if (token == ")") {
            brackets -= 0;
          } else if (i > 1 && brackets === 0 && token == ":") {
            colon = i;
            break;
          }
        }

        if (colon === false) return;

        if (this.input.safe) {
          var split;
          for (split = colon - 1; split >= 0; split--) {
            if (tokens[split][0] == "word") break;
          }
          for (split -= 1; split >= 0; split--) {
            if (tokens[split][0] != "space") {
              split += 1;
              break;
            }
          }
          var other = tokens.splice(split, tokens.length - split);
          this.decl(other);
        } else {
          var token;
          var founded = 0;
          for (var j = colon - 1; j >= 0; j--) {
            token = tokens[j];
            if (token[0] != "space") {
              founded += 1;
              if (founded == 2) break;
            }
          }
          this.input.error("Missed semicolon", token[4], token[5]);
        }
      }
    },
    init: {
      writable: true,


      // Helpers

      value: function (node, line, column) {
        this.current.push(node);

        node.source = { start: { line: line, column: column }, content: this.input.css };
        if (this.input.map) node.source.map = this.input.map;
        if (this.input.file) {
          node.source.file = this.input.file;
        } else {
          node.source.id = this.input.id;
        }

        node.before = this.spaces;
        this.spaces = "";
        this.semicolon = false;
      }
    },
    raw: {
      writable: true,
      value: function (node, prop, tokens) {
        var i;
        var value = "";
        var clean = true;
        for (i = 0; i < tokens.length; i++) {
          if (tokens[i][0] == "comment") {
            clean = false;
          } else {
            value += tokens[i][1];
          }
        }
        if (!clean) {
          var origin = "";
          for (i = 0; i < tokens.length; i++) {
            origin += tokens[i][1];
          }
          node["_" + prop] = { value: value, raw: origin };
        }
        node[prop] = value;
      }
    },
    spacesFromEnd: {
      writable: true,
      value: function (tokens) {
        var next;
        var spaces = "";
        while (tokens.length) {
          next = tokens[tokens.length - 1][0];
          if (next != "space" && next != "comment") break;
          spaces += tokens.pop()[1];
        }
        return spaces;
      }
    },
    spacesFromStart: {
      writable: true,
      value: function (tokens) {
        var next;
        var spaces = "";
        while (tokens.length) {
          next = tokens[0][0];
          if (next != "space" && next != "comment") break;
          spaces += tokens.shift()[1];
        }
        return spaces;
      }
    },
    stringFrom: {
      writable: true,
      value: function (tokens, from) {
        var result = "";
        for (var i = from; i < tokens.length; i++) {
          result += tokens[i][1];
        }
        tokens.splice(from, tokens.length - from);
        return result;
      }
    }
  });

  return Parser;
})();

module.exports = Parser;