# 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;
                });
            }
        }
    });