/*
	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,
    existsOne   = DomUtils.existsOne,
    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) && existsOne(func, getChildren(elem));
	};
};
