"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestScheduler = void 0;
const rxjs_1 = require("rxjs");
const VirtualTimeScheduler_1 = require("rxjs/internal/scheduler/VirtualTimeScheduler");
const ColdObservable_1 = require("rxjs/internal/testing/ColdObservable");
const HotObservable_1 = require("rxjs/internal/testing/HotObservable");
const parseObservableMarble_1 = require("../marbles/parseObservableMarble");
const SubscriptionMarbleToken_1 = require("../marbles/SubscriptionMarbleToken");
const TestMessage_1 = require("../message/TestMessage");
const calculateSubscriptionFrame_1 = require("./calculateSubscriptionFrame");
/**
 * @internal
 */
class TestScheduler extends VirtualTimeScheduler_1.VirtualTimeScheduler {
    constructor(autoFlush, frameTimeFactor, maxFrameValue) {
        super(VirtualTimeScheduler_1.VirtualAction, Number.POSITIVE_INFINITY);
        this.autoFlush = autoFlush;
        this.frameTimeFactor = frameTimeFactor;
        this.coldObservables = [];
        this.hotObservables = [];
        this.flushed = false;
        this.flushing = false;
        this._maxFrame = maxFrameValue * frameTimeFactor;
        this.maxFrame = this._maxFrame;
    }
    flush() {
        this.flushUntil();
    }
    getMessages(observable, unsubscriptionMarbles = null) {
        const { subscribedFrame, unsubscribedFrame } = calculateSubscriptionFrame_1.calculateSubscriptionFrame(observable, unsubscriptionMarbles, this.frameTimeFactor);
        const observableMetadata = [];
        const pushMetadata = (notification) => observableMetadata.push(new TestMessage_1.TestMessageValue(this.frame, notification));
        let subscription = null;
        this.schedule(() => {
            subscription = observable.subscribe((value) => pushMetadata(rxjs_1.Notification.createNext(value instanceof rxjs_1.Observable ? this.materializeInnerObservable(value, this.frame) : value)), (err) => pushMetadata(rxjs_1.Notification.createError(err)), () => pushMetadata(rxjs_1.Notification.createComplete()));
        }, subscribedFrame);
        if (unsubscribedFrame !== Number.POSITIVE_INFINITY) {
            this.schedule(() => subscription.unsubscribe(), unsubscribedFrame);
        }
        if (this.autoFlush) {
            if (this.flushed) {
                throw new Error(`Cannot schedule to get marbles, scheduler's already flushed`);
            }
            this.flush();
        }
        return observableMetadata;
    }
    createColdObservable(...args) {
        const [marbleValue, value, error] = args;
        if (typeof marbleValue === 'string' && marbleValue.indexOf(SubscriptionMarbleToken_1.SubscriptionMarbleToken.SUBSCRIBE) !== -1) {
            throw new Error(`Cold observable cannot have subscription offset ${SubscriptionMarbleToken_1.SubscriptionMarbleToken.SUBSCRIBE}`);
        }
        const messages = Array.isArray(marbleValue)
            ? marbleValue
            : parseObservableMarble_1.parseObservableMarble(marbleValue, value, error, false, this.frameTimeFactor, this._maxFrame);
        const observable = new ColdObservable_1.ColdObservable(messages, this);
        this.coldObservables.push(observable);
        return observable;
    }
    createHotObservable(...args) {
        const [marbleValue, value, error] = args;
        const messages = Array.isArray(marbleValue)
            ? marbleValue
            : parseObservableMarble_1.parseObservableMarble(marbleValue, value, error, false, this.frameTimeFactor, this._maxFrame);
        const subject = new HotObservable_1.HotObservable(messages, this);
        this.hotObservables.push(subject);
        return subject;
    }
    advanceTo(toFrame) {
        if (this.autoFlush) {
            throw new Error('Cannot advance frame manually with autoflushing scheduler');
        }
        if (toFrame < 0 || toFrame < this.frame) {
            throw new Error(`Cannot advance frame, given frame is either negative or smaller than current frame`);
        }
        this.flushUntil(toFrame);
        this.frame = toFrame;
    }
    materializeInnerObservable(observable, outerFrame) {
        const innerObservableMetadata = [];
        const pushMetaData = (notification) => innerObservableMetadata.push(new TestMessage_1.TestMessageValue(this.frame - outerFrame, notification));
        observable.subscribe((value) => pushMetaData(rxjs_1.Notification.createNext(value)), (err) => pushMetaData(rxjs_1.Notification.createError(err)), () => pushMetaData(rxjs_1.Notification.createComplete()));
        return innerObservableMetadata;
    }
    peek() {
        const { actions } = this;
        return actions && actions.length > 0 ? actions[0] : null;
    }
    flushUntil(toFrame = this.maxFrame) {
        if (this.flushing) {
            return;
        }
        const { hotObservables } = this;
        while (hotObservables.length > 0) {
            hotObservables.shift().setup();
        }
        this.flushing = true;
        const { actions } = this;
        let error;
        let action = null;
        while (this.flushing && (action = this.peek()) && action.delay <= toFrame) {
            const action = actions.shift();
            this.frame = action.delay;
            if ((error = action.execute(action.state, action.delay))) {
                break;
            }
        }
        this.flushing = false;
        if (toFrame >= this.maxFrame) {
            this.flushed = true;
        }
        if (error) {
            while ((action = actions.shift())) {
                action.unsubscribe();
            }
            throw error;
        }
    }
}
exports.TestScheduler = TestScheduler;
//# sourceMappingURL=TestScheduler.js.map