"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var lodash_1 = tslib_1.__importDefault(require("lodash"));
var Core = tslib_1.__importStar(require("@intelligentgraphics/ig.gfx.core"));
var MultiArticle = tslib_1.__importStar(require("@intelligentgraphics/ig.gfx.multiarticle"));
var ThreeJS = tslib_1.__importStar(require("@intelligentgraphics/ig.gfx.threejs"));
var THREE = tslib_1.__importStar(require("@intelligentgraphics/three"));
var ig_gfx_core_1 = require("@intelligentgraphics/ig.gfx.core");
var Associates = tslib_1.__importStar(require("../Associates"));
var GhostInsertMode_1 = require("../GhostInsertMode");
var Types_1 = require("../Types");
var MouseTo3DMovement_1 = require("./MouseTo3DMovement");
var OperationStrategyFinder_1 = require("./OperationStrategyFinder");
var ig_gfx_threejs_1 = require("@intelligentgraphics/ig.gfx.threejs");
var Util_1 = require("../Util");
var Logger_1 = tslib_1.__importDefault(require("../Logger"));
var SILogger = Logger_1.default.createSubLogger("StandardInteractor");
/** A enhanced modularized, global Interactor. */
var StandardInteractor = /** @class */ (function (_super) {
    tslib_1.__extends(StandardInteractor, _super);
    /**
     * The constructor of StandardInteractor. Initializes the OperationStrategyFinder
     * member and settings.
     * @param parameters Initialization parameters, see IG.GFX.Core.Interactor
     */
    function StandardInteractor(parameters) {
        var _this = _super.call(this, parameters) || this;
        /** Only called once for the first TouchMotion Event. */
        _this.onMoveStarted = function () {
            return;
        };
        /** The callback to be called after a snap occured. It receives the
         * snapped ids and the snap mode. */
        _this.onSnapped = function (snappedIds, mode) {
            return;
        };
        /** Only called after all movement is finished. */
        _this.onMoveEnded = function () {
            return;
        };
        /**
         * Called after all effects of a MouseMotion Event have been processed.
         * @param positionsById New positions, keyed by the corresponding id
         * @param isValid: is the new position a valid one (e.g. allowed)
         * @param matches: all snap matches that have been calculated
         */
        _this.onMoveStepped = function (positionsById, isValid) { };
        /** cache the niches of all roots */
        _this.niches = new Map();
        /** Article configuration for snap ghost creation */
        _this.articleConfigurations = new Map();
        _this._options = {
            snapping: {
                ignoredModes: [],
                ignoreAll: false,
            },
            ghosts: {
                snapping: true,
                collision: true,
                mode: ig_gfx_core_1.EGhostMode.Box,
            },
            collisions: {
                ignoreAll: false,
            },
            grid: {
                movement: 0,
                rotation: 0,
            },
            bounds: {
                max: {
                    x: Infinity,
                    y: Infinity,
                    z: Infinity,
                },
                min: {
                    x: -Infinity,
                    y: -Infinity,
                    z: -Infinity,
                },
            },
            keepFloatingObjectsFloating: false,
        };
        _this.snapTimer = -1;
        _this.previousState = undefined;
        _this.currentState = {
            duringMovement: false,
            stage: Types_1.EStage.None,
            operation: undefined,
            selectedIds: [],
            initialCoordinates: undefined,
            coordinates: undefined,
            angle: 0,
        };
        return _this;
    }
    Object.defineProperty(StandardInteractor.prototype, "moveStartedCallback", {
        /**
         * Only called once for the first TouchMotion Event.
         * @deprecated getter for backward compatibility
         */
        get: function () {
            SILogger.warn("moveStartedCallback is deprecated. use onMoveStarted instead.");
            return this.onMoveStarted;
        },
        /**
         * @deprecated setter for backward compatibility
         */
        set: function (callback) {
            SILogger.warn("moveStartedCallback is deprecated. use onMoveStarted instead.");
            this.onMoveStarted = callback;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(StandardInteractor.prototype, "onSnapCallback", {
        /**
         * The callback to be called after a snap occured. It receives the
         * snapped ids and the snap mode.
         * @deprecated getter for backward compatibility
         */
        get: function () {
            SILogger.warn("onSnapCallback is deprecated. use onSnapped instead.");
            return this.onSnapped;
        },
        /**
         * @deprecated setter for backward compatibility
         */
        set: function (callback) {
            SILogger.warn("onSnapCallback is deprecated. use onSnapped instead.");
            this.onSnapped = callback;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(StandardInteractor.prototype, "moveEndedCallback", {
        /**
         * Only called after all movement is finished.
         * @deprecated setter for backward compatibility
         */
        get: function () {
            SILogger.warn("moveEndedCallback is deprecated. use onMoveEnded instead.");
            return this.onMoveEnded;
        },
        /**
         * @deprecated setter for backward compatibility
         */
        set: function (callback) {
            SILogger.warn("moveEndedCallback is deprecated. use onMoveEnded instead.");
            this.onMoveEnded = callback;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(StandardInteractor.prototype, "moveSteppedCallback", {
        /**
         * Called after all effects of a MouseMotion Event have been processed.
         * @param positionsById New positions, keyed by the corresponding id
         * @param isValid: is the new position a valid one (e.g. allowed)
         * @param matches: all snap matches that have been calculated
         * @deprecated getter for backward compatibility
         */
        get: function () {
            SILogger.warn("moveSteppedCallback is deprecated. use onMoveStepped instead.");
            return this.onMoveStepped;
        },
        /**
         * @deprecated setter for backward compatibility
         */
        set: function (callback) {
            SILogger.warn("moveSteppedCallback is deprecated. use onMoveStepped instead.");
            this.onMoveStepped = callback;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Set Standard Interactor options. All values have to be provided.
     * @param options
     */
    StandardInteractor.prototype.setGlobalOptions = function (options) {
        this._options = tslib_1.__assign({}, options);
    };
    /**
     * Set Standard Interactor options. All none specified values
     * will not be overridden
     * @param otherOptions
     */
    StandardInteractor.prototype.mergeIntoGlobalOptions = function (otherOptions) {
        this._options = tslib_1.__assign(tslib_1.__assign({}, this._options), otherOptions);
    };
    /**
     * Updates the current state.
     * @param stage the stage that the interaction is in
     * @param coordinates the view coordinates of the touch
     * @param converter the converter
     */
    StandardInteractor.prototype.updateState = function (stage, coordinates) {
        this.previousState = tslib_1.__assign({}, this.currentState);
        this.currentState.duringMovement = stage === Types_1.EStage.Motion;
        this.currentState.stage = stage;
        this.currentState.coordinates = coordinates;
    };
    /**
     * special care about components that are only movable/rotatable
     * when the parent component is in selection too
     * @param selectedIds
     */
    StandardInteractor.prototype.filterSelection = function (selectedIds) {
        var e_1, _a;
        var filteredSelectionIds = new Array();
        try {
            for (var selectedIds_1 = tslib_1.__values(selectedIds), selectedIds_1_1 = selectedIds_1.next(); !selectedIds_1_1.done; selectedIds_1_1 = selectedIds_1.next()) {
                var select = selectedIds_1_1.value;
                var canBeAdded = false;
                var rotatableWithParentOnly = ig_gfx_core_1.Category.hasCategory(select, ig_gfx_core_1.Category.RotatableWithParentOnly);
                var movableWithParentOnly = ig_gfx_core_1.Category.hasCategory(select, ig_gfx_core_1.Category.MovableWithParentOnly);
                if (!movableWithParentOnly && !rotatableWithParentOnly) {
                    canBeAdded = true;
                }
                if (!canBeAdded) {
                    // is rotation only applicable, when parent is within selection?
                    if (this.moveMode === Types_1.EMoveMode.Rotate &&
                        rotatableWithParentOnly) {
                        var parentOfSelected = Associates.parent(select);
                        if (selectedIds.indexOf(parentOfSelected) > -1) {
                            canBeAdded = true;
                        }
                    }
                    // is translation only applicable, when parent is within selection?
                    else if (this.moveMode === Types_1.EMoveMode.Translate &&
                        movableWithParentOnly) {
                        var parentOfSelected = Associates.parent(select);
                        if (selectedIds.indexOf(parentOfSelected) > -1) {
                            canBeAdded = true;
                        }
                    }
                }
                if (canBeAdded) {
                    filteredSelectionIds.push(select);
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (selectedIds_1_1 && !selectedIds_1_1.done && (_a = selectedIds_1.return)) _a.call(selectedIds_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return filteredSelectionIds;
    };
    /**
     * Updates rotation angle for Y axis
     * @param coordinates the view coordinates of the touch
     * @param converter the converter
     */
    StandardInteractor.prototype.updateYRotationAngle = function (coordinates) {
        var centerOfSelectionViewCoordinates = this.converter.WorldToView(this.centerOfSelection);
        var tDx = coordinates.x - centerOfSelectionViewCoordinates.x;
        var tDy = coordinates.y - centerOfSelectionViewCoordinates.y;
        this.currentState.angle = Math.atan2(tDx, -tDy);
    };
    StandardInteractor.prototype.directNeighbors = function (id) {
        return Associates.neighbors(id);
    };
    /**
     * Function for applying an operation directly
     * @param stage the stage for the operation (Start, Motion, End)
     * @param coordinates the ViewCoordinates of the current touch
     * @param moveMode the move mode (rotation or translation)
     * @constructor
     */
    StandardInteractor.prototype.OperationDirect = function (stage, touch) {
        var _a;
        var _this = this;
        var allSelected = Core.State.API.GetAllSelectedComponents();
        if (allSelected.length === 0) {
            this.log("OperationDirect started, but no components selected");
            return;
        }
        var coordinates = { x: touch.x, y: touch.y };
        this.clearTimeouts();
        this.updateState(stage, coordinates);
        if (stage === Types_1.EStage.Start || stage === Types_1.EStage.None) {
            GhostInsertMode_1.GhostInsertMode.stopInstance();
            this.moveMode = touch.shiftKey
                ? Types_1.EMoveMode.Rotate
                : Types_1.EMoveMode.Translate;
            this.converter = new ig_gfx_threejs_1.ScreenWorldConverter(touch.cameraIndex);
            this.strategyFinder = new OperationStrategyFinder_1.OperationStrategyFinder(this.converter);
            this.currentState.selectedIds = this.filterSelection(allSelected);
            this.currentState.initialCoordinates = coordinates;
            var strategyParams = this.getStrategyParameters();
            this.currentState.operation = this.strategyFinder.getStrategy(strategyParams);
            // TODO 2019-09-10 should this really use the unfiltered selection?
            var neighborsOfSelection = lodash_1.default(allSelected)
                .flatMap(this.directNeighbors)
                .compact()
                .value();
            this.currentState.previousNeighbors = (_a = lodash_1.default(neighborsOfSelection)).without.apply(_a, tslib_1.__spread(allSelected)).uniq()
                .value();
            this.centerOfSelection = Util_1.calculateMiddlePointFromAllBoundingBoxes(this.currentState.selectedIds);
            this.updateNiches();
            // movement starts
            this.ignoreSnap = false;
            this.ignoreCollision = false;
            this.mouseAttachmentPoint = this.calculate3DAttachmentPoint();
            var planes = MouseTo3DMovement_1.MovementPlaneDecider.planes(this.converter, this.mouseAttachmentPoint, this.currentState.selectedIds, this._options.keepFloatingObjectsFloating);
            if (this._options.createMouseConverter) {
                this.mouseFollower = this._options.createMouseConverter(this.converter, this.mouseAttachmentPoint, planes);
            }
            else {
                this.mouseFollower = new MouseTo3DMovement_1.MouseTo3DMovement(this.converter, planes);
            }
        }
        else if (stage === Types_1.EStage.Motion) {
            if (this.moveMode === Types_1.EMoveMode.Translate) {
                // only in translate mode:
                // make niches visible so that the SnapCheckOperation also considers niche matches
                // relevant for e.g. T-Element in vifian (see Ticket 6303 in Redmine)
                this.makeAllNichesVisible();
                var ignoredComponents = this.currentState.selectedIds;
                this.hitInfoForScreenBasedChildMatches = Core.State.API.GetDetailedHitInfos(touch, ignoredComponents);
                this.makeAllNichesInvisible();
            }
            else {
                this.hitInfoForScreenBasedChildMatches = null;
            }
        }
        this.updateYRotationAngle(coordinates);
        var request = this.makeRequest(coordinates);
        if (stage === Types_1.EStage.Start || stage === Types_1.EStage.None) {
            this.currentState.operation.executeStart(request);
        }
        else if (stage === Types_1.EStage.Motion) {
            // in motion
            if (this.previousState.stage === Types_1.EStage.Start ||
                this.previousState.stage === Types_1.EStage.None) {
                this.onMoveStarted();
            }
            var tMotionResult = this.currentState.operation.executeMotion(request);
            this.hideAllGhosts();
            this.applyMotionResult(tMotionResult);
            // collect transformations for each component id to pass it to onMoveStepped
            var positionsById_1 = {};
            tMotionResult.transformations.forEach(function (trafo, index) {
                positionsById_1[_this.currentState.selectedIds[index]] = tslib_1.__assign(tslib_1.__assign({}, trafo), { rotation: "Not yet implemented" });
            });
            this.onMoveStepped(positionsById_1, !tMotionResult.isInvalid, tMotionResult.snapMatches);
        }
        else if (stage === Types_1.EStage.End) {
            // movement ends
            var tEndRequest = tslib_1.__assign(tslib_1.__assign({}, request), { ignoreSnap: this.ignoreSnap });
            var tEndResult = this.currentState.operation.executeEnd(tEndRequest);
            this.applyEndResult(tEndResult, this.previousState.duringMovement);
            this.removeAllGhosts();
            if (this.previousState.stage === Types_1.EStage.Motion) {
                this.onMoveEnded();
            }
        }
        else {
            throw new Error("Invalid Operation Mode for OperationDirect");
        }
    };
    /**
     * Intialize the operation.
     * @param touch The Touch supplied by the event (pushing the mouse button).
     */
    StandardInteractor.prototype.SingleTouchStart = function (touch) {
        this.log("SingleTouchStart started");
        this.OperationDirect(Types_1.EStage.Start, touch);
    };
    /**
     * Use the operation to calculate new positions and rotation for the components.
     * @param touch The touch from the mouse event.
     */
    StandardInteractor.prototype.SingleTouchMotion = function (touch) {
        this.log("SingleTouchMotion started");
        this.OperationDirect(Types_1.EStage.Motion, touch);
    };
    /**
     * Apply operation or cancel if result is not valid .
     * @param touch The touch from the mouse event
     */
    StandardInteractor.prototype.SingleTouchEnd = function (touch) {
        this.log("SingleTouchEnd started");
        this.OperationDirect(Types_1.EStage.End, touch);
    };
    /**
     * The Timeout to be called to release the snapping
     */
    StandardInteractor.prototype.ApplySnapTimeout = function () {
        this.ignoreSnap = true;
        this.clearSnapTimeout();
        this.hideAllGhosts();
        Core.State.API.RedrawScene();
    };
    /**
     * Activate the no collision mode.
     * Move object to the invalid ghost location after a certain amount
     * of time has passed with no movement.
     * @param ghostTransformations
     * @constructor
     */
    StandardInteractor.prototype.ApplyInvalidTimeout = function (ghostTransformations) {
        this.ignoreCollision = true;
        this.applyTransformation(this.currentState.selectedIds, ghostTransformations);
        this.hideAllGhosts();
        this.clearInvalidTimeout();
        Core.State.API.RedrawScene();
    };
    /**
     * Update niches cache
     */
    StandardInteractor.prototype.updateNiches = function () {
        var e_2, _a;
        this.niches.clear();
        var rootsWithParent = Core.State.API.GetRoots();
        try {
            for (var rootsWithParent_1 = tslib_1.__values(rootsWithParent), rootsWithParent_1_1 = rootsWithParent_1.next(); !rootsWithParent_1_1.done; rootsWithParent_1_1 = rootsWithParent_1.next()) {
                var root = rootsWithParent_1_1.value;
                var allNiches = MultiArticle.Niche.allNiches(root);
                this.niches.set(root, allNiches);
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (rootsWithParent_1_1 && !rootsWithParent_1_1.done && (_a = rootsWithParent_1.return)) _a.call(rootsWithParent_1);
            }
            finally { if (e_2) throw e_2.error; }
        }
    };
    /**
     * Make niches of roots visible
     */
    StandardInteractor.prototype.makeAllNichesVisible = function () {
        this.niches.forEach(function (niches) {
            niches.forEach(function (n) { return n.makeVisible(); });
        });
    };
    /**
     * Make niches of roots invisible
     */
    StandardInteractor.prototype.makeAllNichesInvisible = function () {
        this.niches.forEach(function (niches) {
            niches.forEach(function (n) { return n.makeInvisible(); });
        });
    };
    /**
     * Applies the rotations and positions from result to the given componentIds.
     * Length of componentIds has to be the same as the amount of
     * positions/rotations in the result.
     * @param componentIds the ids of components that will move
     * @param transformations
     */
    StandardInteractor.prototype.applyTransformation = function (componentIds, transformations) {
        var lenTrafo = transformations.length;
        for (var idx = 0; idx < lenTrafo; idx++) {
            var trafo = transformations[idx];
            var componentId = componentIds[idx];
            Core.State.API.SetTransformation(componentId, trafo);
        }
    };
    /**
     * Reset the Snap and Invalid Timeout
     */
    StandardInteractor.prototype.clearTimeouts = function () {
        this.clearSnapTimeout();
        this.clearInvalidTimeout();
    };
    /**
     * Reset invalid timeout
     */
    StandardInteractor.prototype.clearInvalidTimeout = function () {
        if (this.invalidTimer > 0) {
            clearTimeout(this.invalidTimer);
            this.invalidTimer = -1;
        }
    };
    /**
     * Reset snap timeout
     */
    StandardInteractor.prototype.clearSnapTimeout = function () {
        if (this.snapTimer > 0) {
            clearTimeout(this.snapTimer);
            this.snapTimer = -1;
        }
    };
    /**
     * Get the snap color for the passed snap mode.
     * @param mode The Snap mode.
     * @returns The corresponding Color
     */
    StandardInteractor.getSnapColor = function (mode) {
        switch (mode) {
            case Types_1.SnapMode.None:
                return new Core.ColorRGB(1, 0, 0);
            case Types_1.SnapMode.Weak:
            case Types_1.SnapMode.Wall:
                return new Core.ColorRGB(1, 1, 0);
            case Types_1.SnapMode.Strong:
                return new Core.ColorRGB(0, 1, 0);
            case Types_1.SnapMode.Disabled:
                return new Core.ColorRGB(0.5, 0, 0);
            default:
        }
        if (Core.State.API.AutoDeselectComponent) {
            return null;
        }
        return Core.State.API.PRIMARY_BBOX_COLOR;
    };
    /**
     * Calculate StrategyParameters based on the current state.
     */
    StandardInteractor.prototype.getStrategyParameters = function () {
        var allModes = [
            Types_1.SnapMode.Strong,
            Types_1.SnapMode.Weak,
            Types_1.SnapMode.Wall,
            Types_1.SnapMode.Disabled,
            Types_1.SnapMode.None,
        ];
        var result = {
            componentIds: this.currentState.selectedIds,
            translate: this.moveMode === Types_1.EMoveMode.Translate,
            rotate: this.moveMode === Types_1.EMoveMode.Rotate,
            ignoredModes: this._options.snapping.ignoreAll
                ? allModes
                : this._options.snapping.ignoredModes,
            ignoreCollisions: this._options.collisions.ignoreAll,
            rotationGridLength: this._options.grid.rotation || 0,
            movementGridLength: this._options.grid.movement || 0,
            bounds: this._options.bounds,
        };
        return result;
    };
    /**
     * If the log parameter is set, will log the message via IGX.Planning.PlannningLog
     * @param message
     */
    StandardInteractor.prototype.log = function (message) {
        if (StandardInteractor.log) {
            console.log(message);
        }
    };
    /**
     * @param result
     * @param coordinates
     * @returns {boolean}
     */
    StandardInteractor.prototype.applyMotionResult = function (result) {
        if (result.isInvalid && !this.ignoreCollision) {
            this.makeInvalidGhosts(result);
        }
        else {
            this.applyTransformation(this.currentState.selectedIds, result.transformations);
        }
        // Snap ghosts will override invalid ghosts!
        if (!result.isSnapInvalid && !this.ignoreSnap) {
            this.makeSnapGhosts(result);
        }
        return true;
    };
    /**
     * Make ghosts for invalid movement at the desired location
     * @param {MotionResult} result
     */
    StandardInteractor.prototype.makeInvalidGhosts = function (result) {
        var e_3, _a;
        var _this = this;
        if (!this._options.ghosts.collision) {
            return;
        }
        this.makeAllGhosts(StandardInteractor.invalidGhostMode);
        var ghostIds = this.currentState.selectedIds.map(function (id) { return Core.State.API.GetGhostIds(id)[0]; });
        // to prevent z-fighting with actual article, pull the ghost towards the camera
        var copyOfTransformations = new Array();
        var cameraPosition = this.converter.CameraPosition();
        try {
            for (var _b = tslib_1.__values(result.transformations), _c = _b.next(); !_c.done; _c = _b.next()) {
                var trafo = _c.value;
                var trafoWithOffset = ig_gfx_core_1.MathTypes.cloneTransformation(trafo);
                var direction = Core.Vector.Sub3(cameraPosition, trafoWithOffset.position);
                Core.Vector.NormalizeInPlace3(direction, 0.001);
                Core.Vector.AddOnFirst3(trafoWithOffset.position, direction);
                copyOfTransformations.push(trafoWithOffset);
            }
        }
        catch (e_3_1) { e_3 = { error: e_3_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_3) throw e_3.error; }
        }
        this.applyTransformation(ghostIds, copyOfTransformations);
        ghostIds.forEach(function (id) {
            return Core.State.API.Highlight(id, StandardInteractor.invalidPositionColor, true);
        });
        this.invalidTimer = setTimeout(function () { return _this.ApplyInvalidTimeout(result.transformations); }, StandardInteractor.defaultInvalidTimeout);
    };
    /**
     * Create ghosts for snapping with the correct color at the location
     * that is stored in result.snapMatches[0]
     * @param result
     */
    StandardInteractor.prototype.makeSnapGhosts = function (result) {
        var e_4, _a;
        var _this = this;
        if (!this._options.ghosts.snapping) {
            return;
        }
        var bestMatch = result.isSnapInvalid
            ? undefined
            : result.snapMatches[0];
        if (bestMatch === undefined) {
            return;
        }
        var mode = this._options.ghosts.mode;
        // bestMatch.representation === "Ghost2"
        // 	? Core.EGhostMode.Box
        // 	: Core.EGhostMode.Geometric;
        this.currentState.selectedIds.forEach(function (id) {
            return Core.State.API.SetupGhost(id, mode, true);
        });
        if (bestMatch.representation === "Ghost2") {
            var bounds = Core.State.API.GetBounds(bestMatch.firstId, "Local", true);
            var multiArticleConfig = this.articleConfigurations.get(bestMatch.firstId);
            var componentDimensions = {
                Width: bounds.max.x - bounds.min.x,
                Height: bounds.max.y - bounds.min.y,
                Depth: bounds.max.z - bounds.min.z,
            };
            var ghostConfig_1 = tslib_1.__assign({}, componentDimensions);
            var actions = bestMatch.getActions();
            var actionRules_1 = new Array();
            actions.forEach(function (action) { return actionRules_1.push.apply(actionRules_1, tslib_1.__spread(action.Rules)); });
            var properties_1 = tslib_1.__assign({ BBOX: MultiArticle.Rule.createBoundingBoxForRule(bestMatch.secondId) }, multiArticleConfig);
            actionRules_1.forEach(function (a) {
                var evaluatedAction = MultiArticle.Rule.evalActionRule(a, properties_1);
                if (evaluatedAction.isActive && evaluatedAction.ghostProperty) {
                    ghostConfig_1[evaluatedAction.ghostProperty] = parseFloat(evaluatedAction.value);
                }
            });
            var newScale = {
                x: ghostConfig_1.Width / componentDimensions.Width,
                y: ghostConfig_1.Height / componentDimensions.Height,
                z: ghostConfig_1.Depth / componentDimensions.Depth,
            };
            var theGhostId = Core.State.API.GetGhostIds(bestMatch.firstId)[0];
            Core.State.API.SetScaling(theGhostId, newScale);
            var ghostNode = Core.State.API.GetComponent(theGhostId);
            var component = Core.State.API.GetComponent(bestMatch.firstId);
            var bb = ThreeJS.GetLocalBBox(component);
            var ghostMesh = ghostNode.children[0];
            if (bb && ghostMesh) {
                var meshOffset = new THREE.Vector3(bb.min.x / newScale.x, bb.min.y / newScale.y, bb.min.z / newScale.z);
                ghostMesh.position.copy(meshOffset);
                ghostMesh.updateMatrix();
            }
        }
        var ghostIds = this.currentState.selectedIds.map(function (id) { return Core.State.API.GetGhostIds(id)[0]; });
        var snapTransformations = result.transformations.map(function (t) {
            return bestMatch.adaptTransformation(t);
        });
        // to prevent z-fighting with actual article, pull the ghost towards the camera
        var copyOfTransformations = new Array();
        var cameraPosition = this.converter.CameraPosition();
        try {
            for (var snapTransformations_1 = tslib_1.__values(snapTransformations), snapTransformations_1_1 = snapTransformations_1.next(); !snapTransformations_1_1.done; snapTransformations_1_1 = snapTransformations_1.next()) {
                var trafo = snapTransformations_1_1.value;
                var trafoWithOffset = ig_gfx_core_1.MathTypes.cloneTransformation(trafo);
                var direction = Core.Vector.Sub3(cameraPosition, trafoWithOffset.position);
                Core.Vector.NormalizeInPlace3(direction, 0.001);
                Core.Vector.AddOnFirst3(trafoWithOffset.position, direction);
                copyOfTransformations.push(trafoWithOffset);
            }
        }
        catch (e_4_1) { e_4 = { error: e_4_1 }; }
        finally {
            try {
                if (snapTransformations_1_1 && !snapTransformations_1_1.done && (_a = snapTransformations_1.return)) _a.call(snapTransformations_1);
            }
            finally { if (e_4) throw e_4.error; }
        }
        this.applyTransformation(ghostIds, copyOfTransformations);
        var snapColor = StandardInteractor.getSnapColor(bestMatch.snapMode);
        ghostIds.forEach(function (id) { return Core.State.API.Highlight(id, snapColor, true); });
        this.snapTimer = setTimeout(function () { return _this.ApplySnapTimeout(); }, StandardInteractor.defaultSnappingTimeout);
    };
    /**
     * Sets ghosts for each of the currently selected ids with the passed mode.
     * @param mode
     */
    StandardInteractor.prototype.makeAllGhosts = function (mode) {
        this.currentState.selectedIds.forEach(function (id) {
            return Core.State.API.SetupGhost(id, mode, true);
        });
    };
    /**
     * Applies the positions and rotations of the end result to the selected ids.
     * @param result the result
     * @param executeSnap true if a snap should be executed
     */
    StandardInteractor.prototype.applyEndResult = function (result, executeSnap) {
        var transformations = this.currentState.selectedIds.map(function (id) { return Core.getWorldTransformation(id); });
        // prefer snapping to final movement
        var doSnap = !result.isSnapInvalid && executeSnap;
        if (doSnap && result.snapMatches && result.snapMatches.length > 0) {
            transformations = result.transformations.map(function (t) {
                return result.snapMatches[0].adaptTransformation(t);
            });
        }
        else if (!result.isInvalid) {
            transformations = result.transformations;
        }
        var getRules = function (ac) { return ac.Rules; };
        this.applyTransformation(this.currentState.selectedIds, transformations);
        if (doSnap && result.snapMatches.length > 0) {
            var actions = result.snapMatches[0].getActions();
            var allActions = lodash_1.default.flatMap(actions, getRules);
            MultiArticle.Rule.executeActions(allActions, result.snapMatches[0].firstId, result.snapMatches[0].secondId);
            this.onSnapped(this.currentState.selectedIds, result.snapMatches[0].snapMode);
        }
        this.currentState.selectedIds.forEach(function (id) {
            var actions = Core.State.API.GetActions(id).filter(function (action) {
                return action.Mode === "SpatialDependency" ||
                    action.Mode === "NeighborDependency";
            });
            MultiArticle.Rule.executeActions(lodash_1.default.flatMap(actions, getRules), id, id);
        });
        this.executeNeighborActions();
    };
    /**
     * Apply the neighbor actions that are necessary after a movement has ended.
     */
    StandardInteractor.prototype.executeNeighborActions = function () {
        var _a;
        var getRules = function (ac) { return ac.Rules; };
        var currentNeighbors = lodash_1.default.flatMap(this.currentState.selectedIds, this.directNeighbors);
        var oldNeighbors = this.currentState.previousNeighbors;
        var relevantNeighbors = (_a = lodash_1.default(tslib_1.__spread(oldNeighbors, currentNeighbors))
            .uniq()
            .compact()).without.apply(_a, tslib_1.__spread(this.currentState.selectedIds));
        relevantNeighbors.forEach(function (id) {
            var actions = Core.State.API.GetActions(id).filter(function (action) {
                return action.Mode === "NeighborDependency";
            });
            MultiArticle.Rule.executeActions(lodash_1.default.flatMap(actions, getRules), id, id);
        });
    };
    /**
     * Hides all ghost of the selected Ids
     */
    StandardInteractor.prototype.hideAllGhosts = function () {
        this.makeAllGhosts(Core.EGhostMode.NoDisplay);
    };
    /**
     * Removes all ghost of the selected Ids
     */
    StandardInteractor.prototype.removeAllGhosts = function () {
        this.makeAllGhosts(Core.EGhostMode.None);
    };
    /**
     * Creates a Request containing the relative movement and/or
     * angle from the view coordinates.
     * @param coordinates the view coordinates (touch on the screen)
     * @returns {OperationRequest}
     */
    StandardInteractor.prototype.makeRequest = function (coordinates) {
        var newPosition = this.mouseFollower.transferCoordinates(coordinates);
        var angleDifference = this.currentState.angle - this.previousState.angle;
        var translation;
        if (newPosition && this.mouseAttachmentPoint) {
            translation = Core.Vector.Sub3(newPosition, this.mouseAttachmentPoint);
        }
        else {
            translation = Core.Vector.CreateVec3(0, 0, 0);
        }
        return tslib_1.__assign(tslib_1.__assign({}, translation), { angle: angleDifference, hitInfosBelowMouse: this.hitInfoForScreenBasedChildMatches });
    };
    /**
     * Sets the mouseAttachmentPoint member by casting a ray from the
     * camera to through the ViewCoordinates. If the ray intersects
     * an object, the intersection point is chosen,
     * otherwise we choose the 3D center of the current selection.
     */
    StandardInteractor.prototype.calculate3DAttachmentPoint = function () {
        var tRay = this.converter.ViewToWorldRay(this.currentState.coordinates);
        var tDistance = Core.State.API.GetDistance(tRay.origin, tRay.direction);
        if (tDistance !== -1) {
            var stretchedDirection = Core.Vector.ScalarMultiply3(tDistance, tRay.direction);
            var result = Core.Vector.Add3(tRay.origin, stretchedDirection);
            return result;
        }
        return this.centerOfSelection;
    };
    /** The default timeout for snapping. If after this timeout no motion or
     * release of mouse has occurred, we will stop snapping. */
    StandardInteractor.defaultSnappingTimeout = 1000;
    /** The default timeout for removing the collision check. */
    StandardInteractor.defaultInvalidTimeout = 1000;
    /** The color of the ghost that shows an invalid position */
    StandardInteractor.invalidPositionColor = new Core.ColorRGB(1, 0, 0);
    /** If set, we will log */
    StandardInteractor.log = false;
    /** The type of ghost to be used for showing invalid positions */
    StandardInteractor.invalidGhostMode = Core.EGhostMode.Geometric;
    /**
     * The type of ghost to be used for showing snapping positions
     * @Deprecated this is now regulated by the _options.ghosts.mode property
     */
    StandardInteractor.snappingGhostMode = Core.EGhostMode.Geometric;
    return StandardInteractor;
}(Core.Interactor));
exports.default = StandardInteractor;
