import { __values } from "tslib";
import * as Core from "@intelligentgraphics/ig.gfx.core";
import { isNullOrUndefined, EventEmitter, isValue, noop, } from "@intelligentgraphics/ig.utilities";
import { getPosition as getAnimationPosition } from "./Util";
/**
 * Creates an animation manager that manages animations and positions for all animatable components in the whole scene
 *
 * @param {PlannerManager} plannerManager
 * @returns {SceneAnimationManager}
 */
export var createSceneAnimationManager = function (session, plannerManager, _a, animatorOptions) {
    var _b = _a === void 0 ? {} : _a, _c = _b.continueAnimationsAfterUpdate, continueAnimationsAfterUpdate = _c === void 0 ? true : _c, _d = _b.reapplyAnimationProgressAfterUpdate, reapplyAnimationProgressAfterUpdate = _d === void 0 ? true : _d;
    if (continueAnimationsAfterUpdate) {
        reapplyAnimationProgressAfterUpdate = true;
    }
    var eventEmitter = new EventEmitter();
    var getAnimatableComIds = function () {
        return session
            .getFilteredArticles(function (article) {
            return !isNullOrUndefined(article.gfxId) &&
                !isNullOrUndefined(Core.State.API.GetComponent(article.gfxId)) &&
                Core.State.API.GetComponentData(article.gfxId)
                    .hasAnimations === true;
        })
            .map(function (article) { return article.comId; });
    };
    var getRootIds = function () {
        return getAnimatableComIds()
            .map(function (comId) { return session.getArticleGfxId(comId); })
            .filter(isValue);
    };
    var getPosition = function () {
        return getAnimationPosition(Core.State.API, getRootIds());
    };
    var canAnimate = function () { return getAnimatableComIds().length > 0; };
    var activeStatus = getPosition() === "end" ? "end" : "start";
    var animatable = false;
    var activePlaybackControl;
    var activeAnimationParams;
    var getStatus = function () { return activeStatus; };
    var getActiveOrNextAnimationDirection = function () {
        if (preUpdateState !== undefined &&
            reapplyAnimationProgressAfterUpdate) {
            return preUpdateState.continueForward ? "forwards" : "backwards";
        }
        var position = getPosition();
        return position === "end" ? "backwards" : "forwards";
    };
    var updateStatus = function (newStatus) {
        activeStatus = newStatus;
        eventEmitter.emit("statusChange", newStatus);
    };
    var setPosition = function (position) {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.stop();
            activePlaybackControl = undefined;
        }
        Core.Animation.setPosition(getRootIds(), position === "end" ? "End" : "Start");
        updateStatus(position);
    };
    var showAnimation = function (params) {
        if (activePlaybackControl !== undefined) {
            return;
        }
        activeAnimationParams = params;
        activePlaybackControl = Core.Animation.showAnimation(getRootIds(), params, animatorOptions);
        updateStatus("running");
        var newStatus = params.forward === !params.invertAnimationAfter ? "end" : "start";
        activePlaybackControl.done
            .then(function () {
            updateStatus(newStatus);
        })
            .finally(function () {
            activePlaybackControl = undefined;
        })
            .catch(noop);
    };
    var showAnimationFromCurrentPosition = function (invertAnimationAfter) {
        var position = getPosition();
        return showAnimation({
            forward: position !== "end",
            invertAnimationAfter: invertAnimationAfter,
        });
    };
    var pauseAnimation = function () {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.pause();
            updateStatus("paused");
        }
    };
    var continueAnimation = function () {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.continue();
            updateStatus("running");
        }
    };
    var stopAnimation = function () {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.stop();
            activePlaybackControl = undefined;
            updateStatus("stopped");
        }
    };
    animatable = canAnimate();
    var preUpdateState;
    plannerManager.hooks.sceneUpdateStart.tap("AnimationManager", function () {
        var e_1, _a;
        preUpdateState = {
            maxProgress: 0,
            continueAfterUpdate: false,
            continueForward: true,
            animationWasPaused: false,
        };
        var animatableComIds = getAnimatableComIds();
        try {
            for (var animatableComIds_1 = __values(animatableComIds), animatableComIds_1_1 = animatableComIds_1.next(); !animatableComIds_1_1.done; animatableComIds_1_1 = animatableComIds_1.next()) {
                var comId = animatableComIds_1_1.value;
                var gfxId = session.getArticleGfxId(comId);
                var _b = Core.State.API.GetComponentData(gfxId), animationProgress = _b.animationProgress, animationPosition = _b.animationPosition;
                if (animationProgress !== undefined) {
                    preUpdateState.maxProgress = Math.max(animationProgress, preUpdateState.maxProgress);
                }
                if (animationPosition !== undefined &&
                    preUpdateState.continueForward) {
                    preUpdateState.continueForward = animationPosition !== "End";
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (animatableComIds_1_1 && !animatableComIds_1_1.done && (_a = animatableComIds_1.return)) _a.call(animatableComIds_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        if (activePlaybackControl !== undefined) {
            preUpdateState.animationWasPaused = activeStatus === "paused";
            preUpdateState.continueAfterUpdate = true;
            stopAnimation();
        }
    });
    plannerManager.hooks.articleUpdateDone.tap("AnimationManager", function (article, update) {
        var maxProgress = preUpdateState.maxProgress;
        if (!continueAnimationsAfterUpdate) {
            if (update.updateType !== "materialOnly") {
                setPosition("start");
            }
            return;
        }
        if (reapplyAnimationProgressAfterUpdate) {
            Core.Animation.setProgress([article.gfxId], maxProgress);
        }
    });
    plannerManager.hooks.sceneUpdateDone.tap("AnimationManager", function () {
        var e_2, _a;
        var _b = preUpdateState, maxProgress = _b.maxProgress, continueAfterUpdate = _b.continueAfterUpdate, continueForward = _b.continueForward, animationWasPaused = _b.animationWasPaused;
        if (continueAnimationsAfterUpdate) {
            try {
                // reapply animation positions here, since they might have been lost in the update
                for (var _c = __values(getAnimatableComIds()), _d = _c.next(); !_d.done; _d = _c.next()) {
                    var comId = _d.value;
                    var gfxId = session.getArticleGfxId(comId);
                    if (gfxId !== undefined) {
                        Core.State.API.SetComponentData(gfxId, "animationPosition", continueForward ? "Start" : "End");
                    }
                }
            }
            catch (e_2_1) { e_2 = { error: e_2_1 }; }
            finally {
                try {
                    if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
                }
                finally { if (e_2) throw e_2.error; }
            }
            if (continueAfterUpdate) {
                showAnimation({
                    forward: continueForward,
                    startFromProgress: maxProgress,
                });
                if (animationWasPaused) {
                    pauseAnimation();
                }
            }
        }
        preUpdateState = undefined;
        var canNowAnimate = canAnimate();
        if (canNowAnimate !== animatable) {
            animatable = canNowAnimate;
            eventEmitter.emit("animateabilityChange", animatable);
        }
    });
    return {
        on: eventEmitter.on.bind(eventEmitter),
        off: eventEmitter.off.bind(eventEmitter),
        once: eventEmitter.once.bind(eventEmitter),
        canAnimate: canAnimate,
        setPosition: setPosition,
        getPosition: getPosition,
        getStatus: getStatus,
        getActiveOrNextAnimationDirection: getActiveOrNextAnimationDirection,
        showAnimation: showAnimation,
        showAnimationFromCurrentPosition: showAnimationFromCurrentPosition,
        pauseAnimation: pauseAnimation,
        continueAnimation: continueAnimation,
        stopAnimation: stopAnimation,
    };
};
