# Source: documents/collector.js

    "use strict";
    /**
     @fileOverview An object and array collector
     @module ink/collector
     */

    var probe = require( "ink-probe" );
    var sys = require( "lodash" );
    var dcl = require( "dcl" );

    /**
     * A collector
     * @constructor
     */
    var CollectorBase = dcl( Destroyable, {
    	declaredClass : "CollectorBase",
    	constructor   : function ( obj ) {
    		var that = this;
    		if ( obj && !sys.isObject( obj ) ) {
    			throw new TypeError( "Collectors require an initial object or array passed to the constructor" );
    		}
    		/**
    		 * The collection that being managed
    		 * @type {object|array}
    		 */
    		this.heap = obj || {};
    		// mixin the probe
    		probe.mixTo( this, this.heap );
    		/**
    		 * Get the size of the collection
    		 * @name length
    		 * @type {number}
    		 * @memberOf module:documents/collector~CollectorBase#
    		 */
    		Object.defineProperty( this, "length", {
    				get : function () {
    					return sys.size( that.heap );
    				}
    			}
    		);
    		/**
    		 * Creates an array of shuffled array values, using a version of the Fisher-Yates shuffle.
    		 * See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
    		 * @function
    		 * @memberOf module:documents/collector~CollectorBase#
    		 * @returns {array}
    		 */
    		this.shuffle = sys.bind( sys.shuffle, this, this.heap );

    	},
    	/**
    	 * Adds an item to the collection
    	 * @param {*} key The key to use for the item being added.
    	 * @param {*} item The item to add to the collection. The item is not iterated so that you could add bundled items to the collection
    	 */
    	add           : function ( key, item ) {
    		this.heap[key] = item;
    	},
    	/**
    	 * Iterate over each item in the collection, or a subset that matches a query. This supports two signatures:
    	 * `.each(query, function)` and `.each(function)`. If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param {object=} query A query to evaluate
    	 * @param {function(val, key)} iterator Function to execute against each item in the collection
    	 * @param {object=} thisobj The value of `this`
    	 */
    	each          : function ( query, iterator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			sys.each( this.find( query ), iterator, thisobj );
    		} else {
    			thisobj = iterator || this;
    			sys.each( this.heap, query, thisobj );
    		}
    	},
    	/**
    	 * Returns the collection as an array. If it is already an array, it just returns that.
    	 * @return {array}
    	 */
    	toArray       : function () {
    		return sys.toArray( this.heap );
    	},
    	/**
    	 * Supports conversion to a JSON string or for passing over the wire
    	 * @return {object}
    	 * @returns {Object|array}
    	 */
    	toJSON        : function () {
    		return this.heap;
    	},
    	/**
    	 * Maps the contents to an array by iterating over it and transforming it. You supply the iterator. Supports two signatures:
    	 * `.map(query, function)` and `.map(function)`. If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param {object=} query A query to evaluate
    	 * @param {function(val, key)} iterator Function to execute against each item in the collection
    	 * @param {object=} thisobj The value of `this`
    	 */
    	map           : function ( query, iterator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			return sys.map( this.find( query ), iterator, thisobj );
    		} else {
    			thisobj = iterator || this;
    			return sys.map( this.heap, query, thisobj );
    		}
    	},
    	/**
    	 * Reduces a collection to a value which is the accumulated result of running each element in the collection through the
    	 * callback, where each successive callback execution consumes the return value of the previous execution. If accumulator
    	 * is not passed, the first element of the collection will be used as the initial accumulator value.
    	 * are iterated over.
    	 * @param {object=} query A query to evaluate
    	 * @param {function(result, val, key)} iterator The function that will be executed in each item in the collection
    	 * @param {*=} accumulator Initial value of the accumulator.
    	 * @param {object=} thisobj The value of `this`
    	 * @return {*}
    	 */
    	reduce        : function ( query, iterator, accumulator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			return sys.reduce( this.find( query ), iterator, accumulator, thisobj );
    		} else {
    			thisobj = accumulator || this;
    			return  sys.reduce( this.heap, query, iterator, thisobj );
    		}
    	},
    	/**
    	 * Creates an object composed of keys returned from running each element
    	 * of the collection through the given callback. The corresponding value of each key
    	 * is the number of times the key was returned by the callback.
    	 * @param {object=} query A query to evaluate. If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param  {function(value, key, collection)} iterator
    	 * @param {object=} thisobj The value of `this`
    	 * @return {object}
    	 */
    	countBy       : function ( query, iterator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			return sys.countBy( this.find( query ), iterator, thisobj );
    		} else {
    			thisobj = iterator || this;
    			return sys.countBy( this.heap, query, thisobj );
    		}
    	},
    	/**
    	 * Creates an object composed of keys returned from running each element of the collection through the callback.
    	 * The corresponding value of each key is an array of elements passed to callback that returned the key.
    	 * The callback is invoked with three arguments: (value, index|key, collection).
    	 * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param {function(value, key, collection)} iterator
    	 * @param {object=} thisobj The value of `this`
    	 * @return {object}
    	 */
    	groupBy       : function ( query, iterator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			return sys.groupBy( this.find( query ), iterator, thisobj );
    		} else {
    			thisobj = iterator || this;
    			return sys.groupBy( this.heap, query, thisobj );
    		}
    	},
    	/**
    	 * Reduce the collection to a single value. Supports two signatures:
    	 * `.pluck(query, function)` and `.pluck(function)`
    	 * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param {string} property The property that will be 'plucked' from the contents of the collection
    	 * @return {*}
    	 */
    	pluck         : function ( query, property ) {
    		if ( arguments.length === 2 ) {
    			return sys.map( this.find( query ), function ( record ) {
    				return probe.get( record, property );
    			} );
    		} else {
    			return sys.map( this.heap, function ( record ) {
    				return probe.get( record, query );
    			} );
    		}
    	},
    	/**
    	 * Returns a sorted copy of the collection.
    	 * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param {function(value, key)} iterator
    	 * @param {object=} thisobj The value of `this`
    	 * @return {array}
    	 */
    	sortBy        : function ( query, iterator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			return sys.sortBy( this.find( query ), iterator, thisobj );
    		} else {
    			thisobj = iterator || this;
    			return sys.sortBy( this.heap, query, thisobj );
    		}
    	},
    	/**
    	 * Retrieves the maximum value of an array. If callback is passed,
    	 * it will be executed for each value in the array to generate the criterion by which the value is ranked.
    	 * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param {function(value, key, collection)} iterator
    	 * @param {object=} thisobj The value of `this`
    	 * @return {number}
    	 */
    	max           : function ( query, iterator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			return sys.max( this.find( query ), iterator, thisobj );
    		} else {
    			thisobj = iterator || this;
    			return sys.max( this.heap, query, thisobj );
    		}
    	},
    	/**
    	 * Retrieves the minimum value of an array. If callback is passed,
    	 * it will be executed for each value in the array to generate the criterion by which the value is ranked.
    	 * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
    	 * are iterated over.
    	 * @param {function(value, key, collection)} iterator
    	 * @param {object=} thisobj The value of `this`
    	 * @return {number}
    	 */
    	min           : function ( query, iterator, thisobj ) {
    		if ( sys.isPlainObject( query ) ) {
    			thisobj = thisobj || this;
    			return sys.min( this.find( query ), iterator, thisobj );
    		} else {
    			thisobj = iterator || this;
    			return sys.min( this.heap, query, thisobj );
    		}
    	},
    	/**
    	 * Destructor called when the object is destroyed.
    	 */
    	destroy       : function () {
    		this.heap = null;
    	}
    } );

    /**
     * An object based collector
     * @extends module:documents/collector~CollectorBase
     * @constructor
     */
    var OCollector = dcl( CollectorBase, {
    	/**
    	 * Get a record by key
    	 * @param {*} key The key of the record to get
    	 * @return {*}
    	 */
    	key : function ( key ) {
    		return this.heap[key];
    	}
    } );

    //noinspection JSCommentMatchesSignature
    /**
     An array based collector
     @extends module:documents/collector~CollectorBase
     @constructor
     */
    var ACollector = dcl( CollectorBase, {
    		constructor : function ( obj ) {
    			if ( obj && !sys.isArray( obj ) ) {
    				throw new TypeError( "Collectors require an array passed to the constructor" );
    			}
    			this.heap = obj || [];
    			/**
    			 * Creates an array of array elements not present in the other arrays using strict equality for comparisons, i.e. ===.
    			 * @returns {array}
    			 */
    			this.difference = sys.bind( sys.difference, this, this.heap );
    			/**
    			 * This method gets all but the first values of array
    			 * @param {number=} n The numer of items to return
    			 * @returns {*}
    			 */
    			this.tail = sys.bind( sys.tail, this, this.heap );
    			/**
    			 * Gets the first n values of the array
    			 * @param {number=} n The numer of items to return
    			 * @returns {*}
    			 */
    			this.head = sys.bind( sys.head, this, this.heap );
    		},
    		/**
    		 * Adds to the top of the collection
    		 * @param {*} item The item to add to the collection. Only one item at a time can be added
    		 */
    		add         : function ( item ) {
    			this.heap.unshift( item );
    		},
    		/**
    		 * Add to the bottom of the list
    		 * @param {*} item The item to add to the collection.  Only one item at a time can be added
    		 */
    		append      : function ( item ) {
    			this.heap.push( item );
    		},
    		/**
    		 * Add an item to the top of the list. This is identical to `add`, but is provided for stack semantics
    		 * @param {*} item The item to add to the collection. Only one item at a time can be added
    		 */
    		push        : function ( item ) {
    			this.add( item );
    		},
    		/**
    		 * Modifies the collection with all falsey values of array removed. The values false, null, 0, "", undefined and NaN are all falsey.
    		 */
    		compact     : function () {
    			this.heap = sys.compact( this.heap );
    		},
    		/**
    		 * Creates an array of elements from the specified indexes, or keys, of the collection. Indexes may be specified as
    		 * individual arguments or as arrays of indexes
    		 * @param {indexes} args The indexes to use
    		 */
    		at          : function () {
    			var arr = sys.toArray( arguments );
    			arr.unshift( this.heap );
    			return sys.at.apply( this, arr );
    		},
    		/**
    		 * Flattens a nested array (the nesting can be to any depth). If isShallow is truthy, array will only be flattened a single level.
    		 * If callback is passed, each element of array is passed through a callback before flattening.
    		 * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
    		 * are iterated over.
    		 * @param {function(value, key, collection)} iterator,
    		 * @param {object=} thisobj The value of `this`
    		 * @return {number}
    		 */
    		flatten     : function ( query, iterator, thisobj ) {
    			if ( sys.isPlainObject( query ) ) {
    				thisobj = thisobj || this;
    				return sys.flatten( this.find( query ), iterator, thisobj );
    			} else {
    				thisobj = iterator || this;
    				return sys.flatten( this.heap, query, thisobj );
    			}
    		},
    		/**
    		 * Gets an items by its index
    		 * @param {number} key The index to get
    		 * @return {*}
    		 */
    		index       : function ( index ) {
    			return this.heap[ index ];
    		}
    	}
    );

    /**
     Collect an object
     @param {array|object} obj What to collect
     @return {ACollector|OCollector}
     */
    exports.collect = function ( obj ) {
    	if ( sys.isArray( obj ) ) {
    		return new ACollector( obj );
    	} else {
    		return new OCollector( obj );
    	}
    };

    exports.array = function ( obj ) {
    	return new ACollector( obj );
    };

    exports.object = function ( obj ) {
    	return new OCollector( obj );
    };

    /**
     Returns true if all items match the query. Aliases as `all`
     @function

     @param {object} qu The query to execute
     @returns {boolean}
     @name every
     @memberOf module:documents/collector~CollectorBase#
     */

    /**
     Returns true if any of the items match the query. Aliases as `any`
     @function

     @param {object} qu The query to execute
     @returns {boolean}
     @memberOf module:documents/collector~CollectorBase#
     @name some
     */

    /**
     Returns the set of unique records that match a query

     @param {object} qu The query to execute.
     @return {array}
     @memberOf module:documents/collector~CollectorBase#
     @name unique
     @method
     **/

    /**
     Returns true if all items match the query. Aliases as `every`
     @function

     @param {object} qu The query to execute
     @returns {boolean}
     @name all
     @memberOf module:documents/collector~CollectorBase#
     */

    /**
     Returns true if any of the items match the query. Aliases as `all`
     @function

     @param {object} qu The query to execute
     @returns {boolean}
     @memberOf module:documents/collector~CollectorBase#
     @name any
     */

    /**
     Remove all items in the object/array that match the query

     @param {object} qu The query to execute. See {@link module:ink/probe.queryOperators} for the operators you can use.
     @return {object|array} The array or object as appropriate without the records.
     @memberOf module:documents/collector~CollectorBase#
     @name remove
     @method
     **/

    /**
     Returns the first record that matches the query and returns its key or index depending on whether `obj` is an object or array respectively.
     Aliased as `seekKey`.

     @param {object} qu The query to execute.
     @returns {object}
     @memberOf module:documents/collector~CollectorBase#
     @name findOneKey
     @method
     */

    /**
     Returns the first record that matches the query. Aliased as `seek`.

     @param {object} qu The query to execute.
     @returns {object}
     @memberOf module:documents/collector~CollectorBase#
     @name findOne
     @method
     */

    /**
     Find all records that match a query and returns the keys for those items. This is similar to {@link module:ink/probe.find} but instead of returning
     records, returns the keys. If `obj` is an object it will return the hash key. If 'obj' is an array, it will return the index

     @param {object} qu The query to execute.
     @returns {array}
     @memberOf module:documents/collector~CollectorBase#
     @name findKeys
     @method
     */

    /**
     Find all records that match a query

     @param {object} qu The query to execute.
     @returns {array} The results
     @memberOf module:documents/collector~CollectorBase#
     @name find
     @method
     **/

    /**
     Updates all records in obj that match the query. See {@link module:ink/probe.updateOperators} for the operators that are supported.

     @param {object} qu The query which will be used to identify the records to updated
     @param {object} setDocument The update operator. See {@link module:ink/probe.updateOperators}
     @memberOf module:documents/collector~CollectorBase#
     @name update
     @method
     */