Source: data/service/snapshot-service.js

var Montage = require("core/core").Montage,
    Map = require("collections/map");

/**
 * @class SnapshotService
 * @extends Montage
 */
exports.SnapshotService = Montage.specialize(/** @lends SnapshotService# */ {

    constructor: {
        value: function SnapshotService() {
            this._cache = new Map();
        }
    },

    _cache: {
        value: null
    },

    saveSnapshotForTypeNameAndId: {
        value: function (snapshot, typeName, id) {
            if (!this._cache.has(typeName)) {
                this._cache.set(typeName, new Map());
            }

            this._cache.get(typeName).set(id, this._getClone(snapshot));
        }
    },

    getSnapshotForTypeNameAndId: {
        value: function (typeName, id) {
            var result;
            if (this._cache.has(typeName)) {
                result = this._cache.get(typeName).get(id);
            }
            return result;
        }
    },

    removeSnapshotForTypeNameAndId: {
        value: function (typeName, id) {
            if (this._cache.has(typeName)) {
                this._cache.get(typeName).delete(id);
            }
        }
    },

    getDifferenceWithSnapshotForTypeNameAndId: {
        value: function (rawData, typeName, id) {
            var difference = this._getClone(rawData),
                cachedVersion, cachedKeys, key;
            if (this._cache.has(typeName)) {
                cachedVersion = this._cache.get(typeName).get(id);
            }
            if (cachedVersion) {
                cachedKeys = Object.keys(cachedVersion);
                for (var i = 0, length = cachedKeys.length; i < length; i++) {
                    key = cachedKeys[i];
                    if (this._areSameValues(rawData[key], cachedVersion[key])) {
                        delete difference[key];
                    }
                }
            }
            return difference;
        }
    },

    _getClone: {
        value: function (object) {
            var result, keys, key;
            /*, temp, j, arrayLength, arrayKeys*/
            if (object) {
                result = Object.create(null);
                keys = Object.keys(object);
                for (var i = 0, length = keys.length; i < length; i++) {
                    key = keys[i];
                    if (Array.isArray(object[key])) {
                        result[key] = this._getArrayClone(object[key]);
                    } else if (typeof object[key] === "object") {
                        result[key] = this._getClone(object[key]);
                    } else {
                        result[key] = object[key];
                    }
                }
            } else {
                result = object;
            }
            return result;
        }
    },

    _getArrayClone: {
        value: function (array) {
            var result = [],
                value;
            for (var i = 0, length = array.length; i < length; i++) {
                value = array[i];
                if (Array.isArray(value)) {
                    result.push(this._getArrayClone(value));
                } else if (typeof value === "object") {
                    result.push(this._getClone(value));
                } else {
                    result.push(value);
                }
            }
            return result;
        }
    },

    _areSameValues: {
        value: function(a, b) {
            var result = a === b;
            if (!result) {
                if (typeof a === "object" && typeof b === "object" &&
                    a !== null && b !== null) {
                    var aKeys = Object.keys(a).sort(), aValue,
                        bKeys = Object.keys(b).sort(), bValue,
                        key;

                    var aResult = aKeys.filter(function(x) { return a[x] !== null; }).length,
                        bResult = bKeys.filter(function(x) { return b[x] !== null;  }).length;

                    result = aResult === bResult;

                    if (result) {
                        for (var i = 0, length = bKeys.length; i < length; i++) {
                            key = bKeys[i];
                            aValue = a[key];
                            bValue = b[key];
                            if (!this._areSameValues(aValue, bValue)) {
                                result = false;
                                break;
                            }
                        }
                    }
                }
            }
            return result;
        }
    },

    _equals: {
        value: function (a, b) {
            return  typeof a === "object" && a !== null &&
                    typeof b === "object" && b !== null ? this._compareObjects(a, b) : a === b;
        }
    },

    _compareObjects: {
        value: function (a, b) {
            var aKeys = this._sortedNonNullKeysForObject(a),
                bKeys = this._sortedNonNullKeysForObject(b),
                areEqual = aKeys.length === bKeys.length,
                i, length, key;

            for (i = 0, length = bKeys.length; i < length && areEqual; i += 1) {
                key = bKeys[i];
                areEqual = this._equals(a[key], b[key]);
            }
            return areEqual;
        }
    },

    _sortedNonNullKeysForObject: {
        value: function (object) {
            return Object.keys(object).sort().filter(function (key) {
                return object[key] !== null;
            });
        }
    }
});