text: var Selector = require("./selector"),
      Element = require("./element"),
      Ruleset = require("./ruleset"),
      Rule = require("./rule"),
      Expression = require("./expression"),
      contexts = require("../contexts");
  
  var Definition = function (name, params, rules, condition, variadic, frames, visibilityInfo) {
      this.name = name;
      this.selectors = [new Selector([new Element(null, name, this.index, this.currentFileInfo)])];
      this.params = params;
      this.condition = condition;
      this.variadic = variadic;
      this.arity = params.length;
      this.rules = rules;
      this._lookups = {};
      var optionalParameters = [];
      this.required = params.reduce(function (count, p) {
          if (!p.name || (p.name && !p.value)) {
              return count + 1;
          }
          else {
              optionalParameters.push(p.name);
              return count;
          }
      }, 0);
      this.optionalParameters = optionalParameters;
      this.frames = frames;
      this.copyVisibilityInfo(visibilityInfo);
  };
  Definition.prototype = new Ruleset();
  Definition.prototype.type = "MixinDefinition";
  Definition.prototype.evalFirst = true;
  Definition.prototype.accept = function (visitor) {
      if (this.params && this.params.length) {
          this.params = visitor.visitArray(this.params);
      }
      this.rules = visitor.visitArray(this.rules);
      if (this.condition) {
          this.condition = visitor.visit(this.condition);
      }
  };
  Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArguments) {
      /*jshint boss:true */
      var frame = new Ruleset(null, null),
          varargs, arg,
          params = this.params.slice(0),
          i, j, val, name, isNamedFound, argIndex, argsLength = 0;
  
      if (mixinEnv.frames && mixinEnv.frames[0] && mixinEnv.frames[0].functionRegistry) {
          frame.functionRegistry = mixinEnv.frames[0].functionRegistry.inherit();
      }
      mixinEnv = new contexts.Eval(mixinEnv, [frame].concat(mixinEnv.frames));
  
      if (args) {
          args = args.slice(0);
          argsLength = args.length;
  
          for (i = 0; i < argsLength; i++) {
              arg = args[i];
              if (name = (arg && arg.name)) {
                  isNamedFound = false;
                  for (j = 0; j < params.length; j++) {
                      if (!evaldArguments[j] && name === params[j].name) {
                          evaldArguments[j] = arg.value.eval(context);
                          frame.prependRule(new Rule(name, arg.value.eval(context)));
                          isNamedFound = true;
                          break;
                      }
                  }
                  if (isNamedFound) {
                      args.splice(i, 1);
                      i--;
                      continue;
                  } else {
                      throw { type: 'Runtime', message: "Named argument for " + this.name +
                          ' ' + args[i].name + ' not found' };
                  }
              }
          }
      }
      argIndex = 0;
      for (i = 0; i < params.length; i++) {
          if (evaldArguments[i]) { continue; }
  
          arg = args && args[argIndex];
  
          if (name = params[i].name) {
              if (params[i].variadic) {
                  varargs = [];
                  for (j = argIndex; j < argsLength; j++) {
                      varargs.push(args[j].value.eval(context));
                  }
                  frame.prependRule(new Rule(name, new Expression(varargs).eval(context)));
              } else {
                  val = arg && arg.value;
                  if (val) {
                      val = val.eval(context);
                  } else if (params[i].value) {
                      val = params[i].value.eval(mixinEnv);
                      frame.resetCache();
                  } else {
                      throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
                          ' (' + argsLength + ' for ' + this.arity + ')' };
                  }
  
                  frame.prependRule(new Rule(name, val));
                  evaldArguments[i] = val;
              }
          }
  
          if (params[i].variadic && args) {
              for (j = argIndex; j < argsLength; j++) {
                  evaldArguments[j] = args[j].value.eval(context);
              }
          }
          argIndex++;
      }
  
      return frame;
  };
  Definition.prototype.makeImportant = function() {
      var rules = !this.rules ? this.rules : this.rules.map(function (r) {
          if (r.makeImportant) {
              return r.makeImportant(true);
          } else {
              return r;
          }
      });
      var result = new Definition (this.name, this.params, rules, this.condition, this.variadic, this.frames);
      return result;
  };
  Definition.prototype.eval = function (context) {
      return new Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || context.frames.slice(0));
  };
  Definition.prototype.evalCall = function (context, args, important) {
      var _arguments = [],
          mixinFrames = this.frames ? this.frames.concat(context.frames) : context.frames,
          frame = this.evalParams(context, new contexts.Eval(context, mixinFrames), args, _arguments),
          rules, ruleset;
  
      frame.prependRule(new Rule('@arguments', new Expression(_arguments).eval(context)));
  
      rules = this.rules.slice(0);
  
      ruleset = new Ruleset(null, rules);
      ruleset.originalRuleset = this;
      ruleset = ruleset.eval(new contexts.Eval(context, [this, frame].concat(mixinFrames)));
      if (important) {
          ruleset = ruleset.makeImportant();
      }
      return ruleset;
  };
  Definition.prototype.matchCondition = function (args, context) {
      if (this.condition && !this.condition.eval(
          new contexts.Eval(context,
              [this.evalParams(context, /* the parameter variables*/
                  new contexts.Eval(context, this.frames ? this.frames.concat(context.frames) : context.frames), args, [])]
              .concat(this.frames || []) // the parent namespace/mixin frames
              .concat(context.frames)))) { // the current environment frames
          return false;
      }
      return true;
  };
  Definition.prototype.matchArgs = function (args, context) {
      var allArgsCnt = (args && args.length) || 0, len, optionalParameters = this.optionalParameters;
      var requiredArgsCnt = !args ? 0 : args.reduce(function (count, p) {
          if (optionalParameters.indexOf(p.name) < 0) {
              return count + 1;
          } else {
              return count;
          }
      }, 0);
  
      if (! this.variadic) {
          if (requiredArgsCnt < this.required) {
              return false;
          }
          if (allArgsCnt > this.params.length) {
              return false;
          }
      } else {
          if (requiredArgsCnt < (this.required - 1)) {
              return false;
          }
      }
  
      // check patterns
      len = Math.min(requiredArgsCnt, this.arity);
  
      for (var i = 0; i < len; i++) {
          if (!this.params[i].name && !this.params[i].variadic) {
              if (args[i].value.eval(context).toCSS() != this.params[i].value.eval(context).toCSS()) {
                  return false;
              }
          }
      }
      return true;
  };
  module.exports = Definition;
  