(function( $ ) {

    function filter(array, filters, logic)
    {
        var results = [];

        if(filters instanceof Function) filters = [filters];

        for(var i = 0 ; i < array.length ; i++)
        {
            var include = 0;

            for(var j = 0 ; j < filters.length ; j++)
            {
                if(filters[j](array[i]))
                {
                    include++;
                }
            }

            if(!logic && include == filters.length)
            {
                results.push(array[i]);
            }

            if(logic && include > 0)
            {
                results.push(array[i]);
            }
        }

        return results;
    }

    filter.AND = false;
    filter.OR = true;

    /* This is designed for internal use. Do not call directly. */
    filter.findField = function(field, object, appliedFilter, value) {
        var fieldList = field;

        while(fieldList.length > 1)
        {
            object = object[fieldList[0]];
            fieldList = fieldList.slice(1, fieldList.length);

            if(!object) return [];
            if(object instanceof Array) return filter(object, [appliedFilter(fieldList, value)], filter.AND);
        }

        return filter([object], [appliedFilter(fieldList[0], value)], filter.AND);
    }

    /*
     * Does a substring search on the specified field. The field can either be
     * a String or an array of Strings. For example, "systemName" would test the
     * each row for a property of "systemName". Whereas ["facilities", "facilityName"]
     * would first travel to the "facilities" property, then test the "facilityName"
     * property of the "facilities" object. If the "facilities" property is an
     * array value, each object in the array is tested for "facilityName". If ANY
     * values match, the row will be considered a match.
     */
    function contains(field, value)
    {
        value = value.toLowerCase();

        return function(row) {
            if(field instanceof Array)
            {
                return (filter.findField(field, row, contains, value).length > 0);
            }

            return (String(row[field]).toLowerCase().indexOf(value) >= 0);
        };
    }

    /*
     * Does an exact match search on the specified field. The field can either be
     * a String or an array of Strings. For example, "systemName" would test the
     * each row for a property of "systemName". Whereas ["facilities", "facilityName"]
     * would first travel to the "facilities" property, then test the "facilityName"
     * property of the "facilities" object. If the "facilities" property is an
     * array value, each object in the array is tested for "facilityName". If ANY
     * values match, the row will be considered a match.
     */
    function exact(field, value)
    {
        return function(row) {
            if(field instanceof Array)
            {
                return (filter.findField(field, row, exact, value).length > 0);
            }

            return (row[field] == value);
        };
    }

    function and(filters)
    {
        return function(row) {
            var match = true;

            $.each(filters, function(index, value) {
                match = match && value(row);
            });

            return match;
        };
    }

    function or(filters)
    {
        return function(row) {
            var match = false;

            $.each(filters, function(index, value) {
                match = match || value(row);
            });

            return match;
        };
    }

    var methods = {
        contains: contains,
        exact: exact,
        and: and,
        or: or
    }

    $.jsonFilter = function(arg1, arg2) {
        if(methods[arg1])
        {
            return methods[arg1].apply(this, Array.prototype.slice.call(arguments, 1));
        }
        else
        {
            if(!(arg1 instanceof Array)) $.error("First argument to jsonFilter must be an array of Javascript Objects.");

            return filter(arg1, arg2);
        }
    };
})(jQuery);
