"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var Core = tslib_1.__importStar(require("@intelligentgraphics/ig.gfx.core"));
var MultiArticle = tslib_1.__importStar(require("@intelligentgraphics/ig.gfx.multiarticle"));
var ig_gfx_core_1 = require("@intelligentgraphics/ig.gfx.core");
var WorldLocalConverter_1 = tslib_1.__importDefault(require("../WorldLocalConverter"));
var Cache_1 = require("../Cache");
var SnapPoints_1 = tslib_1.__importDefault(require("./SnapPoints"));
var SnapMatches_1 = require("./SnapMatches");
var WallPointCalculator_1 = tslib_1.__importDefault(require("./WallPointCalculator"));
var Interactor_1 = require("../Interactor");
var GhostInsertMode_1 = require("../GhostInsertMode");
/** Class for calculating snap matches */
var SnapCalculator = /** @class */ (function () {
    /**
     * The constructor of the Snap Calculator. It calculates the snap points
     * @param state the state of the first component
     * @param snapToAll whether it is possible to snap to neighbor elements (for GhostMode)
     */
    function SnapCalculator(state, snapToAll) {
        var _this = this;
        if (snapToAll === void 0) { snapToAll = true; }
        /** If set to false, also return invalid matches (e.g. for initial positioning) */
        this.onlyValidMatches = true;
        /** Filter out matches that have no ghost representation */
        this.onlyGhostRepresentationMatches = false;
        /** Filter out matches that collide with other components */
        this.removeCollidingMatches = true;
        /** Return first match that is not colliding */
        this.onlyTakeFirstNonCollidingMatch = false;
        /** Filter out matches that are redundant */
        this.removeRedundantMatches = true;
        this._makeStrongNeighborMatchesLambda = function (pt1, pt2) {
            return new SnapMatches_1.StrongNeighborMatch(pt1, pt2, _this.first, _this.second);
        };
        this._makeStrongParentMatchesLambda = function (childPoint, parentPoint) {
            return new SnapMatches_1.StrongChildMatch(childPoint, parentPoint, _this.second, _this.first);
        };
        this._makeStrongChildMatchesLambda = function (pt1, pt2) {
            return new SnapMatches_1.StrongChildMatch(pt1, pt2, _this.first, _this.second);
        };
        this._makeWeakMatchesLambda = function (pt1, pt2) {
            return new SnapMatches_1.WeakSnapMatch(pt1, pt2, _this.first, _this.second);
        };
        this._makeWeakWallMatchesLambda = function (pt1, pt2) {
            return new SnapMatches_1.WallMatch(pt1, pt2, _this.first, _this.second, _this.firstPoints.weakSnapPoints);
        };
        this.snapToAll = snapToAll;
        this.first = state.id;
        this.firstPoints = new SnapPoints_1.default(state, this.snapToAll);
    }
    SnapCalculator.prototype.getScreenBasedChildMatches = function (state, converter, hitInfoScreenBased) {
        this.updateSecond(state);
        var matches = this.makeScreenBasedChildMatches(converter, hitInfoScreenBased);
        var sortedMatches = matches.sort(this.sortMatchesLambda);
        return sortedMatches;
    };
    SnapCalculator.prototype.updateSecond = function (state) {
        if (this.second !== state.id) {
            this.second = state.id;
            this.secondPoints = this.getSecondPoints(state);
        }
    };
    SnapCalculator.prototype.getSecondPoints = function (state) {
        if (SnapCalculator.useCache) {
            var tCachedPoints = Cache_1.Caches.snapPointsCache.get(this.second);
            if (tCachedPoints) {
                return tCachedPoints;
            }
        }
        var result = new SnapPoints_1.default(state, this.snapToAll);
        return result;
    };
    SnapCalculator.prototype.getStrongParentMatches = function (state) {
        this.updateSecond(state);
        var matches = this.makeStrongParentMatches();
        var sortedMatches = matches.sort(this.sortMatchesLambda);
        return sortedMatches;
    };
    SnapCalculator.prototype.getStrongChildMatches = function (state) {
        this.updateSecond(state);
        var matches = this.makeStrongChildMatches();
        var sortedMatches = matches.sort(this.sortMatchesLambda);
        return sortedMatches;
    };
    SnapCalculator.prototype.getStrongNeighborMatches = function (state) {
        this.updateSecond(state);
        var matches = this.makeStrongNeighborMatches();
        var sortedMatches = matches.sort(this.sortMatchesLambda);
        return sortedMatches;
    };
    SnapCalculator.prototype.getWeakWallMatches = function (state, desiredComponentTransformation) {
        this.updateSecond(state);
        var matches = this.makeWeakWallMatches(desiredComponentTransformation);
        var sortedMatches = matches.sort(this.sortMatchesLambda);
        return sortedMatches;
    };
    SnapCalculator.prototype.getWeakMatches = function (state) {
        this.updateSecond(state);
        var matches = this.makeWeakMatches();
        var sortedMatches = matches.sort(this.sortMatchesLambda);
        return sortedMatches;
    };
    /**
     * Calculates all possible strong matches
     * @returns {SnapMatch[]}
     */
    SnapCalculator.prototype.makeStrongNeighborMatches = function () {
        return this.makeMatches(this.firstPoints.strongNeighborSnapPoints, this.secondPoints.strongNeighborSnapPoints, this._makeStrongNeighborMatchesLambda, SnapMatches_1.StrongNeighborMatch.isCoreMatch);
    };
    SnapCalculator.prototype.makeScreenBasedChildMatches = function (converter, hitInfoScreenBased) {
        var _this = this;
        var mousePositionOnScreen = converter.WorldToView(hitInfoScreenBased.hitPoint);
        var matchGenerator = function (childPoint, parentPoint) {
            var childPointOnScreen = mousePositionOnScreen; //converter.WorldToView(childPoint.point);
            var parentPointOnScreen = converter.WorldToView(parentPoint.transformedPoint);
            return new SnapMatches_1.ScreenBasedChildMatch(childPoint, parentPoint, _this.first, _this.second, childPointOnScreen, parentPointOnScreen);
        };
        var result = this.makeMatches(this.firstPoints.strongChildPoints, this.secondPoints.strongChildPoints, matchGenerator, SnapMatches_1.ScreenBasedChildMatch.isCoreMatch);
        // traverse to node that possibly is a niche and prefer it
        var nicheComponent = hitInfoScreenBased.intersectedComponent;
        if (nicheComponent &&
            nicheComponent.userData.layer &&
            MultiArticle.Niche.nicheRegex.test(nicheComponent.userData.layer)) {
            var matchesInPreferredNiche = result.filter(function (match) {
                return match.secondId === nicheComponent.name;
            });
            // it may be that the wrong niche was in focus
            if (matchesInPreferredNiche.length > 0) {
                result = matchesInPreferredNiche;
            }
        }
        return result;
    };
    /**
     * Calculates all strong parent matches, i.e. the strong child matches with the order reversed.
     * @returns {SnapMatch[]}
     */
    SnapCalculator.prototype.makeStrongParentMatches = function () {
        return this.makeMatches(this.secondPoints.strongChildPoints, this.firstPoints.strongChildPoints, this._makeStrongParentMatchesLambda, SnapMatches_1.StrongChildMatch.isCoreMatch);
    };
    /**
     * Calculates all possible weak matches
     * @returns {SnapMatch[]}
     */
    SnapCalculator.prototype.makeStrongChildMatches = function () {
        return this.makeMatches(this.firstPoints.strongChildPoints, this.secondPoints.strongChildPoints, this._makeStrongChildMatchesLambda, SnapMatches_1.StrongChildMatch.isCoreMatch);
    };
    /**
     * Calculates all possible weak matches
     * @returns {SnapMatch[]}
     */
    SnapCalculator.prototype.makeWeakMatches = function () {
        return this.makeMatches(this.firstPoints.weakSnapPoints, this.secondPoints.weakSnapPoints, this._makeWeakMatchesLambda, SnapMatches_1.WeakSnapMatch.isCoreMatch);
    };
    /**
     * Abstract match making algorithm.
     * @param firstPoints the points to consider for the first component
     * @param secondPoints the points to consider for the second component
     * @param matchGenerator a function that generates SnapMatch objects from the two points.
     * @returns {SnapMatch[]} All valid snap matches
     */
    SnapCalculator.prototype.makeMatches = function (firstPoints, secondPoints, matchGenerator, matchValidator) {
        var matches = new Array();
        var len1 = firstPoints.length;
        var len2 = secondPoints.length;
        for (var idx1 = 0; idx1 < len1; idx1++) {
            var pt1 = firstPoints[idx1];
            for (var idx2 = 0; idx2 < len2; idx2++) {
                var pt2 = secondPoints[idx2];
                // check if match would be valid
                if (matchValidator && this.onlyValidMatches) {
                    if (!matchValidator(pt1, pt2)) {
                        continue;
                    }
                }
                var possibleMatch = matchGenerator(pt1, pt2);
                matches.push(possibleMatch);
            }
        }
        if (this.onlyValidMatches) {
            matches = matches.filter(function (match) { return match.isValid(); });
        }
        return matches;
    };
    /**
     * Sorting function for matches.
     * @param a the first match
     * @param b the second match
     * @returns {number}
     */
    SnapCalculator.prototype.sortMatchesLambda = function (a, b) {
        if (a.distance < b.distance) {
            return -1;
        }
        if (a.distance === b.distance) {
            return 0;
        }
        return 1;
    };
    SnapCalculator.prototype.makeWeakWallMatches = function (desiredComponentTransformation) {
        var calculator = new WallPointCalculator_1.default(this.second, this.first, desiredComponentTransformation);
        var componentPoint = calculator.getComponentPoint();
        var wallPoint = calculator.getWallPoint();
        return this.makeMatches([componentPoint], [wallPoint], this._makeWeakWallMatchesLambda);
    };
    // FROM GHOSTINSERTMODE
    /**
     *
     * @param componentId
     * @param componentConfiguration
     * @param rootsToExamine
     */
    SnapCalculator.prototype.getViableMatches = function (componentId, trafo, componentConfiguration, rootsToExamine, converter, hitInfos, sortFunction) {
        var matches = new Array();
        var lenRoots = rootsToExamine.length;
        var _loop_1 = function (idxRoot) {
            var rootId = rootsToExamine[idxRoot];
            var rootState = {
                id: rootId,
            };
            //// SCREEN BASED CHILD MATCHES
            if (converter && hitInfos && hitInfos.length > 0) {
                // ERSTMAL NUR DEN ERSTEN HIT NEHMEN
                var matchingHitInfos = hitInfos.filter(function (tHitInfo) { return rootId === tHitInfo.rootComponentId; });
                if (matchingHitInfos.length > 0) {
                    var screenBasedChildMatches = this_1.getScreenBasedChildMatches(rootState, converter, matchingHitInfos[0]);
                    matches.push.apply(matches, tslib_1.__spread(screenBasedChildMatches));
                }
            }
            else {
                // NORMAL CHILD MATCHES
                var childMatches = this_1.getStrongChildMatches(rootState).filter(function (match) { return match.isCoreMatch; });
                matches.push.apply(matches, tslib_1.__spread(childMatches));
            }
            var neighborMatches = this_1.getStrongNeighborMatches(rootState).filter(function (match) { return match.isCoreMatch; });
            matches.push.apply(matches, tslib_1.__spread(neighborMatches));
        };
        var this_1 = this;
        for (var idxRoot = 0; idxRoot < lenRoots; idxRoot++) {
            _loop_1(idxRoot);
        }
        var filteredMatches = matches;
        if (this.onlyGhostRepresentationMatches) {
            filteredMatches = filteredMatches.filter(function (match) { return match.hasGhostModeRepresentation; });
        }
        if (sortFunction) {
            filteredMatches = sortFunction(filteredMatches);
        }
        if (this.removeCollidingMatches) {
            var matchesWithoutCollision = [];
            var lenFM = filteredMatches.length;
            for (var idxFM = 0; idxFM < lenFM; idxFM++) {
                var cachedMatch = filteredMatches[idxFM];
                var isViable = this.isViableForInsertedId(componentId, //e.g. tablar
                trafo, cachedMatch, componentConfiguration, rootsToExamine);
                if (isViable) {
                    if (this.onlyTakeFirstNonCollidingMatch) {
                        return [cachedMatch];
                    }
                    matchesWithoutCollision.push(cachedMatch);
                }
            }
            filteredMatches = matchesWithoutCollision;
        }
        if (this.removeRedundantMatches) {
            filteredMatches = Interactor_1.removeRedundantMatches(filteredMatches);
        }
        return filteredMatches;
    };
    // FROM GHOSTINSERTMODE
    /**
     * Check if it is viable to create a ghost at the given position / rotation.
     * A ghost is viable if it does not intersect another object or room.
     * @param componentId  //e.g. tablar
     * @param match
     * @returns {boolean}
     */
    SnapCalculator.prototype.isViableForInsertedId = function (componentId, desiredWorldTrafo, match, componentConfiguration, otherRoots) {
        var worldTrafo;
        if (desiredWorldTrafo) {
            worldTrafo = desiredWorldTrafo;
        }
        else {
            worldTrafo = Core.getWorldTransformation(componentId);
        }
        var transformation = match.adaptTransformation(worldTrafo);
        // everything below the ground is invalid (can't see it)
        if (transformation.position.y <= -0.01) {
            return false;
        }
        var updateComponentRegions = Core.State.API.RegionManager.updateComponentRegions;
        Core.State.API.RegionManager.updateComponentRegions = function () { return []; };
        // TODO: NOCH EINFÜGEN ODER IM KONTEXT ANWENDEN
        // if (GhostInsertMode.instance) {
        // 	const blockedPos = GhostInsertMode.instance.blockedPositions;
        // 	const isBlocked = blockedPos.some(blockedTransformation => {
        // 		if (
        // 			Vector.IsEqual3(
        // 				blockedTransformation.position,
        // 				transformation.position
        // 			) &&
        // 			Vector.IsEqual3(
        // 				blockedTransformation.rotation,
        // 				transformation.rotation
        // 			)
        // 		) {
        // 			return true;
        // 		}
        // 	});
        // 	if (isBlocked) {
        // 		return false;
        // 	}
        // }
        var oldScaling;
        // get current tablar size
        var articleBounds = Core.State.API.GetBounds(componentId, "Local", false);
        var resetScale = function () {
            if (oldScaling !== undefined) {
                Core.State.API.SetScaling(componentId, oldScaling);
            }
        };
        var converter = new WorldLocalConverter_1.default(match.secondId); //secondid = corpus
        //								corpus           tablarposition
        var relativeSnapPosition = converter.toLocal(transformation.position);
        //#region tablar rescaled to size desired by actions
        var matchActions = match.getActions();
        var matchActionRules = new Array();
        matchActions.forEach(function (mAction) {
            return matchActionRules.push.apply(matchActionRules, tslib_1.__spread(mAction.Rules));
        });
        var bboxData = GhostInsertMode_1.GhostInsertMode.getEvaluatedBBox(match.secondId, matchActionRules, componentConfiguration, relativeSnapPosition);
        var articleSize = Core.getBoundsSize(articleBounds);
        var newBounds = {
            min: ig_gfx_core_1.Vector.CreateVec3(),
            max: {
                x: bboxData.Width != null ? bboxData.Width : articleSize.x,
                y: bboxData.Height != null ? bboxData.Height : articleSize.y,
                z: bboxData.Depth != null ? bboxData.Depth : articleSize.z,
            },
        };
        var calcScaleForDim = function (newBoundsParam, oldBoundsParam, dim) {
            return (newBoundsParam.max[dim] - newBoundsParam.min[dim]) /
                (oldBoundsParam.max[dim] - oldBoundsParam.min[dim]);
        };
        var scale = {
            x: calcScaleForDim(newBounds, articleBounds, "x"),
            y: calcScaleForDim(newBounds, articleBounds, "y"),
            z: calcScaleForDim(newBounds, articleBounds, "z"),
        };
        articleBounds = newBounds;
        oldScaling = Core.State.API.GetScaling(componentId);
        Core.State.API.SetScaling(componentId, scale);
        //#endregion
        // filter out direct parents to allow children to be integrated into parent beyond the tolerance of 1cm.
        // e.g. HeadRest into Couch
        // do not exclude grand parents since the collision tolerance is now working e.g. vifian pure tablar in NicheContainer in Korpus-Element
        if (match instanceof SnapMatches_1.StrongChildMatch ||
            match instanceof SnapMatches_1.ScreenBasedChildMatch) {
            otherRoots = otherRoots.filter(function (root) { return root !== match.secondRootId; });
        }
        // exclude matching neighbor from collision
        // e.g. couch elements with overlapping upholstery next to each other
        if (match instanceof SnapMatches_1.StrongNeighborMatch) {
            otherRoots = otherRoots.filter(function (root) { return root !== match.secondRootId; });
        }
        // check for collisions with other components
        var isCollisionWithObject = false;
        for (var idxOR = 0, lenOR = otherRoots.length; idxOR < lenOR; idxOR++) {
            var otherRoot = otherRoots[idxOR];
            var tOtherRootTransform = Core.getWorldTransformation(otherRoot);
            var collisionInformation = Core.LinearAlgebra.wouldComponentsCollide(componentId, otherRoot, transformation, tOtherRootTransform, 0.01);
            if (collisionInformation && collisionInformation.isColliding) {
                isCollisionWithObject = true;
                break;
            }
        }
        if (isCollisionWithObject) {
            if (GhostInsertMode_1.GhostInsertMode.instance) {
                GhostInsertMode_1.GhostInsertMode.instance.addBlockedPosition(transformation);
            }
            resetScale();
            Core.State.API.RegionManager.updateComponentRegions = updateComponentRegions;
            return false;
        }
        // check for collisions with walls
        // VOM PARAMETER otherRoots BEHANDELT
        resetScale();
        Core.State.API.RegionManager.updateComponentRegions = updateComponentRegions;
        return true;
    };
    SnapCalculator.useCache = false;
    return SnapCalculator;
}());
exports.default = SnapCalculator;
