import { __read, __spread } from "tslib";
import _ from "lodash";
import * as Core from "@intelligentgraphics/ig.gfx.core";
import { AbstractBoundingBox, } from "@intelligentgraphics/ig.gfx.threejs";
import Environment from "../../Environment";
import { WallUtilities } from "../../WallUtilities";
import SnapCalculator from "./../SnapCalculator";
import { SnapMode } from "../../Types";
import { children as calculateChildren } from "../../Associates";
import { SnapMatch, ScreenBasedChildMatch, removeRedundantMatches, } from "../SnapMatches";
/** Operation that checks snapping. */
var SnapCheckOperation = /** @class */ (function () {
    /**
     * Constructor of the SnapCheckOperation
     * @param operation The operation that will be computed before looking for snapping
     * @param componentIds The ids of moved components
     * @param converter
     * @param disabledModes
     */
    function SnapCheckOperation(operation, componentIds, converter, disabledModes) {
        if (disabledModes === void 0) { disabledModes = []; }
        /** The ids of all relevant roots (i.e. roots that are not in componentIds). */
        this.relevantRoots = [];
        this.operation = operation;
        this.componentIds = componentIds;
        this.converter = converter;
        this.disabledModes = disabledModes;
    }
    SnapCheckOperation.prototype.executeStart = function (request) {
        this.operation.executeStart(request);
        this.relevantRoots = _.without.apply(_, __spread([Core.State.API.GetRoots(),
            Environment.getInstance().getRoot()], this.componentIds));
    };
    SnapCheckOperation.prototype.executeMotion = function (request) {
        var tMotionResult = this.operation.executeMotion(request);
        this.calculateMatches(tMotionResult.transformations, request.hitInfosBelowMouse);
        return {
            transformations: tMotionResult.transformations,
            isInvalid: tMotionResult.isInvalid,
            snapMatches: this.matches,
            isSnapInvalid: false,
        };
    };
    /**
     * Calculate the current matches and sort them.
     * Considered types: StrongNeighbour/StrongChild/Weak/WallMatch
     * Afterwards, sort matches by relevance
     * @param transformations
     * @param hitInfosScreenBased
     */
    SnapCheckOperation.prototype.calculateMatches = function (transformations, hitInfosScreenBased) {
        var _a;
        var _this = this;
        this.matches = new Array();
        var viableSnapPartners = this.relevantRoots.concat(Environment.getInstance().getWalls());
        if (!viableSnapPartners || viableSnapPartners.length === 0) {
            return;
        }
        this.componentIds.forEach(function (componentId, index) {
            var _a;
            var componentState = {
                id: componentId,
                transformation: transformations[index],
            };
            var calc = new SnapCalculator(componentState);
            calc.onlyGhostRepresentationMatches = false;
            calc.removeCollidingMatches = SnapCheckOperation.considerCollisions;
            calc.onlyTakeFirstNonCollidingMatch = true;
            var nearbyRoots = Core.State.API.RegionManager.getNearbyComponents([componentId], SnapMatch.maxDistance, "distance");
            if (hitInfosScreenBased) {
                hitInfosScreenBased.forEach(function (hitInfo) {
                    if (nearbyRoots.indexOf(hitInfo.rootComponentId) === -1) {
                        nearbyRoots.push(hitInfo.rootComponentId);
                        var children = calculateChildren(hitInfo.rootComponentId);
                        nearbyRoots.push.apply(nearbyRoots, __spread(children));
                    }
                });
            }
            nearbyRoots = __spread(new Set(nearbyRoots));
            var relevantNearbyRoots = nearbyRoots.filter(function (root) { return _this.relevantRoots.indexOf(root) > -1; });
            var component = Core.State.API.GetComponent(componentId);
            var multiArticleConfig = component.userData.multiArticleConfiguration;
            var additionalMatches = calc.getViableMatches(componentId, transformations[index], multiArticleConfig, relevantNearbyRoots, _this.converter, hitInfosScreenBased, _this.sortMatches);
            (_a = _this.matches).push.apply(_a, __spread(additionalMatches));
        });
        var wallMatches = new Array();
        if (this.disabledModes.indexOf(SnapMode.Wall) === -1) {
            wallMatches = this.getWallMatches(transformations);
        }
        (_a = this.matches).push.apply(_a, __spread(wallMatches));
        this.matches = removeRedundantMatches(this.matches);
        this.matches = this.sortMatches(this.matches);
    };
    /**
     * Calculate the matches for snapping to the wall.
     * Only considers wall matches for the closest wall
     * @param transformations
     * @returns {SnapMatch[]}
     */
    SnapCheckOperation.prototype.getWallMatches = function (transformations) {
        var walls = Environment.getInstance().getWalls();
        if (!walls || walls.length === 0) {
            return [];
        }
        var closestWallsWithDistance = new Array();
        this.componentIds.forEach(function (componentId, index) {
            var transformation = transformations[index];
            var wall = WallUtilities.closestWall(componentId, transformation);
            if (!wall) {
                return;
            }
            var bounds = Core.State.API.GetBounds(componentId, "Local", true);
            var abb = new AbstractBoundingBox();
            abb.setFromTransformation(transformation, bounds);
            var componentPoints = abb.getPoints();
            var distance = WallUtilities.distanceOfPointsToWall(componentPoints, wall);
            var wallWithDistance = {
                wallId: wall,
                id: componentId,
                transformation: transformation,
                distance: distance,
            };
            closestWallsWithDistance.push(wallWithDistance);
        });
        if (closestWallsWithDistance.length === 0) {
            return [];
        }
        closestWallsWithDistance.sort(function (a, b) {
            return a.distance < b.distance ? -1 : a.distance > b.distance ? 1 : 0;
        });
        var _a = __read(closestWallsWithDistance, 1), closest = _a[0];
        if (closest.wallId && !SnapCheckOperation.ignoreWallSnap) {
            var calc = new SnapCalculator({
                id: closest.id,
                transformation: closest.transformation,
            });
            var weakWallMatches = calc.getWeakWallMatches({
                id: closest.wallId,
            }, closest.transformation);
            return weakWallMatches;
        }
        return [];
    };
    SnapCheckOperation.prototype.executeEnd = function (request) {
        var tEndResult = this.operation.executeEnd(request);
        if (request.ignoreSnap) {
            this.log("executeEnd: snap ignored");
            return tEndResult;
        }
        this.calculateMatches(tEndResult.transformations, request.hitInfosBelowMouse);
        var noMatches = !this.matches || this.matches.length === 0;
        return {
            transformations: tEndResult.transformations,
            isInvalid: tEndResult.isInvalid,
            snapMatches: this.matches,
            isSnapInvalid: noMatches,
        };
    };
    /**
     * Sort the matches member array by different priorities
     */
    SnapCheckOperation.prototype.sortMatches = function (unsorted) {
        unsorted.sort(function (a, b) {
            // first priority : snapMode
            if (a.snapMode < b.snapMode) {
                return -1;
            }
            if (a.snapMode > b.snapMode) {
                return 1;
            }
            // for screenbased matches compare screenDistances
            if (a instanceof ScreenBasedChildMatch &&
                b instanceof ScreenBasedChildMatch) {
                if (a.screenDistance < b.screenDistance) {
                    return -1;
                }
                if (a.screenDistance > b.screenDistance) {
                    return 1;
                }
                return 0;
            }
            // prefer screenbased matches to non-screen based matches
            if (a instanceof ScreenBasedChildMatch) {
                return -1;
            }
            if (b instanceof ScreenBasedChildMatch) {
                return 1;
            }
            // prefer shorter distances
            if (a.distance < b.distance) {
                return -1;
            }
            if (a.distance > b.distance) {
                return 1;
            }
            return 0;
        });
        return unsorted;
    };
    /**
     * Logs the message, if the log variable is set
     * @param message the message to be logged
     */
    SnapCheckOperation.prototype.log = function (message) {
        if (SnapCheckOperation.log) {
            console.log("SnapCheck: " + message);
        }
    };
    /** Indicates if snap events should be logged. */
    SnapCheckOperation.log = false;
    /** If set to true, wall snap is ignored. Used for measurements mode */
    SnapCheckOperation.ignoreWallSnap = false;
    /** Option for SnapCalculator, default true */
    SnapCheckOperation.considerCollisions = true;
    return SnapCheckOperation;
}());
export { SnapCheckOperation };
