/*
	compiles a selector to an executable function
*/

module.exports = compile;
module.exports.compileUnsafe = compileUnsafe;

var parse = require("CSSwhat"),
    DomUtils = require("domutils"),
    isTag = DomUtils.isTag,
    Rules = require("./general.js"),
    sortRules = require("./sort.js"),
    BaseFuncs = require("./basefunctions.js"),
    trueFunc = BaseFuncs.trueFunc,
    falseFunc = BaseFuncs.falseFunc;

function compile(selector, options) {
    var next = compileUnsafe(selector, options);

    return function base(elem) {
        return isTag(elem) && next(elem);
    };
}

function compileUnsafe(selector, options) {
    return parse(selector, options)
        .map(compileRules)
        .reduce(reduceRules, falseFunc);
}

function compileRules(arr) {
    if (arr.length === 0) return falseFunc;
    return sortRules(arr).reduce(function(func, rule) {
        if (func === falseFunc) return func;
        return Rules[rule.type](func, rule);
    }, trueFunc);
}

function reduceRules(a, b) {
    if (b === falseFunc || a === trueFunc) {
        return a;
    }
    if (a === falseFunc || b === trueFunc) {
        return b;
    }

    return function combine(elem) {
        return a(elem) || b(elem);
    };
}

//:not and :has have to compile selectors
//doing this in lib/pseudos.js would lead to circular dependencies,
//so we add them here

var Pseudos = require("./pseudos.js"),
    filters = Pseudos.filters,
    isParent = Pseudos.pseudos.parent,
    findOne = DomUtils.findOne,
    getChildren = DomUtils.getChildren;

filters.not = function(next, select) {
    var func = compile(select);

    if (func === falseFunc) return next;
    if (func === trueFunc) return falseFunc;

    return function(elem) {
        return !func(elem) && next(elem);
    };
};

filters.has = function(next, selector) {
    var func = compile(selector);

    if (func === falseFunc) return falseFunc;
    if (func === trueFunc)
        return function(elem) {
            return isParent(elem) && next(elem);
        };

    return function has(elem) {
        return next(elem) && findOne(func, getChildren(elem)) !== null;
    };
};

/*
	compiles a selector to an executable function
*/

module.exports = compile;
module.exports.compileUnsafe = compileUnsafe;

var parse = require("CSSwhat"),
    DomUtils = require("domutils"),
    isTag = DomUtils.isTag,
    Rules = require("./general.js"),
    sortRules = require("./sort.js"),
    BaseFuncs = require("./basefunctions.js"),
    trueFunc = BaseFuncs.trueFunc,
    falseFunc = BaseFuncs.falseFunc;

function compile(selector, options) {
    var next = compileUnsafe(selector, options);

    return function base(elem) {
        return isTag(elem) && next(elem);
    };
}

function compileUnsafe(selector, options) {
    return parse(selector, options)
        .map(compileRules)
        .reduce(reduceRules, falseFunc);
}

function compileRules(arr) {
    if (arr.length === 0) return falseFunc;
    return sortRules(arr).reduce(function(func, rule) {
        if (func === falseFunc) return func;
        return Rules[rule.type](func, rule);
    }, trueFunc);
}

function reduceRules(a, b) {
    if (b === falseFunc || a === trueFunc) {
        return a;
    }
    if (a === falseFunc || b === trueFunc) {
        return b;
    }

    return function combine(elem) {
        return a(elem) || b(elem);
    };
}

//:not and :has have to compile selectors
//doing this in lib/pseudos.js would lead to circular dependencies,
//so we add them here

var Pseudos = require("./pseudos.js"),
    filters = Pseudos.filters,
    isParent = Pseudos.pseudos.parent,
    findOne = DomUtils.findOne,
    getChildren = DomUtils.getChildren;

filters.not = function(next, select) {
    var func = compileUnsafe(select);

    if (func === falseFunc) return next;
    if (func === trueFunc) return falseFunc;

    return function(elem) {
        return !func(elem) && next(elem);
    };
};

filters.has = function(next, selector) {
    var func = compile(selector);

    if (func === falseFunc) return falseFunc;
    if (func === trueFunc)
        return function(elem) {
            return isParent(elem) && next(elem);
        };

    return function has(elem) {
        return next(elem) && findOne(func, getChildren(elem)) !== null;
    };
};

Use `compileUnsafe` in :not