"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module RpcInterface
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.RpcRegistry = exports.CURRENT_INVOCATION = exports.CURRENT_REQUEST = exports.INSTANCE = exports.POLICY = exports.OPERATION = exports.REGISTRY = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const IModelError_1 = require("../../IModelError");
const RpcConfiguration_1 = require("./RpcConfiguration");
const RpcPendingQueue_1 = require("./RpcPendingQueue");
const RpcRequest_1 = require("./RpcRequest");
const RpcRoutingToken_1 = require("./RpcRoutingToken");
const RpcInterface_1 = require("../../RpcInterface");
const RpcControl_1 = require("./RpcControl");
const RpcOperation_1 = require("./RpcOperation");
/** @internal */
exports.REGISTRY = Symbol.for("@itwin/core-common/RpcRegistry");
/** @internal */
exports.OPERATION = Symbol.for("@itwin/core-common/RpcOperation");
/** @internal */
exports.POLICY = Symbol.for("@itwin/core-common/RpcOperationPolicy");
/** @internal */
exports.INSTANCE = Symbol.for("@itwin/core-common/RpcInterface/__instance__");
/** @internal */
exports.CURRENT_REQUEST = Symbol.for("@itwin/core-common/RpcRequest/__current__");
/** @internal */
exports.CURRENT_INVOCATION = Symbol.for("@itwin/core-common/RpcInvocation/__current__");
/** @internal */
class RpcRegistry {
    constructor() {
        this.definitionClasses = new Map();
        this.proxies = new Map();
        this.implementations = new Map();
        this.suppliedImplementations = new Map();
        this.implementationClasses = new Map();
        this.id = (() => {
            let i = 0;
            return () => ++i;
        })();
    }
    static get instance() {
        if (!RpcRegistry._instance) {
            const globalObj = typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
            if (!globalObj[exports.REGISTRY])
                globalObj[exports.REGISTRY] = new RpcRegistry();
            RpcRegistry._instance = globalObj[exports.REGISTRY];
        }
        return RpcRegistry._instance;
    }
    lookupInterfaceDefinition(name) {
        if (!this.definitionClasses.has(name))
            throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `RPC interface "${name}" is not initialized.`);
        return this.definitionClasses.get(name);
    }
    async describeAvailableEndpoints() {
        const requests = [];
        for (const channel of RpcControl_1.RpcControlChannel.channels) {
            requests.push(channel.describeEndpoints());
        }
        const responses = await Promise.all(requests);
        const endpoints = responses.reduce((a, b) => a.concat(b), []);
        for (const endpoint of endpoints) {
            const definition = this.definitionClasses.get(endpoint.interfaceName);
            endpoint.compatible = (definition && RpcInterface_1.RpcInterface.isVersionCompatible(endpoint.interfaceVersion, definition.interfaceVersion)) ? true : false;
        }
        return endpoints;
    }
    getClientForInterface(definition, routing = RpcRoutingToken_1.RpcRoutingToken.default) {
        let instance;
        const proxies = this.proxies.get(definition.interfaceName);
        if (proxies) {
            instance = proxies.get(routing.id);
        }
        if (!instance)
            instance = this.instantiateClient(definition, routing);
        return instance;
    }
    getImplForInterface(definition) {
        let instance = this.implementations.get(definition.interfaceName);
        if (!instance)
            instance = this.instantiateImpl(definition);
        return instance;
    }
    lookupImpl(interfaceName) {
        const definition = this.lookupInterfaceDefinition(interfaceName);
        return this.getImplForInterface(definition);
    }
    registerImpl(definition, implementation) {
        this.unregisterImpl(definition);
        this.implementationClasses.set(definition.interfaceName, implementation);
    }
    unregisterImpl(definition) {
        this.implementationClasses.delete(definition.interfaceName);
        const impl = this.implementations.get(definition.interfaceName);
        if (impl) {
            impl.configuration.onRpcImplTerminated(definition, impl);
            this.implementations.delete(definition.interfaceName);
        }
    }
    supplyImplInstance(definition, instance) {
        this.suppliedImplementations.set(definition.interfaceName, instance);
    }
    isRpcInterfaceInitialized(definition) {
        return this.definitionClasses.has(definition.interfaceName);
    }
    initializeRpcInterface(definition) {
        if (this.definitionClasses.has(definition.interfaceName)) {
            const existing = this.definitionClasses.get(definition.interfaceName);
            if (existing && definition.interfaceVersion === "CONTROL" && existing !== definition) {
                this.configureOperations(definition); // configs that differ only by routing still need the control ops initialized
            }
            return;
        }
        this.notifyInitialize();
        this.definitionClasses.set(definition.interfaceName, definition);
        this.configureOperations(definition);
    }
    terminateRpcInterface(definition) {
        this.unregisterImpl(definition);
        this.purgeClient(definition);
        this.definitionClasses.delete(definition.interfaceName);
    }
    instantiateImpl(definition) {
        this.checkInitialized(definition);
        const registeredImplementation = this.implementationClasses.get(definition.interfaceName);
        if (!registeredImplementation)
            throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `An RPC interface implementation class for "${definition.interfaceName}" is not registered.`);
        if (definition.prototype.configurationSupplier)
            registeredImplementation.prototype.configurationSupplier = definition.prototype.configurationSupplier;
        const supplied = this.suppliedImplementations.get(definition.interfaceName);
        const implementation = supplied || new registeredImplementation();
        if (!(implementation instanceof registeredImplementation))
            throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `Invalid RPC interface implementation.`);
        if (supplied) {
            supplied.configuration = RpcConfiguration_1.RpcConfiguration.supply(supplied);
        }
        this.implementations.set(definition.interfaceName, implementation);
        implementation.configuration.onRpcImplInitialized(definition, implementation);
        return implementation;
    }
    instantiateClient(definition, routing = RpcRoutingToken_1.RpcRoutingToken.default) {
        this.checkInitialized(definition);
        const proxy = new definition(routing);
        if (!this.proxies.has(definition.interfaceName)) {
            this.proxies.set(definition.interfaceName, new Map());
        }
        this.proxies.get(definition.interfaceName)?.set(routing.id, proxy);
        Object.getOwnPropertyNames(definition.prototype).forEach((operationName) => {
            if (operationName === "constructor" || operationName === "configurationSupplier")
                return;
            this.interceptOperation(proxy, operationName);
        });
        proxy.configuration.onRpcClientInitialized(definition, proxy);
        return proxy;
    }
    interceptOperation(proxy, operation) {
        const clientFunction = proxy[operation];
        proxy[operation] = function () {
            const args = Array.from(arguments);
            args.push(operation);
            return clientFunction.apply(proxy, args);
        };
    }
    checkInitialized(definition) {
        if (!this.definitionClasses.has(definition.interfaceName))
            throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `RPC interface "${definition.interfaceName}" is not initialized.`);
    }
    configureOperations(definition) {
        const proto = definition.prototype;
        Object.getOwnPropertyNames(proto).forEach((operationName) => {
            if (operationName === "constructor" || operationName === "configurationSupplier")
                return;
            const propertyName = RpcOperation_1.RpcOperation.computeOperationName(operationName);
            if (!proto[propertyName][exports.OPERATION]) {
                const policy = definition[exports.POLICY] || new RpcOperation_1.RpcOperationPolicy();
                proto[propertyName][exports.OPERATION] = new RpcOperation_1.RpcOperation(definition, propertyName, policy);
            }
        });
    }
    purgeClient(definition) {
        const proxies = this.proxies.get(definition.interfaceName);
        if (proxies) {
            proxies.forEach((proxy) => proxy.configuration.onRpcClientTerminated(definition, proxy));
            this.proxies.delete(definition.interfaceName);
        }
    }
    notifyInitialize() {
        (0, RpcRequest_1.initializeRpcRequest)();
        RpcPendingQueue_1.RpcPendingQueue.initialize();
    }
}
exports.RpcRegistry = RpcRegistry;
//# sourceMappingURL=RpcRegistry.js.map