/* arrays.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define(['util/functions'], function (Fn) {
	'use strict';

	/**
	 * Implements unique() using the browser's sort().
	 *
	 * @param a
	 *        The array to sort and strip of duplicate values.
	 *        Warning: this array will be modified in-place.
	 * @param compFn
	 *        A custom comparison function that accepts two values a and
	 *        b from the given array and returns -1, 0, 1 depending on
	 *        whether a < b, a == b, a > b respectively.
	 *
	 *        If no compFn is provided, the algorithm will use the
	 *        browsers default sort behaviour and loose comparison to
	 *        detect duplicates.
	 * @return
	 *        The given array.
	 */
	function sortUnique(a, compFn) {
		var i;
		if (compFn) {
			a.sort(compFn);
			for (i = 1; i < a.length; i++) {
				if (0 === compFn(a[i], a[i - 1])) {
					a.splice(i--, 1);
				}
			}
		} else {
			a.sort();
			for (i = 1; i < a.length; i++) {
				// Use loosely typed comparsion if no compFn is given
				// to avoid sortUnique([6, "6", 6]) => [6, "6", 6]
				if (a[i] == a[i - 1]) {
					a.splice(i--, 1);
				}
			}
		}
		return a;
	}

	/**
	 * Shallow comparison of two arrays.
	 *
	 * @param a, b
	 *        The arrays to compare.
	 * @param equalFn
	 *        A custom comparison function that accepts two values a and
	 *        b from the given arrays and returns true or false for
	 *        equal and not equal respectively.
	 *
	 *        If no equalFn is provided, the algorithm will use the strict
	 *        equals operator.
	 * @return
	 *        True if all items in a and b are equal, false if not.
	 */
	function equal(a, b, equalFn) {
		var i,
			len = a.length;
		if (len !== b.length) {
			return false;
		}
		if (equalFn) {
			for (i = 0; i < len; i++) {
				if (!equalFn(a[i], b[i])) {
					return false;
				}
			}
		} else {
			for (i = 0; i < len; i++) {
				if (a[i] !== b[i]) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Emulates ECMAScript edition 5 Arrays.map
	 * See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
	 * And http://es5.github.com/#x15.4.4.19
	 * It's not exactly according to standard, but it does what one expects.
	 */
	function map(a, fn) {
		var i, len, result = [];
		for (i = 0, len = a.length; i < len; i++) {
			result.push(fn(a[i]));
		}
		return result;
	}

	function mapNative(a, fn) {
		// Call map directly on the object instead of going through
		// Array.prototype.map. This avoids the problem that we may get
		// passed an array-like object (NodeList) which may cause an
		// error if the implementation of Array.prototype.map can only
		// deal with arrays (Array.prototype.map may be native or
		// provided by a javscript framework).
		return a.map(fn);
	}

	/**
	 * Returns a new array that contains all values in the given a for
	 * which pred returns true.
	 */
	function filter(a, pred) {
		var i,
		    len,
		    value,
		    result = [];
		for (i = 0, len = a.length; i < len; i++) {
			value = a[i];
			if (pred(value)) {
				result.push(value);
			}
		}
		return result;
	}

	/**
	 * Finds a value in the given array.
	 * Strict comparison is used to find the value.
	 * Returns the index of the first occurrence of the given value in
	 * the given a, or -1 if a contains no such value.
	 */
	function indexOf(a, value) {
		var i,
		    len;
		for (i = 0, len = a.length; i < len; i++) {
			if (value === a[i]) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Reduces an array of values to a single value.
	 *
	 * For example:
	 * Arrays.reduce([2, 3, 4], 1, function (a, b) { return a + b; });
	 * returns the result of (((1 + 2) + 3) + 4)
	 *
	 * Emulates ECMAScript edition 5 Array.reduce.
	 *
	 * @param a
	 *        An array of values.
	 * @param init
	 *        An initial value.
	 * @param fn
	 *        A function that takes two values and returns the reduction
	 *        of both.
	 */
	function reduce(a, init, fn) {
		var i,
		    len;
		for (i = 0, len = a.length; i < len; i++) {
			init = fn(init, a[i]);
		}
		return init;
	}

	/**
	 * Returns true if the given xs contains the given x.
	 */
	function contains(xs, x) {
		return -1 !== indexOf(xs, x);
	}

	/**
	 * Applies the given value to the given function unless the value is
	 * null, in which case just returns null.
	 *
	 * This is a utility function to be used with reduce().
	 */
	function applyNotNull(value, fn) {
		return value == null ? null : fn(value);
	}

	/**
	 * For each item in xs, call cb(item, index, xs).
	 *
	 * Emulates ECMAScript edition 5 Array.forEach.
	 */
	function forEach(xs, cb) {
		var i,
		    len;
		for (i = 0, len = xs.length; i < len; i++) {
			cb(xs[i], i, xs);
		}
	}

	/**
	 * Returns true if the given predicate function returns true for at
	 * least one item.
	 *
	 * Emulates ECMAScript edition 5 Array.some.
	 */
	function some(xs, pred) {
		var i,
		    len;
		for (i = 0, len = xs.length; i < len; i++) {
			if (pred(xs[i])) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns true if the given predicate function returns true for all
	 * items in xs.
	 *
	 * Emulates ECMAScript edition 5 Array.every.
	 */
	function every(xs, pred) {
		return !some(xs, Fn.complement(pred));
	}

	/**
	 * Returns concatenation of two arrays.
	 * @param {Array} xs
	 * @param {Array} zx
	 * @returns {Array}
	 */
	function concat(xs, zx) {
		return xs.concat(zx);
	}

	/**
	 * Returns all items in xs that are also contained in zs.
	 */
	function intersect(xs, zs) {
		return filter(xs, function (x) {
			return contains(zs, x);
		});
	}

	/**
	 * Returns all items in xs that are not contained in zs.
	 */
	function subtract(xs, zs) {
		return filter(xs, function (x) {
			return !contains(zs, x);
		});
	}

	/**
	 * Returns the last item in xs or null.
	 */
	function last(xs) {
		return xs.length ? xs[xs.length - 1] : null;
	}

	/**
	 * Returns the second item in xs.
	 */
	function second(xs) {
		return xs[1];
	}

	/**
	 * Coerces the given object (NodeList, arguments) to an array.
	 * @param  {*} list
	 * @return {Array}
	 */
	function coerce(list) {
		var i;
		var len;
		var resultArray = [];

		for (i = 0, len = list.length; i < len; i++) {
			resultArray.push(list[i]);
		}

		return resultArray;
	}

	/**
	 * Checks if `array` is empty.
	 * @param {Array.<*>} array
	 * @return {boolean}
	 */
	function isEmpty(array) {
		return array.length === 0;
	}

	return {
		filter: filter,
		indexOf: indexOf,
		reduce: reduce,
		forEach: forEach,
		some: some,
		every: every,
		map: Array.prototype.map ? mapNative : map,
		contains: contains,
		equal: equal,
		applyNotNull: applyNotNull,
		sortUnique: sortUnique,
		intersect: intersect,
		subtract: subtract,
		second: second,
		last: last,
		coerce: coerce,
		isEmpty: isEmpty,
		concat: concat
	};
});
