"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrderMessages = void 0;
const protocol_1 = require("@streamr/protocol");
const utils_1 = require("@streamr/utils");
const Mapping_1 = require("../../utils/Mapping");
const PushBuffer_1 = require("../../utils/PushBuffer");
const caches_1 = require("../../utils/caches");
const GapFiller_1 = require("./GapFiller");
const OrderedMessageChain_1 = require("./OrderedMessageChain");
const createMessageChain = (context, getStorageNodes, onUnfillableGap, resends, config, abortSignal) => {
    const resend = async function* (gap, storageNodeAddress, abortSignal) {
        const msgs = await resends.resend(context.streamPartId, {
            from: {
                timestamp: gap.from.getMessageRef().timestamp,
                sequenceNumber: gap.from.getMessageRef().sequenceNumber + 1,
            },
            to: gap.to.prevMsgRef,
            publisherId: context.publisherId,
            msgChainId: context.msgChainId,
            raw: true
        }, async () => [storageNodeAddress], abortSignal);
        yield* msgs;
    };
    const chain = new OrderedMessageChain_1.OrderedMessageChain(context, abortSignal);
    chain.on('unfillableGap', (gap) => onUnfillableGap(gap));
    const gapFiller = new GapFiller_1.GapFiller({
        chain,
        resend,
        // TODO maybe caching should be configurable? (now uses 30 min maxAge, which is the default of CacheAsyncFn)
        // - maybe the caching should be done at application level, e.g. with a new CacheStreamStorageRegistry class?
        // - also not that this is a cache which contains just one item (as streamPartId always the same)
        getStorageNodeAddresses: (0, caches_1.CacheAsyncFn)(() => getStorageNodes(protocol_1.StreamPartIDUtils.getStreamID(context.streamPartId))),
        strategy: config.gapFillStrategy,
        initialWaitTime: config.gapFillTimeout,
        retryWaitTime: config.retryResendAfter,
        maxRequestsPerGap: (config.gapFill) ? config.maxGapRequests : 0,
        abortSignal
    });
    gapFiller.start();
    return chain;
};
/**
 * Manages message ordering and gap filling (per stream part). Provides an iterator
 * to read the sequence of messages which are in ascending order and gaps are filled
 * (if enabled, and the missing message available in a storage node)
 */
class OrderMessages {
    chains;
    outBuffer = new PushBuffer_1.PushBuffer();
    abortController = new AbortController();
    constructor(streamPartId, getStorageNodes, onUnfillableGap, resends, config) {
        this.chains = new Mapping_1.Mapping(async (publisherId, msgChainId) => {
            const chain = createMessageChain({
                streamPartId,
                publisherId,
                msgChainId
            }, getStorageNodes, onUnfillableGap, resends, config, this.abortController.signal);
            chain.on('orderedMessageAdded', (msg) => this.onOrdered(msg));
            return chain;
        });
    }
    onOrdered(orderedMessage) {
        if (!this.outBuffer.isDone()) {
            (0, utils_1.executeSafePromise)(() => this.outBuffer.push(orderedMessage));
        }
    }
    destroy() {
        this.outBuffer.endWrite();
        this.abortController.abort();
    }
    async addMessages(src) {
        try {
            for await (const msg of src) {
                if (this.abortController.signal.aborted) {
                    return;
                }
                const chain = await this.chains.get(msg.getPublisherId(), msg.getMsgChainId());
                chain.addMessage(msg);
            }
            await Promise.all(this.chains.values().map((chain) => chain.waitUntilIdle()));
            this.outBuffer.endWrite();
        }
        catch (err) {
            this.outBuffer.endWrite(err);
        }
    }
    [Symbol.asyncIterator]() {
        return this.outBuffer;
    }
}
exports.OrderMessages = OrderMessages;
//# sourceMappingURL=OrderMessages.js.map