var Node = require(“./node”),

Selector = require("./selector"),
MixinDefinition = require("./mixin-definition"),
defaultFunc = require("../functions/default");

var MixinCall = function (elements, args, index, currentFileInfo, important) {

this.selector = new Selector(elements);
this.arguments = args || [];
this.index = index;
this.currentFileInfo = currentFileInfo;
this.important = important;

}; MixinCall.prototype = new Node(); MixinCall.prototype.type = “MixinCall”; MixinCall.prototype.accept = function (visitor) {

if (this.selector) {
    this.selector = visitor.visit(this.selector);
}
if (this.arguments.length) {
    this.arguments = visitor.visitArray(this.arguments);
}

}; MixinCall.prototype.eval = function (context) {

var mixins, mixin, mixinPath, args = [], arg, argValue,
    rules = [], match = false, i, m, f, isRecursive, isOneFound,
    candidates = [], candidate, conditionResult = [], defaultResult, defFalseEitherCase = -1,
    defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset, noArgumentsFilter;

function calcDefGroup(mixin, mixinPath) {
    var f, p, namespace;

    for (f = 0; f < 2; f++) {
        conditionResult[f] = true;
        defaultFunc.value(f);
        for (p = 0; p < mixinPath.length && conditionResult[f]; p++) {
            namespace = mixinPath[p];
            if (namespace.matchCondition) {
                conditionResult[f] = conditionResult[f] && namespace.matchCondition(null, context);
            }
        }
        if (mixin.matchCondition) {
            conditionResult[f] = conditionResult[f] && mixin.matchCondition(args, context);
        }
    }
    if (conditionResult[0] || conditionResult[1]) {
        if (conditionResult[0] != conditionResult[1]) {
            return conditionResult[1] ?
                defTrue : defFalse;
        }

        return defNone;
    }
    return defFalseEitherCase;
}

for (i = 0; i < this.arguments.length; i++) {
    arg = this.arguments[i];
    argValue = arg.value.eval(context);
    if (arg.expand && Array.isArray(argValue.value)) {
        argValue = argValue.value;
        for (m = 0; m < argValue.length; m++) {
            args.push({value: argValue[m]});
        }
    } else {
        args.push({name: arg.name, value: argValue});
    }
}

noArgumentsFilter = function(rule) {return rule.matchArgs(null, context);};

for (i = 0; i < context.frames.length; i++) {
    if ((mixins = context.frames[i].find(this.selector, null, noArgumentsFilter)).length > 0) {
        isOneFound = true;

        // To make `default()` function independent of definition order we have two "subpasses" here.
        // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
        // and build candidate list with corresponding flags. Then, when we know all possible matches,
        // we make a final decision.

        for (m = 0; m < mixins.length; m++) {
            mixin = mixins[m].rule;
            mixinPath = mixins[m].path;
            isRecursive = false;
            for (f = 0; f < context.frames.length; f++) {
                if ((!(mixin instanceof MixinDefinition)) && mixin === (context.frames[f].originalRuleset || context.frames[f])) {
                    isRecursive = true;
                    break;
                }
            }
            if (isRecursive) {
                continue;
            }

            if (mixin.matchArgs(args, context)) {
                candidate = {mixin: mixin, group: calcDefGroup(mixin, mixinPath)};

                if (candidate.group !== defFalseEitherCase) {
                    candidates.push(candidate);
                }

                match = true;
            }
        }

        defaultFunc.reset();

        count = [0, 0, 0];
        for (m = 0; m < candidates.length; m++) {
            count[candidates[m].group]++;
        }

        if (count[defNone] > 0) {
            defaultResult = defFalse;
        } else {
            defaultResult = defTrue;
            if ((count[defTrue] + count[defFalse]) > 1) {
                throw { type: 'Runtime',
                    message: 'Ambiguous use of `default()` found when matching for `' + this.format(args) + '`',
                    index: this.index, filename: this.currentFileInfo.filename };
            }
        }

        for (m = 0; m < candidates.length; m++) {
            candidate = candidates[m].group;
            if ((candidate === defNone) || (candidate === defaultResult)) {
                try {
                    mixin = candidates[m].mixin;
                    if (!(mixin instanceof MixinDefinition)) {
                        originalRuleset = mixin.originalRuleset || mixin;
                        mixin = new MixinDefinition("", [], mixin.rules, null, false, null, originalRuleset.visibilityInfo());
                        mixin.originalRuleset = originalRuleset;
                    }
                    var newRules = mixin.evalCall(context, args, this.important).rules;
                    this._setVisibilityToReplacement(newRules);
                    Array.prototype.push.apply(rules, newRules);
                } catch (e) {
                    throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
                }
            }
        }

        if (match) {
            return rules;
        }
    }
}
if (isOneFound) {
    throw { type:    'Runtime',
        message: 'No matching definition was found for `' + this.format(args) + '`',
        index:   this.index, filename: this.currentFileInfo.filename };
} else {
    throw { type:    'Name',
        message: this.selector.toCSS().trim() + " is undefined",
        index:   this.index, filename: this.currentFileInfo.filename };
}

};

MixinCall.prototype._setVisibilityToReplacement = function (replacement) {

var i, rule;
if (this.blocksVisibility()) {
    for (i = 0; i < replacement.length; i++) {
        rule = replacement[i];
        rule.addVisibilityBlock();
    }
}

}; MixinCall.prototype.format = function (args) {

return this.selector.toCSS().trim() + '(' +
    (args ? args.map(function (a) {
        var argValue = "";
        if (a.name) {
            argValue += a.name + ":";
        }
        if (a.value.toCSS) {
            argValue += a.value.toCSS();
        } else {
            argValue += "???";
        }
        return argValue;
    }).join(', ') : "") + ")";

}; module.exports = MixinCall;