import { __extends, __read, __spread, __values } from "tslib";
import _ from "lodash";
import * as Core from "@intelligentgraphics/ig.gfx.core";
import { LinearAlgebra, } from "@intelligentgraphics/ig.gfx.core";
import { AbstractBoundingBox } from "@intelligentgraphics/ig.gfx.threejs";
import Environment from "../../Environment";
import * as Associates from "../../Associates";
import { NeighborType } from "../../Types";
import { ProcessFurtherOperation } from "./ProcessFurtherOperation";
/**
 * Class for checking if the position that objects will be moved / rotated to is invalid due to collisions
 * If so, it resets the position (and rotation) to the last known valid one.
 */
var CollisionCheckOperation = /** @class */ (function (_super) {
    __extends(CollisionCheckOperation, _super);
    /**
     * The constructor. Sets the other mode that will be executed.
     * @param preceedingOperation
     * @param componentIds The ids of the involved components.
     */
    function CollisionCheckOperation(preceedingOperation, componentIds) {
        var _this = _super.call(this, preceedingOperation) || this;
        _this.componentIds = componentIds;
        return _this;
    }
    CollisionCheckOperation.prototype.processMotionResult = function (resultParam) {
        var validMatches = this.validMatches(resultParam);
        var isMotionInvalid = this.isMotionInvalid(resultParam);
        var result = {
            transformations: resultParam.transformations,
            isInvalid: isMotionInvalid,
            snapTo: resultParam.snapTo,
            snapMatches: validMatches,
            isSnapInvalid: validMatches.length === 0,
        };
        return result;
    };
    /**
     * Checks if the motion result has invalid transformations
     * @param result
     * @returns {boolean}
     */
    CollisionCheckOperation.prototype.isMotionInvalid = function (result) {
        var collidableComponents = this.getCollidableComponents();
        var isInvalid = this.isInvalid(result.transformations, collidableComponents, Core.State.maxBBoxOverlap);
        return isInvalid;
    };
    CollisionCheckOperation.prototype.getCollidableComponents = function () {
        var _a;
        var canCollideWithWalls = this.componentIds.every(function (component) {
            return Core.Category.hasCategory(component, Core.Category.CuttingThroughWall);
        });
        var wallCollidableComponents = canCollideWithWalls
            ? []
            : Environment.getInstance().getWalls();
        var result = (_a = _(Core.State.API.GetRoots())
            .without(Environment.getInstance().getRoot())).push.apply(_a, __spread(wallCollidableComponents)).value();
        return result;
    };
    /**
     * Checks if the transformation assigned by snapTransformations results in an invalid snap
     * @param snapTransformations the transformations of the selected components due to the snap
     * @param snapTo the component to snap to
     * @param ignoreSnapCollision if set, ignore collision with the snapped object
     * @returns {any}
     */
    CollisionCheckOperation.prototype.isSnapInvalid = function (snapTransformations, snapTo, ignoreSnapCollision) {
        this.log("is Snap Invalid started");
        if (!snapTo) {
            return false;
        }
        var ignoredRoots = Associates.getParents([snapTo], true).concat([snapTo]);
        var tCollidableComponents = this.getCollidableComponents();
        var tFilteredCollidableComponents = tCollidableComponents.filter(function (id) { return ignoredRoots.indexOf(id) === -1; });
        var collisionInfoWithRootsItDoesNotSnapTo = this.isCollision(tFilteredCollidableComponents, snapTransformations, Core.State.maxBBoxOverlap);
        if (collisionInfoWithRootsItDoesNotSnapTo &&
            collisionInfoWithRootsItDoesNotSnapTo.isColliding) {
            this.log("Collides with Root");
            return true;
        }
        if (!ignoreSnapCollision) {
            var snapCollisionInfo = this.isCollision(snapTo ? [snapTo] : [], snapTransformations, Core.State.maxBBoxOverlap * 10);
            if (snapCollisionInfo && snapCollisionInfo.isColliding) {
                this.log("Collides with Snap");
                return true;
            }
        }
        return false;
        // deprecated: no one knows about this code
        // const isSomePointUnsupported = this.isSomePointUnsupported(
        // 	snapTransformations,
        // 	Core.State.maxBBoxOverlap * 2.0
        // );
        // return isSomePointUnsupported;
    };
    /**
     * Checks if the transformation of the selected components will result in an invalid position
     * @param transformations the transformation
     * @param componentsToCheck the components we want to check collision with
     * @param error the amount of overlap allowed
     * @returns {boolean}
     */
    CollisionCheckOperation.prototype.isInvalid = function (transformations, componentsToCheck, error) {
        var collisionInfo = this.isCollision(componentsToCheck, transformations, error);
        if (collisionInfo && collisionInfo.isColliding) {
            return true;
        }
        var isBelowGround = transformations.some(function (transformation) { return transformation.position.y <= -0.01; });
        if (isBelowGround) {
            return true;
        }
        return false;
        // deprecated: no one knows about this code
        // const isSomePointUnsupported = this.isSomePointUnsupported(
        // 	transformations,
        // 	error
        // );
        // return isSomePointUnsupported;
    };
    /**
     * Check if a point in the passed transformation is unsupported, i.e. if it is hovering in the air unsupported
     * @param transformations
     * @param error the amount by which to reduce the bounding box to allow for errors e.g. in modeling
     * @returns {boolean}
     */
    CollisionCheckOperation.prototype.isSomePointUnsupported = function (transformations, error) {
        var _this = this;
        var isEveryComponentFloating = this.componentIds.every(function (id) {
            return Core.Category.hasCategory(id, Core.Category.Floating);
        });
        if (isEveryComponentFloating) {
            return false;
        }
        var isEveryTrafoOnFloor = transformations.every(function (transformation) { return transformation.position.y <= 0.01; });
        if (isEveryTrafoOnFloor) {
            return false;
        }
        var idsToCheck = this.componentIds.filter(function (id, index) {
            var tBottomNeighbor = Associates.neighbor(id, NeighborType.Bottom);
            if (_.find(_this.componentIds, function (componentId) { return componentId === tBottomNeighbor; })) {
                return false;
            }
            var tParent = Associates.parent(id);
            if (_.includes(_this.componentIds, tParent)) {
                return false;
            }
            return transformations[index].position.y >= 0.01;
        });
        var tPotentiallyUnsupportedPoints = idsToCheck
            .map(function (id, index) {
            var localBounds = Core.State.API.GetBounds(id, "Local", true);
            if (!localBounds) {
                return [];
            }
            var errorVector = { x: error, y: error, z: error };
            localBounds.min = Core.Vector.Add3(localBounds.min, errorVector);
            localBounds.max = Core.Vector.Sub3(localBounds.max, errorVector);
            var bounds = new AbstractBoundingBox();
            bounds.setFromTransformation(transformations[index], localBounds);
            return bounds.getPoints();
        })
            .reduce(function (a, b) { return a.concat(b); }, []);
        var tIsSomePointUnsupported = tPotentiallyUnsupportedPoints.some(function (point) {
            return Core.State.API.GetDistance(point, new Core.Float3(0, -1, 0)) ===
                -1;
        });
        if (tIsSomePointUnsupported) {
            this.log("isInvalid: there is an unsupported point");
        }
        return tIsSomePointUnsupported;
    };
    /**
     * check if there is a collision of the selected components and the passed components
     * @param componentsToCheck the components against which to check for collision
     * @param transformations
     * @param error the amount by which to reduce the bounding box to allow for some overlap
     * @returns {boolean}
     */
    CollisionCheckOperation.prototype.isCollision = function (componentsToCheck, transformations, error) {
        var e_1, _a, e_2, _b;
        var relevantComponents = new Array();
        try {
            for (var componentsToCheck_1 = __values(componentsToCheck), componentsToCheck_1_1 = componentsToCheck_1.next(); !componentsToCheck_1_1.done; componentsToCheck_1_1 = componentsToCheck_1.next()) {
                var ctc = componentsToCheck_1_1.value;
                if (this.componentIds.indexOf(ctc) === -1) {
                    relevantComponents.push(ctc);
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (componentsToCheck_1_1 && !componentsToCheck_1_1.done && (_a = componentsToCheck_1.return)) _a.call(componentsToCheck_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        var worldTransformations = {};
        relevantComponents.forEach(function (relevantId) {
            worldTransformations[relevantId] = Core.getWorldTransformation(relevantId);
        });
        var collisionInfo;
        var tCollidingComponent;
        try {
            for (var relevantComponents_1 = __values(relevantComponents), relevantComponents_1_1 = relevantComponents_1.next(); !relevantComponents_1_1.done; relevantComponents_1_1 = relevantComponents_1.next()) {
                var relevantId = relevantComponents_1_1.value;
                var trafoRelevantComponent = worldTransformations[relevantId];
                for (var idx = 0; idx < this.componentIds.length; idx++) {
                    var compId = this.componentIds[idx];
                    var trafo = transformations[idx];
                    collisionInfo = LinearAlgebra.wouldComponentsCollide(compId, relevantId, trafo, trafoRelevantComponent, error);
                    if (collisionInfo && collisionInfo.isColliding) {
                        tCollidingComponent = relevantId;
                        break;
                    }
                }
                if (collisionInfo && collisionInfo.isColliding) {
                    break;
                }
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (relevantComponents_1_1 && !relevantComponents_1_1.done && (_b = relevantComponents_1.return)) _b.call(relevantComponents_1);
            }
            finally { if (e_2) throw e_2.error; }
        }
        if (collisionInfo && collisionInfo.isColliding) {
            this.log("isInvalid: there is a collision with " + tCollidingComponent);
        }
        return collisionInfo;
    };
    CollisionCheckOperation.prototype.processEndResult = function (result) {
        var validMatches = this.validMatches(result);
        var isMotionInvalid = this.isMotionInvalid(result);
        return {
            transformations: result.transformations,
            isInvalid: isMotionInvalid,
            snapTo: result.snapTo,
            snapMatches: validMatches,
            isSnapInvalid: validMatches.length === 0,
        };
    };
    /**
     * Logs the message, if the log variable is set
     * @param message the message to be logged
     *
     */
    CollisionCheckOperation.prototype.log = function (message) {
        if (CollisionCheckOperation.log) {
            console.log(message);
        }
    };
    /**
     * Get all valid matches from the result
     * @param resultParam
     * @returns {Array<SnapMatch>}
     */
    CollisionCheckOperation.prototype.validMatches = function (resultParam) {
        var beginningIdx = 0;
        var _loop_1 = function (idx) {
            var match = resultParam.snapMatches[idx];
            var adaptedTransformations = resultParam.transformations.map(function (transformation) { return match.adaptTransformation(transformation); });
            if (this_1.isSnapInvalid(adaptedTransformations, match.secondRootId, match.canIntersectSnappedTo)) {
                beginningIdx = idx;
            }
            else {
                return "break";
            }
        };
        var this_1 = this;
        for (var idx = 0; idx < resultParam.snapMatches.length; idx++) {
            var state_1 = _loop_1(idx);
            if (state_1 === "break")
                break;
        }
        var result = resultParam.snapMatches.slice(beginningIdx);
        return result;
    };
    /** If set we will log collision events */
    CollisionCheckOperation.log = false;
    return CollisionCheckOperation;
}(ProcessFurtherOperation));
export { CollisionCheckOperation };
