"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamStorageRegistry = void 0;
const network_contracts_1 = require("@streamr/network-contracts");
const utils_1 = require("@streamr/utils");
const ethers_1 = require("ethers");
const min_1 = __importDefault(require("lodash/min"));
const tsyringe_1 = require("tsyringe");
const Identity_1 = require("../identity/Identity");
const Config_1 = require("../Config");
const RpcProviderSource_1 = require("../RpcProviderSource");
const StreamIDBuilder_1 = require("../StreamIDBuilder");
const StreamMetadata_1 = require("../StreamMetadata");
const ethereumUtils_1 = require("../ethereumUtils");
const events_1 = require("../events");
const LoggerFactory_1 = require("../utils/LoggerFactory");
const Mapping_1 = require("../utils/Mapping");
const ChainEventPoller_1 = require("./ChainEventPoller");
const ContractFactory_1 = require("./ContractFactory");
const contract_1 = require("./contract");
const GET_ALL_STORAGE_NODES = Symbol('GET_ALL_STORAGE_NODES');
/**
 * Stores storage node assignments (mapping of streamIds <-> storage nodes addresses)
 */
let StreamStorageRegistry = class StreamStorageRegistry {
    streamStorageRegistryContract;
    streamStorageRegistryContractReadonly;
    streamIdBuilder;
    contractFactory;
    rpcProviderSource;
    theGraphClient;
    config;
    identity;
    logger;
    storageNodesCache;
    constructor(streamIdBuilder, contractFactory, rpcProviderSource, chainEventPoller, theGraphClient, config, identity, eventEmitter, loggerFactory) {
        this.streamIdBuilder = streamIdBuilder;
        this.contractFactory = contractFactory;
        this.rpcProviderSource = rpcProviderSource;
        this.theGraphClient = theGraphClient;
        this.config = config;
        this.identity = identity;
        this.logger = loggerFactory.createLogger(module);
        this.streamStorageRegistryContractReadonly = this.contractFactory.createReadContract((0, utils_1.toEthereumAddress)(this.config.contracts.streamStorageRegistryChainAddress), network_contracts_1.StreamStorageRegistryABI, rpcProviderSource.getProvider(), 'streamStorageRegistry');
        this.initStreamAssignmentEventListeners(eventEmitter, chainEventPoller, loggerFactory);
        this.storageNodesCache = (0, Mapping_1.createCacheMap)({
            valueFactory: (query) => {
                return this.getStorageNodes_nonCached(query);
            },
            ...config.cache
        });
    }
    initStreamAssignmentEventListeners(eventEmitter, chainEventPoller, loggerFactory) {
        const transformation = (streamId, nodeAddress, blockNumber) => ({
            streamId: (0, utils_1.toStreamID)(streamId),
            nodeAddress: (0, utils_1.toEthereumAddress)(nodeAddress),
            blockNumber
        });
        const contractAddress = (0, utils_1.toEthereumAddress)(this.config.contracts.streamStorageRegistryChainAddress);
        const contractInterface = new ethers_1.Interface(network_contracts_1.StreamStorageRegistryABI);
        (0, contract_1.initContractEventGateway)({
            sourceDefinition: {
                contractInterfaceFragment: contractInterface.getEvent('Added'),
                contractAddress
            },
            sourceEmitter: chainEventPoller,
            targetName: 'streamAddedToStorageNode',
            targetEmitter: eventEmitter,
            transformation,
            loggerFactory
        });
        (0, contract_1.initContractEventGateway)({
            sourceDefinition: {
                contractInterfaceFragment: contractInterface.getEvent('Removed'),
                contractAddress
            },
            sourceEmitter: chainEventPoller,
            targetName: 'streamRemovedFromStorageNode',
            targetEmitter: eventEmitter,
            transformation,
            loggerFactory
        });
    }
    async connectToContract() {
        if (!this.streamStorageRegistryContract) {
            const chainSigner = await this.identity.getTransactionSigner(this.rpcProviderSource);
            this.streamStorageRegistryContract = this.contractFactory.createWriteContract((0, utils_1.toEthereumAddress)(this.config.contracts.streamStorageRegistryChainAddress), network_contracts_1.StreamStorageRegistryABI, chainSigner, 'streamStorageRegistry');
        }
    }
    async addStreamToStorageNode(streamIdOrPath, nodeAddress) {
        const streamId = await this.streamIdBuilder.toStreamID(streamIdOrPath);
        this.logger.debug('Add stream to storage node', { streamId, nodeAddress });
        await this.connectToContract();
        const ethersOverrides = await (0, ethereumUtils_1.getEthersOverrides)(this.rpcProviderSource, this.config);
        await (0, contract_1.waitForTx)(this.streamStorageRegistryContract.addStorageNode(streamId, nodeAddress, ethersOverrides));
        this.storageNodesCache.invalidate((key) => key === streamId);
    }
    async removeStreamFromStorageNode(streamIdOrPath, nodeAddress) {
        const streamId = await this.streamIdBuilder.toStreamID(streamIdOrPath);
        this.logger.debug('Remove stream from storage node', { streamId, nodeAddress });
        await this.connectToContract();
        const ethersOverrides = await (0, ethereumUtils_1.getEthersOverrides)(this.rpcProviderSource, this.config);
        await (0, contract_1.waitForTx)(this.streamStorageRegistryContract.removeStorageNode(streamId, nodeAddress, ethersOverrides));
        this.storageNodesCache.invalidate((key) => key === streamId);
    }
    async isStoredStream(streamIdOrPath, nodeAddress) {
        const streamId = await this.streamIdBuilder.toStreamID(streamIdOrPath);
        this.logger.debug('Check if stream is stored in storage node', { streamId, nodeAddress });
        return await this.streamStorageRegistryContractReadonly.isStorageNodeOf(streamId, nodeAddress);
    }
    async getStoredStreams(nodeAddress) {
        this.logger.debug('Get stored streams of storage node', { nodeAddress });
        const blockNumbers = [];
        const res = await (0, utils_1.collect)(this.theGraphClient.queryEntities((lastId, pageSize) => {
            const query = `{
                    node (id: "${nodeAddress}") {
                        id
                        metadata
                        lastSeen
                        storedStreams (first: ${pageSize} orderBy: "id" where: { id_gt: "${lastId}"}) {
                            id,
                            metadata
                        }
                    }
                    _meta {
                        block {
                            number
                        }
                    }
                }`;
            return { query };
        }, (response) => {
            // eslint-disable-next-line no-underscore-dangle
            blockNumbers.push(response._meta.block.number);
            return (response.node !== null) ? response.node.storedStreams : [];
        }));
        const streams = res.map((stream) => {
            return { id: (0, utils_1.toStreamID)(stream.id), metadata: (0, StreamMetadata_1.parseMetadata)(stream.metadata) }; // toStreamID() not strictly necessary
        });
        return {
            streams,
            blockNumber: (0, min_1.default)(blockNumbers)
        };
    }
    async getStorageNodes(streamIdOrPath) {
        const query = (streamIdOrPath !== undefined) ? await this.streamIdBuilder.toStreamID(streamIdOrPath) : GET_ALL_STORAGE_NODES;
        return this.storageNodesCache.get(query);
    }
    async getStorageNodes_nonCached(query) {
        let queryResults;
        if (query !== GET_ALL_STORAGE_NODES) {
            const streamId = query;
            this.logger.debug('Get storage nodes of stream', { streamId });
            queryResults = await (0, utils_1.collect)(this.theGraphClient.queryEntities((lastId, pageSize) => {
                const query = `{
                        stream (id: "${streamId}") {
                            id
                            metadata
                            storageNodes (first: ${pageSize} orderBy: "id" where: { id_gt: "${lastId}"}) {
                                id
                                metadata
                                lastSeen
                            }
                        }
                    }`;
                return { query };
            }, (response) => {
                return (response.stream !== null) ? response.stream.storageNodes : [];
            }));
        }
        else {
            this.logger.debug('Get all storage nodes');
            queryResults = await (0, utils_1.collect)(this.theGraphClient.queryEntities((lastId, pageSize) => {
                const query = `{
                        nodes (first: ${pageSize} orderBy: "id" where: { id_gt: "${lastId}"}) {
                            id
                            metadata
                            lastSeen
                        }
                    }`;
                return { query };
            }));
        }
        return queryResults.map((node) => (0, utils_1.toEthereumAddress)(node.id));
    }
};
exports.StreamStorageRegistry = StreamStorageRegistry;
exports.StreamStorageRegistry = StreamStorageRegistry = __decorate([
    (0, tsyringe_1.scoped)(tsyringe_1.Lifecycle.ContainerScoped),
    __param(5, (0, tsyringe_1.inject)(Config_1.ConfigInjectionToken)),
    __param(6, (0, tsyringe_1.inject)(Identity_1.IdentityInjectionToken)),
    __metadata("design:paramtypes", [StreamIDBuilder_1.StreamIDBuilder,
        ContractFactory_1.ContractFactory,
        RpcProviderSource_1.RpcProviderSource,
        ChainEventPoller_1.ChainEventPoller,
        utils_1.TheGraphClient, Object, Identity_1.Identity,
        events_1.StreamrClientEventEmitter,
        LoggerFactory_1.LoggerFactory])
], StreamStorageRegistry);
//# sourceMappingURL=StreamStorageRegistry.js.map