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;