# Source: core/criteria.js

    var Montage = require("./core").Montage;

    var parse = require("frb/parse"),
        stringify = require("frb/stringify"),
        evaluate = require("frb/evaluate"),
        precedence = require("frb/language").precedence,
        Scope = require("frb/scope"),
        compile = require("frb/compile-evaluator");

    var Criteria = exports.Criteria = Montage.specialize({
        _expression: {
            value: null
        },

        /**
         * returns Criteria's expression, which is not expected to change after being
         * initialized
         *
         * @type {string}
         */
        expression: {
            get: function() {
                return this._expression;
            }
        },
        /**
         * @type {object}
         */
        parameters: {
            value: null
        },
        /**
         * @private
         * @type {object}
         */
        _syntax: {
            value: null
        },
        /**
         * The parsed expression, a syntactic tree.
         *
         * @type {object}
         */
       syntax: {
            get: function() {
                return this._syntax || (this._syntax = parse(this._expression));
            }
        },
        _compiledSyntax: {
            value: null
        },
        /**
         * The compiled expression, a function, that is used directly for evaluation.
         *
         * @type {function}
         */
       compiledSyntax: {
            get: function() {
                return this._compiledSyntax || (this._compiledSyntax = compile(this.syntax));
            }
        },

        /**
         * Initialize a Criteria with a compiled syntax.
         *
         * @method
         * @returns {Criteria} - The Criteria initialized.
         */
        initWithSyntax: {
            value: function (syntax, parameters) {
                this._syntax = syntax;
                this.parameters = parameters;
                return this;
            }
        },

        /**
         * Initialize a Criteria with an expression as string representation
         *
         * for example expression: "(firstName= $firstName) && (lastName = $lastName)"
         *             parameters: {
         *                  "firstName": "Han",
         *                  "lastName": "Solo"
         *             }
         *
         * @method
         * @argument {string} expression - A string representaton of the criteria
         *                                  expected to be a valid Montage expression.
         * @argument {object} parameters - Optional object containing value for an expressions' prameters
         *
         * @returns {Criteria} - The Criteria initialized.
         */
        initWithExpression: {
            value: function (expression,parameters) {
                this._expression = expression;
                this.parameters = parameters;
                return this;
            }
        },

        /**
         * Backward compatibility with selector.js
         *
         * @type {function}
         */
       initWithPath: {
            value: function (path) {
                return this.initWithExpression(path);
            }
        },

        criteriaWithParameters: {
            value: function (parameters) {
                var clone = (new this.constructor).initWithExpression(this.expression);
                clone.parameters = parameters;
                return clone;
            }
        },

        serializeSelf: {
            value: function (serializer) {
                serializer.setProperty("expression", this._expression || (this._expression = stringify(this.syntax)));
                serializer.setProperty("parameters", this.parameters);
            }
        },

        deserializeSelf: {
            value: function (deserializer) {
                var value;
                value = deserializer.getProperty("expression") || deserializer.getProperty("path");
                if (value !== void 0) {
                    this._expression = value;
                }
                value = deserializer.getProperty("parameters");
                if (value !== void 0) {
                    this.parameters = value;
                }
            }
        },
        __scope: {
            value: null
        },
        _scope: {
            get: function() {
                return this.__scope || (this.__scope = new Scope());
            }
        },
        evaluate: {
            value: function (value, parameters) {
                this._scope.parameters = parameters || this.parameters;
                this._scope.value = value;
                return this.compiledSyntax(this._scope);
            }
        }

    },{
        forObjectsLike: {
            value: function(object) {
                var properties = Object.keys(object),
                    expression = "",
                    i, iKey, iValue,
                    j, jValue, jExpression, jKey;

                for(i=0;(iKey = properties[i]);i++) {
                    iValue = object[iKey];
                    if(Array.isArray(iValue)) {
                        jExpression = "";

                        for(j=0;(jValue = iValue[j]);j++) {
                            jKey = iKey;
                            jKey += j;

                            if(jExpression.length > 0) {
                                jExpression += " && ";
                            }
                            jExpression += iKey;
                            jExpression += ".has($";
                            jExpression += jKey;
                            jExpression += ")";

                            //Now alias the value on object;
                            object[jKey] = jValue;
                        }

                        if(expression.length > 0) {
                            expression += " && ";
                        }
                        expression += jExpression;
                    }
                    else {
                        if(expression.length > 0) {
                            expression += " && ";
                        }

                        expression += iKey;
                        expression += "== $";
                        expression += iKey;
                    }
                }

                return (new this).initWithExpression(expression,object);
            }
        },

        withExpression: {
            value: function(expression,parameters) {
                return (new this).initWithExpression(expression,parameters);
            }
        },
        withSyntax: {
            value: function(syntax,parameters) {
                return (new this).initWithSyntax(syntax,parameters);
            }
        }

    });

    // generate methods on Criteria for each of the tokens of the language.
    // support invocation both as class and instance methods like
    // Selector.and("a", "b") and aSelector.and("b")
    precedence.forEach(function (value,type, precedence) {
        Montage.defineProperty(Criteria.prototype, type, {
            value: function () {
                var args = Array.prototype.map.call(arguments, function (argument) {
                    if (typeof argument === "string") {
                        return parse(argument);
                    } else if (argument.syntax) {
                        return argument.syntax;
                    } else if (typeof argument === "object") {
                        return argument;
                    }
                });
                // invoked as instance method
                return new (this.constructor)().initWithSyntax({
                    type: type,
                    args: [this.syntax].concat(args)
                });
            }
        });
        Montage.defineProperty(Criteria, type, {
            value: function () {
                var args = Array.prototype.map.call(arguments, function (argument) {
                    if (typeof argument === "string") {
                        return parse(argument);
                    } else if (argument.syntax) {
                        return argument.syntax;
                    } else if (typeof argument === "object") {
                        return argument;
                    }
                });
                // invoked as class method
                return new this().initWithSyntax({
                    type: type,
                    args: args
                });
            }
        });
    });