Source: core/meta/module-object-descriptor.js

var Montage = require("../core").Montage,
    Promise = require("../promise").Promise,
    ObjectDescriptor = require("./object-descriptor").ObjectDescriptor,
    Deserializer = require("../serialization/deserializer/montage-deserializer").MontageDeserializer,
    ModuleReference = require("../module-reference").ModuleReference,
    deprecate = require("../deprecate");


// Adapted from mr/sandbox
function getModuleRequire(parentRequire, moduleId) {
    var topId = parentRequire.resolve(moduleId);
    var module = parentRequire.getModuleDescriptor(topId);

    while (module.redirect || module.mappingRedirect) {
        if (module.redirect) {
            topId = module.redirect;
        } else {
            parentRequire = module.mappingRequire;
            topId = module.mappingRedirect;
        }
        module = parentRequire.getModuleDescriptor(topId);
    }

    return module.require;
}

// Cache all loaded object descriptors
var OBJECT_DESCRIPTOR_CACHE = Object.create(null);

/**
 * @class ModuleObjectDescriptor
 * @extends ObjectDescriptor
 */
var ModuleObjectDescriptor = exports.ModuleObjectDescriptor = ObjectDescriptor.specialize(/** @lends ModuleObjectDescriptor# */ {

    /**
     * @function
     * @param {ModuleReference} module
     * @param {string} name
     * @returns this
     */
    initWithModuleAndExportName: {
        value: function (module, exportName) {
            var self = ObjectDescriptor.prototype.initWithName.call(this, exportName);
            self.module = module;
            self.exportName = exportName;
            return self;
        }
    },

    serializeSelf: {
        value: function (serializer) {
            if (!this.module) {
                throw new Error("Cannot serialize object descriptor without a module reference");
            }
            if (!this.exportName) {
                throw new Error("Cannot serialize object descriptor without an exportName");
            }

            this.super(serializer);
            this._setPropertyWithDefaults(serializer, "module", this.module);
            this._setPropertyWithDefaults(serializer, "exportName", this.exportName);
        }
    },

    deserializeSelf: {
        value: function (deserializer) {
            this.super(deserializer);
            var value;
            value = deserializer.getProperty("module");
            if (value !== void 0) {
                this.module = value;
            }
            value = deserializer.getProperty("exportName") || this.exportName;
            if (value !== void 0) {
                this.exportName = value;
            }

            if (!this.module) {
                throw new Error("Cannot deserialize object descriptor without a module reference");
            }
            if (!this.exportName) {
                throw new Error("Cannot deserialize object descriptor without an exportName");
            }
        }
    },

    /**
     * A reference to the module that this object descriptor is for.
     * @type {ModuleReference}
     */
    module: {
        value: null
    },

    /**
     * The name of the export this object descriptor is for.
     * @type {string}
     */
    exportName: {
        value: null
    },

    objectDescriptorInstanceModule: {
        serializable: false,
        value: null
    }

}, /** @lends ModuleObjectDescriptor. */ {

    /**
     * Gets an object descriptor from a serialized file at the given module id.
     * @function
     * @param {string} object descriptor module id
     * @param {function} require function
     */
    getObjectDescriptorWithModuleId: {
        value: function (moduleId, _require) {
            if (moduleId.search(/\.meta$/) === -1 && moduleId.search(/\.mjson$/) === -1) {
                throw new Error(moduleId + " object descriptor module id does not end in '.meta' or '.mjson'");
            }
            if (!_require) {
                throw new Error("Require needed to get object descriptor " + moduleId);
            }

            var targetRequire;

            var key = _require.location + "#" + moduleId;
            if (key in OBJECT_DESCRIPTOR_CACHE) {
                return OBJECT_DESCRIPTOR_CACHE[key];
            }

            return (OBJECT_DESCRIPTOR_CACHE[key] = _require.async(moduleId)
                .then(function (object) {
                    // Need to get the require from the module, because thats
                    // what all the moduleId references are relative to.
                    targetRequire = Deserializer.getModuleRequire(_require, moduleId);
                    return new Deserializer().init(JSON.stringify(object), targetRequire).deserializeObject();
                }).then(function (objectDescriptor) {

                    // TODO: May want to relax this to being just an Object Descriptor
                    if (!ModuleObjectDescriptor.prototype.isPrototypeOf(objectDescriptor)) {
                        throw new Error("Object in " + moduleId + " is not a module-object-descriptor");
                    }

                    objectDescriptor.objectDescriptorInstanceModule = new ModuleReference().initWithIdAndRequire(moduleId, _require);

                    if (objectDescriptor._parentReference) {
                        // Load parent "synchronously" so that all the properties
                        // through the object descriptor chain are available
                        return objectDescriptor._parentReference.promise(targetRequire) // MARK
                            .then(function (parentObjectDescriptor) {
                                objectDescriptor._parent = parentObjectDescriptor;
                                return objectDescriptor;
                            });
                    }

                    return objectDescriptor;
                }));
        }
    },

    createDefaultObjectDescriptorForObject: {
        value: function (object) {
            var target = Montage.getInfoForObject(object).isInstance ? Object.getPrototypeOf(object) : object;
            var info = Montage.getInfoForObject(target);
            if (!info.objectName || !info.moduleId) {
                return Promise.reject("Cannot create module-object-descriptor for an object that has no been loaded from a module");
            }

            return this.super(object)
                .then(function (objectDescriptor) {
                    objectDescriptor.module = new ModuleReference().initWithIdAndRequire(info.moduleId, info.require);
                    objectDescriptor.exportName = info.objectName;
                    return objectDescriptor;
                });
        }
    },

    /***************************************************************
     * Deprecated methods.
     */

    /**
     * Gets an object descriptor from a serialized file at the given module id.
     * @deprecated
     * @function
     * @param {string} object descriptor module id
     * @param {function} require function
     */
    getBlueprintWithModuleId: {
        value: deprecate.deprecateMethod(void 0, function (moduleId, _require) {
            return ModuleObjectDescriptor.getObjectDescriptorWithModuleId(moduleId, _require);
        }, "ModuleBlueprint.getBlueprintWithModuleId", "ModuleObjectDescriptor.getObjectDescriptorWithModuleId")
    },

    /**
     * @deprecated
     */
    createDefaultBlueprintForObject: {
        value: deprecate.deprecateMethod(void 0, function (object) {
            return ModuleObjectDescriptor.createDefaultObjectDescriptorForObject(object);
        }, "ModuleBlueprint.createDefaultBlueprintForObject", "ModuleObjectDescriptor.createDefaultObjectDescriptorForObject")
    }
});