"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 protocol_1 = require("@streamr/protocol");
const utils_1 = require("@streamr/utils");
const min_1 = __importDefault(require("lodash/min"));
const tsyringe_1 = require("tsyringe");
const Authentication_1 = require("../Authentication");
const Config_1 = require("../Config");
const ContractFactory_1 = require("../ContractFactory");
const Ethereum_1 = require("../Ethereum");
const Stream_1 = require("../Stream");
const StreamFactory_1 = require("../StreamFactory");
const StreamIDBuilder_1 = require("../StreamIDBuilder");
const StreamStorageRegistryV2Abi_json_1 = __importDefault(require("../ethereumArtifacts/StreamStorageRegistryV2Abi.json"));
const events_1 = require("../events");
const LoggerFactory_1 = require("../utils/LoggerFactory");
const contract_1 = require("../utils/contract");
/**
 * Stores storage node assignments (mapping of streamIds <-> storage nodes addresses)
 */
let StreamStorageRegistry = class StreamStorageRegistry {
    streamStorageRegistryContract;
    streamStorageRegistryContractsReadonly;
    streamFactory;
    streamIdBuilder;
    contractFactory;
    theGraphClient;
    config;
    authentication;
    logger;
    /* eslint-disable indent */
    constructor(streamFactory, streamIdBuilder, contractFactory, theGraphClient, config, authentication, eventEmitter, loggerFactory) {
        this.streamFactory = streamFactory;
        this.streamIdBuilder = streamIdBuilder;
        this.contractFactory = contractFactory;
        this.theGraphClient = theGraphClient;
        this.config = config;
        this.authentication = authentication;
        this.logger = loggerFactory.createLogger(module);
        this.streamStorageRegistryContractsReadonly = (0, Ethereum_1.getStreamRegistryChainProviders)(config).map((provider) => {
            return this.contractFactory.createReadContract((0, utils_1.toEthereumAddress)(this.config.contracts.streamStorageRegistryChainAddress), StreamStorageRegistryV2Abi_json_1.default, provider, 'streamStorageRegistry');
        });
        this.initStreamAssignmentEventListeners(eventEmitter, loggerFactory);
    }
    initStreamAssignmentEventListeners(eventEmitter, loggerFactory) {
        const primaryReadonlyContract = this.streamStorageRegistryContractsReadonly[0];
        const transformation = (streamId, nodeAddress, extra) => ({
            streamId: (0, protocol_1.toStreamID)(streamId),
            nodeAddress: (0, utils_1.toEthereumAddress)(nodeAddress),
            blockNumber: extra.blockNumber
        });
        (0, contract_1.initContractEventGateway)({
            sourceName: 'Added',
            sourceEmitter: primaryReadonlyContract,
            targetName: 'addToStorageNode',
            targetEmitter: eventEmitter,
            transformation,
            loggerFactory
        });
        (0, contract_1.initContractEventGateway)({
            sourceName: 'Removed',
            sourceEmitter: primaryReadonlyContract,
            targetName: 'removeFromStorageNode',
            targetEmitter: eventEmitter,
            transformation,
            loggerFactory
        });
    }
    async connectToContract() {
        if (!this.streamStorageRegistryContract) {
            const chainSigner = await this.authentication.getStreamRegistryChainSigner();
            this.streamStorageRegistryContract = this.contractFactory.createWriteContract((0, utils_1.toEthereumAddress)(this.config.contracts.streamStorageRegistryChainAddress), StreamStorageRegistryV2Abi_json_1.default, 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 = (0, Ethereum_1.getEthersOverrides)(this.config);
        await (0, contract_1.waitForTx)(this.streamStorageRegistryContract.addStorageNode(streamId, nodeAddress, ethersOverrides));
    }
    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 = (0, Ethereum_1.getEthersOverrides)(this.config);
        await (0, contract_1.waitForTx)(this.streamStorageRegistryContract.removeStorageNode(streamId, nodeAddress, ethersOverrides));
    }
    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 (0, contract_1.queryAllReadonlyContracts)((contract) => {
            return contract.isStorageNodeOf(streamId, nodeAddress);
        }, this.streamStorageRegistryContractsReadonly);
    }
    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) => {
            const props = Stream_1.Stream.parseMetadata(stream.metadata);
            return this.streamFactory.createStream((0, protocol_1.toStreamID)(stream.id), props); // toStreamID() not strictly necessary
        });
        return {
            streams,
            blockNumber: (0, min_1.default)(blockNumbers)
        };
    }
    async getStorageNodes(streamIdOrPath) {
        let queryResults;
        if (streamIdOrPath !== undefined) {
            const streamId = await this.streamIdBuilder.toStreamID(streamIdOrPath);
            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(0, (0, tsyringe_1.inject)((0, tsyringe_1.delay)(() => StreamFactory_1.StreamFactory))),
    __param(4, (0, tsyringe_1.inject)(Config_1.ConfigInjectionToken)),
    __param(5, (0, tsyringe_1.inject)(Authentication_1.AuthenticationInjectionToken)),
    __metadata("design:paramtypes", [StreamFactory_1.StreamFactory,
        StreamIDBuilder_1.StreamIDBuilder,
        ContractFactory_1.ContractFactory,
        utils_1.TheGraphClient, Object, Object, events_1.StreamrClientEventEmitter,
        LoggerFactory_1.LoggerFactory])
], StreamStorageRegistry);
//# sourceMappingURL=StreamStorageRegistry.js.map