(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "../core/TransientDisposable", "../core/types", "./Inject", "./ServiceCollection", "./TypeKey"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.ServiceProvider = void 0;
    const TransientDisposable_1 = require("../core/TransientDisposable");
    const types_1 = require("../core/types");
    const Inject_1 = require("./Inject");
    const ServiceCollection_1 = require("./ServiceCollection");
    const TypeKey_1 = require("./TypeKey");
    class ServiceProvider {
        constructor(parent) {
            this.parent = parent;
            this.instances = {};
            if (parent === null) {
                ServiceCollection_1.ServiceCollection.instance.registerScoped(ServiceProvider);
            }
            const sd = ServiceCollection_1.ServiceCollection.instance.get(ServiceProvider);
            this.instances[sd.id] = this;
        }
        get global() {
            return this.parent === null ? this : this.parent.global;
        }
        get(key) {
            return this.resolve(key, true);
        }
        put(key, value) {
            let sd = ServiceCollection_1.ServiceCollection.instance.get(key);
            if (!sd) {
                sd = ServiceCollection_1.ServiceCollection.instance.register(key, () => value, ServiceCollection_1.Scope.Global);
            }
            this.instances[sd.id] = value;
        }
        resolve(key, create = false, defValue) {
            const sd = ServiceCollection_1.ServiceCollection.instance.get(key);
            if (!sd) {
                if (!create) {
                    if (defValue !== undefined) {
                        return defValue;
                    }
                    throw new Error(`No service registered for type ${key}`);
                }
                return this.create(key);
            }
            if (sd.type === ServiceProvider) {
                return this;
            }
            if (sd.scope === ServiceCollection_1.Scope.Global) {
                return this.global.getValue(sd);
            }
            if (sd.scope === ServiceCollection_1.Scope.Scoped) {
                if (this.parent === null) {
                    throw new Error("Scoped dependency cannot be created on global");
                }
            }
            return this.getValue(sd);
        }
        getValue(sd) {
            if (sd.scope === ServiceCollection_1.Scope.Transient) {
                return sd.factory(this);
            }
            let v = this.instances[sd.id];
            if (!v) {
                this.instances[sd.id] = v = sd.factory(this);
            }
            return v;
        }
        newScope() {
            return new ServiceProvider(this);
        }
        dispose() {
            for (const key in this.instances) {
                if (this.instances.hasOwnProperty(key)) {
                    const element = this.instances[key];
                    if (element === this) {
                        continue;
                    }
                    const d = element;
                    if (d.dispose) {
                        d.dispose();
                    }
                }
            }
        }
        create(key) {
            const originalKey = key;
            const originalTypeKey = TypeKey_1.TypeKey.get(originalKey);
            if (types_1.DI.resolveType) {
                const mappedType = ServiceProvider.mappedTypes[originalTypeKey] || (ServiceProvider.mappedTypes[originalTypeKey] = types_1.DI.resolveType(originalKey));
                key = mappedType;
            }
            const typeKey1 = TypeKey_1.TypeKey.get(key);
            const plist = Inject_1.InjectedTypes.getParamList(key, typeKey1);
            let value = null;
            if (plist) {
                const pv = plist.map((x) => x ? this.resolve(x) : (void 0));
                pv.unshift(null);
                value = new (key.bind.apply(key, pv))();
                for (const iterator of pv) {
                    if (iterator && iterator instanceof TransientDisposable_1.default) {
                        iterator.registerIn(value);
                    }
                }
            }
            else {
                value = new (key)();
            }
            const propList = Inject_1.InjectedTypes.getPropertyList(key, typeKey1);
            if (propList) {
                for (const key1 in propList) {
                    if (propList.hasOwnProperty(key1)) {
                        const element = propList[key1];
                        const d = this.resolve(element);
                        value[key1] = d;
                        if (d && d instanceof TransientDisposable_1.default) {
                            d.registerIn(value);
                        }
                    }
                }
            }
            return value;
        }
    }
    exports.ServiceProvider = ServiceProvider;
    ServiceProvider.mappedTypes = {};
});
//# sourceMappingURL=ServiceProvider.js.map