"use strict";

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

var _extends = function (child, parent) {
  child.prototype = Object.create(parent.prototype, {
    constructor: {
      value: child,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  child.__proto__ = parent;
};

var Node = require("./node");
var Declaration = require("./declaration");

// CSS node, that contain another nodes (like at-rules or rules with selectors)
var Container = (function (Node) {
  var Container = function Container() {
    Node.apply(this, arguments);
  };

  _extends(Container, Node);

  _classProps(Container, null, {
    styleMap: {
      writable: true,
      value: function () {
        var style = {
          beforeRule: this.before,
          beforeOpen: this.between
        };
        if (this.childs && this.childs.length) {
          style.beforeClose = this.after;
        } else {
          style.emptyBody = this.after;
        }
        return style;
      }
    },
    stringifyContent: {
      writable: true,


      // Stringify container childs
      value: function (builder) {
        if (!this.childs) return;

        var i, last = this.childs.length - 1;
        while (last > 0) {
          if (this.childs[last].type != "comment") break;
          last -= 1;
        }

        for (i = 0; i < this.childs.length; i++) {
          this.childs[i].stringify(builder, last != i || this.semicolon);
        }
      }
    },
    stringifyBlock: {
      writable: true,


      // Stringify node with start (for example, selector) and brackets block
      // with child inside
      value: function (builder, start) {
        var before = this.style("beforeRule");
        if (before) builder(before);
        builder(start + this.style("beforeOpen") + "{", this, "start");

        var after;
        if (this.childs && this.childs.length) {
          this.stringifyContent(builder);
          after = this.style("beforeClose");
        } else {
          after = this.style("emptyBody");
        }

        if (after) builder(after);
        builder("}", this, "end");
      }
    },
    push: {
      writable: true,


      // Add child to end of list without any checks.
      // Please, use `append()` method, `push()` is mostly for parser.
      value: function (child) {
        child.parent = this;
        this.childs.push(child);
        return this;
      }
    },
    each: {
      writable: true,


      // Execute `callback` on every child element. First arguments will be child
      // node, second will be index.
      //
      //   css.each( (rule, i) => {
      //       console.log(rule.type + ' at ' + i);
      //   });
      //
      // It is safe for add and remove elements to list while iterating:
      //
      //  css.each( (rule) => {
      //      css.insertBefore( rule, addPrefix(rule) );
      //      # On next iteration will be next rule, regardless of that
      //      # list size was increased
      //  });
      value: function (callback) {
        if (!this.lastEach) this.lastEach = 0;
        if (!this.indexes) this.indexes = {};

        this.lastEach += 1;
        var id = this.lastEach;
        this.indexes[id] = 0;

        if (!this.childs) return;

        var index, result;
        while (this.indexes[id] < this.childs.length) {
          index = this.indexes[id];
          result = callback(this.childs[index], index);
          if (result === false) break;

          this.indexes[id] += 1;
        }

        delete this.indexes[id];

        if (result === false) return false;
      }
    },
    eachInside: {
      writable: true,


      // Execute callback on every child in all rules inside.
      //
      // First argument will be child node, second will be index inside parent.
      //
      //   css.eachInside( (node, i) => {
      //       console.log(node.type + ' at ' + i);
      //   });
      //
      // Also as `each` it is safe of insert/remove nodes inside iterating.
      value: function (callback) {
        return this.each(function (child, i) {
          var result = callback(child, i);

          if (result !== false && child.eachInside) {
            result = child.eachInside(callback);
          }

          if (result === false) return result;
        });
      }
    },
    eachDecl: {
      writable: true,


      // Execute callback on every declaration in all rules inside.
      // It will goes inside at-rules recursivelly.
      //
      // First argument will be declaration node, second will be index inside
      // parent rule.
      //
      //   css.eachDecl( (decl, i) => {
      //       console.log(decl.prop + ' in ' + decl.parent.selector + ':' + i);
      //   });
      //
      // Also as `each` it is safe of insert/remove nodes inside iterating.
      value: function (callback) {
        return this.eachInside(function (child, i) {
          if (child.type == "decl") {
            var result = callback(child, i);
            if (result === false) return result;
          }
        });
      }
    },
    eachRule: {
      writable: true,


      // Execute `callback` on every rule in conatiner and inside child at-rules.
      //
      // First argument will be rule node, second will be index inside parent.
      //
      //   css.eachRule( (rule, i) => {
      //       if ( parent.type == 'atrule' ) {
      //           console.log(rule.selector + ' in ' + rule.parent.name);
      //       } else {
      //           console.log(rule.selector + ' at ' + i);
      //       }
      //   });
      value: function (callback) {
        return this.eachInside(function (child, i) {
          if (child.type == "rule") {
            var result = callback(child, i);
            if (result === false) return result;
          }
        });
      }
    },
    eachAtRule: {
      writable: true,


      // Execute `callback` on every at-rule in conatiner and inside at-rules.
      //
      // First argument will be at-rule node, second will be index inside parent.
      //
      //   css.eachAtRule( (atrule, parent, i) => {
      //       if ( parent.type == 'atrule' ) {
      //           console.log(atrule.name + ' in ' + atrule.parent.name);
      //       } else {
      //           console.log(atrule.name + ' at ' + i);
      //       }
      //   });
      value: function (callback) {
        return this.eachInside(function (child, i) {
          if (child.type == "atrule") {
            var result = callback(child, i);
            if (result === false) return result;
          }
        });
      }
    },
    eachComment: {
      writable: true,


      // Execute callback on every block comment (only between rules
      // and declarations, not inside selectors and values) in all rules inside.
      //
      // First argument will be comment node, second will be index inside
      // parent rule.
      //
      //   css.eachComment( (comment, i) => {
      //       console.log(comment.content + ' at ' + i);
      //   });
      //
      // Also as `each` it is safe of insert/remove nodes inside iterating.
      value: function (callback) {
        return this.eachInside(function (child, i) {
          if (child.type == "comment") {
            var result = callback(child, i);
            if (result === false) return result;
          }
        });
      }
    },
    append: {
      writable: true,


      // Add child to container.
      //
      //   css.append(rule);
      //
      // You can add declaration by hash:
      //
      //   rule.append({ prop: 'color', value: 'black' });
      value: function (child) {
        var childs = this.normalize(child, this.last);
        for (var i = 0; i < childs.length; i++) {
          this.childs.push(childs[i]);
        }
        return this;
      }
    },
    prepend: {
      writable: true,


      // Add child to beginning of container
      //
      //   css.prepend(rule);
      //
      // You can add declaration by hash:
      //
      //   rule.prepend({ prop: 'color', value: 'black' });
      value: function (child) {
        var childs = this.normalize(child, this.first, "prepend").reverse();
        for (var i = 0; i < childs.length; i++) {
          this.childs.unshift(childs[i]);
        }

        for (var id in this.indexes) {
          this.indexes[id] = this.indexes[id] + childs.length;
        }

        return this;
      }
    },
    insertBefore: {
      writable: true,


      // Insert new `added` child before `exist`.
      // You can set node object or node index (it will be faster) in `exist`.
      //
      //   css.insertAfter(1, rule);
      //
      // You can add declaration by hash:
      //
      //   rule.insertBefore(1, { prop: 'color', value: 'black' });
      value: function (exist, add) {
        exist = this.index(exist);

        var type = exist === 0 ? "prepend" : false;
        var childs = this.normalize(add, this.childs[exist], type).reverse();
        for (var i = 0; i < childs.length; i++) {
          this.childs.splice(exist, 0, childs[i]);
        }

        var index;
        for (var id in this.indexes) {
          index = this.indexes[id];
          if (exist <= index) {
            this.indexes[id] = index + childs.length;
          }
        }

        return this;
      }
    },
    insertAfter: {
      writable: true,


      // Insert new `added` child after `exist`.
      // You can set node object or node index (it will be faster) in `exist`.
      //
      //   css.insertAfter(1, rule);
      //
      // You can add declaration by hash:
      //
      //   rule.insertAfter(1, { prop: 'color', value: 'black' });
      value: function (exist, add) {
        exist = this.index(exist);

        var childs = this.normalize(add, this.childs[exist]).reverse();
        for (var i = 0; i < childs.length; i++) {
          this.childs.splice(exist + 1, 0, childs[i]);
        }

        var index;
        for (var id in this.indexes) {
          index = this.indexes[id];
          if (exist < index) {
            this.indexes[id] = index + childs.length;
          }
        }

        return this;
      }
    },
    remove: {
      writable: true,


      // Remove `child` by index or node.
      //
      //   css.remove(2);
      value: function (child) {
        child = this.index(child);
        this.childs.splice(child, 1);

        var index;
        for (var id in this.indexes) {
          index = this.indexes[id];
          if (index >= child) {
            this.indexes[id] = index - 1;
          }
        }

        return this;
      }
    },
    every: {
      writable: true,


      // Return true if all childs return true in `condition`.
      // Just shorcut for `childs.every`.
      value: function (condition) {
        return this.childs.every(condition);
      }
    },
    some: {
      writable: true,


      // Return true if one or more childs return true in `condition`.
      // Just shorcut for `childs.some`.
      value: function (condition) {
        return this.childs.some(condition);
      }
    },
    index: {
      writable: true,


      // Return index of child
      value: function (child) {
        if (typeof (child) == "number") {
          return child;
        } else {
          return this.childs.indexOf(child);
        }
      }
    },
    first: {
      // Shortcut to get first child
      get: function () {
        if (!this.childs) return undefined;
        return this.childs[0];
      }
    },
    last: {
      // Shortcut to get first child
      get: function () {
        if (!this.childs) return undefined;
        return this.childs[this.childs.length - 1];
      }
    },
    normalize: {
      writable: true,


      // Normalize child before insert. Copy before from `sample`.
      value: function (child, sample) {
        if (!child.type && !Array.isArray(child)) {
          child = new Declaration(child);
        }

        var childs;
        if (child.type == "root") {
          childs = child.childs;
        } else if (Array.isArray(child)) {
          childs = child.map(function (i) {
            return i.clone();
          });
        } else {
          if (child.parent) {
            child = child.clone();
          }
          childs = [child];
        }

        for (var i = 0; i < childs.length; i++) {
          child = childs[i];
          child.parent = this;
          if (typeof (child.before) == "undefined" && sample) {
            child.before = sample.before;
          }
        }

        return childs;
      }
    }
  });

  return Container;
})(Node);

["rules", "decls", "list"].forEach(function (name) {
  Object.defineProperty(Container.prototype, name, {
    get: function () {
      if (console && console.warn) {
        console.warn("Property " + name + " has been deprecated and " + "will be removed in 3.1. Use childs instead.");
      }
      return this.childs;
    }
  });
});

module.exports = Container;