# src/IndexedShadow.js

    import has from "lodash.has";
    import sortBy from "lodash.sortby";

    import Shadow from "./Shadow";

    const _cache = Symbol("_cache");
    const _valuesArray = Symbol('_valuesArray');

    /**
    	Default shadow api for the `IndexedProperty`. Exposes the non-mutating methods of the
    	{@link Array} interface.

    	@see {@link IndexedProperty}
    */
    export default class IndexedShadow extends Shadow {
    	constructor(impl) {
    		super(impl);

    		this[_cache] = {};
    	}

    	get length() {
    		return this.__().length;
    	}

    	//------------------------------------------------------------------------------------------------------
    	//	Read-only array methods
    	//------------------------------------------------------------------------------------------------------

    	/*
    		Alias for valuesArray().
    	*/
    	all() {
    		return this.valuesArray();
    	}

    	concat(...values) {
    		return this.valuesArray().concat(...values);
    	}

    	every(pred, context) {
    		for (let i=0, len=this.length; i<len; i++) {
    			if (!pred.call(context, this[i], i, this)) {
    				return false;
    			}
    		}

    		return true;
    	}

    	filter(callback, context) {
    		const acc = [];

    		for (let i=0, len=this.length; i<len; i++) {
    			if (callback.call(context, this[i], i, this)) {
    				acc.push(this[i]);
    			}
    		}

    		return acc;
    	}

    	find(pred, context) {
    		for (let i=0, len=this.length; i<len; i++) {
    			if (pred.call(context, this[i], i, this)) {
    				return this[i];
    			}
    		}
    	}

    	findIndex(pred, context) {
    		for (let i=0, len=this.length; i<len; i++) {
    			if (pred.call(context, this[i], i, this)) {
    				return i;
    			}
    		}

    		return -1;
    	}

    	forEach(callback, context) {
    		for (let i=0, len=this.length; i<len; i++) {
    			callback.call(context, this[i], i, this);
    		}
    	}

    	groupBy(callback, context) {
    		var result = { };

    		for (let i=0, len=this.length; i<len; i++) {
    			const prop = this[i];
    			const key = callback.call(context, prop);

    			if (has(result, key)) {
    				result[key].push(prop);
    			} else {
    				result[key] = [ prop ];
    			}
    		}

    		return result;
    	}

    	includes(searchElement, fromIndex=0) {
    		return this.indexOf(searchElement, fromIndex) != -1;
    	}

    	indexOf(value, fromIndex=0) {
    		const fromIdx = Math.min(fromIndex, this.length);

    		for (let i=fromIdx, len=this.length; i<len; i++) {
    			if (this[i] === value) {
    				return i;
    			}
    		}

    		return -1;
    	}

    	join(separator=',') {
    		return this.__().state().join(separator);
    	}

    	lastIndexOf(value, fromIndex=this.length-1) {
    		if (!this.length) { return -1; }

    		for (let i=Math.min(fromIndex, this.length-1); i>=0; i--) {
    			if (this[i] === value) {
    				return i;
    			}
    		}

    		return -1;
    	}

    	map(callback, context) {
    		const acc = [];

    		for (let i=0, len=this.length; i<len; i++) {
    			acc.push(callback.call(context, this[i], i, this));
    		}

    		return acc;
    	}

    	reduce(callback, acc, context) {
    		for (let i=0, len=this.length; i<len; i++) {
    			acc = callback.call(context, acc, this[i], i, this);
    		}

    		return acc;
    	}

    	some(pred, context) {
    		for (let i=0, len=this.length; i<len; i++) {
    			if (pred.call(context, this[i], i, this)) {
    				return true;
    			}
    		}

    		return false;
    	}

    	sort(callback) {
    		const values = this.valuesArray();

    		return values.sort(callback);
    	}

    	sortBy(...iteratee) {
    		const values = this.valuesArray();

    		return sortBy.apply(null, [values, ...iteratee]);
    	}

    	/*
    		The values() method returns a new Array Iterator object that contains the values for each index in the array.
    	*/
    	values() {
    		var i = 0;
    		const length = this.length;
    		const next = () => i<length ?{ value: this[i++], done: false } :{ done: true };

    		return {
    			next: next,
    			[Symbol.iterator]() { return { next: next } }
    		}
    	}

    	valuesArray() {
    		if (!this[_cache][_valuesArray]) {
    			var values = [];

    			for (let i=0, len=this.length; i<len; i++) {
    				values.push(this[i]);
    			}

    			this[_cache][_valuesArray] = values;
    		}

    		return this[_cache][_valuesArray];
    	}

    	[Symbol.iterator]() { return this.values() }
    }