# Source: core/serialization/deserializer/montage-interpreter.js

    var Montage = require("../../core").Montage,
        MontageReviver = require("./montage-reviver").MontageReviver,
        Promise = require("../../promise").Promise,
        deprecate = require("../../deprecate"),
        ONE_ASSIGNMENT = "=",
        ONE_WAY = "<-",
        TWO_WAY = "<->";

    /**
     * @deprecated
     */
    var MontageInterpreter = Montage.specialize({
        _require: {value: null},
        _reviver: {value: null},

        init: {
            value: function (_require, reviver) {
                deprecate.deprecationWarningOnce("MontageInterpreter", "MontageDeserializer");
                if (typeof _require !== "function") {
                    throw new Error("Function 'require' missing.");
                }

                this._reviver = reviver;
                this._require = _require;

                return this;
            }
        },

        instantiate: {
            value: function (serialization, objects, element) {
                var context;

                context = new exports.MontageContext()
                    .init(serialization, this._reviver, objects, element, this._require);

                return context.getObjects();
            }
        },

        preloadModules: {
            value: function (serialization) {
                var reviver = this._reviver,
                    moduleLoader = reviver.moduleLoader,
                    object,
                    locationId,
                    locationDesc,
                    module,
                    promises = [];

                for (var label in serialization) {
                    if (serialization.hasOwnProperty(label)) {
                        object = serialization[label];
                        locationId = object.prototype || object.object;

                        if (locationId) {
                            if (typeof locationId !== "string") {
                                throw new Error(
                                    "Property 'object' of the object with the label '" +
                                    label + "' must be a module id"
                                );
                            }
                            locationDesc = MontageReviver.parseObjectLocationId(locationId);
                            module = moduleLoader.getModule(
                                locationDesc.moduleId, label);
                            if (Promise.is(module)) {
                                promises.push(module);
                            }
                        }
                    }
                }

                if (promises.length > 0) {
                    return Promise.all(promises);
                }
            }
        }
    });

    var MontageContext = Montage.specialize({
        _ELEMENT_ID_ATTRIBUTE: {value: "data-montage-id"},
        _unitsToDeserialize: {value: null},
        _element: {value: null},
        _require: {value: null},
        _objects: {value: null},
        _userObjects: {value: null},
        _serialization: {value: null},
        _reviver: { value: null },
        _bindingsToDeserialize: { value: null },

        constructor: {
            value: function () {
                this._unitsToDeserialize = [];
            }
        },

        init: {
            value: function (serialization, reviver, objects, element, _require) {
                this._reviver = reviver;
                this._serialization = serialization;
                this._objects = Object.create(null);

                if (objects) {
                    this._userObjects = Object.create(null);

                    /* jshint forin: true */
                    for (var label in objects) {
                    /* jshint forin: false */
                        this._userObjects[label] = objects[label];
                    }
                }

                this._element = element;
                this._require = _require;

                return this;
            }
        },

        setObjectLabel: {
            value: function(object, label) {
                this._objects[label] = object;
            }
        },

        getObject: {
            value: function(label) {
                var serialization = this._serialization,
                    reviver = this._reviver,
                    objects = this._objects,
                    object;

                if (label in objects) {
                    return objects[label];
                } else if (label in serialization) {
                    object = reviver.reviveRootObject(serialization[label], this, label);
                    // If no object has been set by the reviver we safe its
                    // return, it could be a value or a promise, we need to
                    // make sure the object won't be revived twice.
                    if (!(label in objects)) {
                        objects[label] = object;
                    }

                    return object;
                } else {
                    return Promise.reject(
                        new Error("Object with label '" + label + "' was not found.")
                    );
                }
            }
        },

        getObjects: {
            value: function() {
                var self = this,
                    serialization = this._serialization,
                    promises = [],
                    result;

                for (var label in serialization) {
                    if (serialization.hasOwnProperty(label)) {
                        result = this.getObject(label);

                        if (Promise.is(result)) {
                            promises.push(result);
                        }
                    }
                }

                if (promises.length === 0) {
                    return Promise.resolve(this._invokeDidReviveObjects());
                } else {
                    return Promise.all(promises).then(function() {
                        return self._invokeDidReviveObjects();
                    });
                }
            }
        },

        hasUserObject: {
            value: function(label) {
                var userObjects = this._userObjects;

                if (userObjects) {
                    return label in userObjects;
                } else {
                    return false;
                }
            }
        },

        getUserObject: {
            value: function(label) {
                var userObjects = this._userObjects;

                if (userObjects) {
                    return userObjects[label];
                }
            }
        },

        _invokeDidReviveObjects: {
            value: function() {
                var self = this,
                    reviver = this._reviver,
                    result;

                if (typeof reviver.didReviveObjects === "function") {
                    result = reviver.didReviveObjects(this._objects, this);
                    if (Promise.is(result)) {
                        return result.then(function() {
                            return self._objects;
                        });
                    }
                }

                return this._objects;
            }
        },

        hasObject: {
            value: function (label) {
                return label in this._serialization;
            }
        },

        getRequire: {
            value: function () {
                return this._require;
            }
        },

        getElement: {
            value: function () {
                return this._element;
            }
        },

        getElementById: {
            value: function (id) {
                var selector = '*[' + this._ELEMENT_ID_ATTRIBUTE + '="' + id + '"]';

                return this._element.querySelector(selector);
            }
        },

        _extractBindingsToDeserialize: {
            value: function (values, bindings) {
                var value;

                for (var key in values) {
                    if (values.hasOwnProperty(key)) {
                        value = values[key];

                        if ((typeof value === "object" && value &&
                            Object.keys(value).length === 1 &&
                            (ONE_WAY in value || TWO_WAY in value || ONE_ASSIGNMENT in value)) ||
                            key.indexOf('.') > -1
                        ) {
                            bindings[key] = value;
                            delete values[key];
                        }
                    }
                }

                return bindings;
            }
        },

        getBindingsToDeserialize: {
            value: function () {
                return this._bindingsToDeserialize;
            }
        },

        setBindingsToDeserialize: {
            value: function (object, objectDesc) {
                var bindings = Object.create(null);

                if (objectDesc.values) {
                    this._extractBindingsToDeserialize(objectDesc.values, bindings);
                } else if (objectDesc.properties) { // deprecated
                    this._extractBindingsToDeserialize(objectDesc.properties, bindings);
                }

                if (Object.keys(bindings).length > 0) {
                    if (!this._bindingsToDeserialize) {
                        this._bindingsToDeserialize = [];
                    }

                    this._bindingsToDeserialize.push({
                        object: object,
                        bindings: bindings
                    });
                }
            }
        },

        setUnitsToDeserialize: {
            value: function (object, objectDesc, unitNames) {
                this._unitsToDeserialize.push({
                    object: object,
                    objectDesc: objectDesc,
                    unitNames: unitNames
                });
            }
        },

        getUnitsToDeserialize: {
            value: function () {
                return this._unitsToDeserialize;
            }
        }
    });

    exports.MontageInterpreter = MontageInterpreter;
    exports.MontageContext = MontageContext;