/**
 * Underscore mixins for deep objects
 *
 * Based on https://gist.github.com/echong/3861963
 */
define(
    [
        'underscore'
    ], function (_) {

        var arrays, basicObjects, deepClone, deepExtend, deepExtendCouple, isBasicObject, __slice =
            [
            ].slice;

        deepClone = function (obj) {
            var func, isArr;
            if (!_.isObject(obj) || _.isFunction(obj)) {
                return obj;
            }
            if (obj instanceof Backbone.Collection || obj instanceof Backbone.Model) {
                return obj;
            }
            if (_.isDate(obj)) {
                return new Date(obj.getTime());
            }
            if (_.isRegExp(obj)) {
                return new RegExp(obj.source, obj.toString().replace(/.*\//, ''));
            }
            isArr = _.isArray(obj || _.isArguments(obj));
            func = function (memo, value, key) {
                if (isArr) {
                    memo.push(deepClone(value));
                }
                else {
                    memo[key] = deepClone(value);
                }
                return memo;
            };
            return _.reduce(obj, func, isArr ?
                [
                ] :{});
        };

        isBasicObject = function (object) {
            if (object == null) {
                return false;
            }
            return (object.prototype === {}.prototype || object.prototype === Object.prototype) && _.isObject(object) && !_.isArray(object) && !_.isFunction(object) && !_.isDate(object) && !_.isRegExp(object) && !_.isArguments(object);
        };

        basicObjects = function (object) {
            return _.filter(_.keys(object), function (key) {
                return isBasicObject(object[key]);
            });
        };

        arrays = function (object) {
            return _.filter(_.keys(object), function (key) {
                return _.isArray(object[key]);
            });
        };

        deepExtendCouple = function (destination, source, maxDepth) {
            var combine, recurse, sharedArrayKey, sharedArrayKeys, sharedObjectKey, sharedObjectKeys, _i, _j, _len, _len1;
            if (maxDepth == null) {
                maxDepth = 20;
            }
            if (maxDepth <= 0) {
                console.warn('_.deepExtend(): Maximum depth of recursion hit.');
                return _.extend(destination, source);
            }
            sharedObjectKeys = _.intersection(basicObjects(destination), basicObjects(source));
            recurse = function (key) {
                return source[key] = deepExtendCouple(destination[key], source[key], maxDepth - 1);
            };
            for (_i = 0, _len = sharedObjectKeys.length; _i < _len; _i++) {
                sharedObjectKey = sharedObjectKeys[_i];
                recurse(sharedObjectKey);
            }
            sharedArrayKeys = _.intersection(arrays(destination), arrays(source));
            combine = function (key) {
                return source[key] = _.union(destination[key], source[key]);
            };
            for (_j = 0, _len1 = sharedArrayKeys.length; _j < _len1; _j++) {
                sharedArrayKey = sharedArrayKeys[_j];
                combine(sharedArrayKey);
            }
            return _.extend(destination, source);
        };

        deepExtend = function () {
            var finalObj, maxDepth, objects, _i;
            objects = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) :(_i = 0,
                [
                ]), maxDepth = arguments[_i++];
            if (!_.isNumber(maxDepth)) {
                objects.push(maxDepth);
                maxDepth = 20;
            }
            if (objects.length <= 1) {
                return objects[0];
            }
            if (maxDepth <= 0) {
                return _.extend.apply(this, objects);
            }
            finalObj = objects.shift();
            while (objects.length > 0) {
                finalObj = deepExtendCouple(finalObj, deepClone(objects.shift()), maxDepth);
            }
            return finalObj;
        };


        _.mixin({
            deepClone    : deepClone,
            isBasicObject: isBasicObject,
            basicObjects : basicObjects,
            arrays       : arrays,
            deepExtend   : deepExtend
        });
    });