import { __read, __spread, __values } from "tslib";
import _ from "lodash";
import * as Core from "@intelligentgraphics/ig.gfx.core";
import * as ThreeJS from "@intelligentgraphics/ig.gfx.threejs";
import { Vector, } from "@intelligentgraphics/ig.gfx.core";
import Environment from "./Environment";
import WorldLocalConverter from "./WorldLocalConverter";
/** Class containing static methods for interaction with walls. */
var WallUtilities = /** @class */ (function () {
    function WallUtilities() {
    }
    /**
     * Returns the directions of the component.
     * @param wallId
     * @returns WallDirections
     */
    WallUtilities.directions = function (wallId) {
        var wallBounds = Core.State.API.GetAbstractBounds(wallId);
        var directions = wallBounds.getDirections();
        var sortedDirections = _(directions).sortBy(Core.Vector.Magnitude3);
        var sortedByHeight = sortedDirections.tail().sortBy("y");
        // The smallest direction is the thickness of the wall
        // The other two directions are the height and width of the wall.
        // We get the direction of the wall by choosing the one with the smaller y component
        var result = {
            width: sortedDirections.first(),
            height: sortedByHeight.last(),
            length: sortedByHeight.first(),
        };
        return result;
    };
    /**
     * Find the wall closest to the component
     * @param componentId
     * @returns {string}
     */
    WallUtilities.closestWall = function (componentId, desiredTransformation) {
        var e_1, _a;
        var walls = Environment.getInstance().getWalls();
        if (!walls || walls.length === 0) {
            return undefined;
        }
        if (!desiredTransformation) {
            desiredTransformation = Core.State.API.GetBasicWorldTransformation(componentId);
        }
        var bounds = Core.State.API.GetBounds(componentId, "Local", true);
        var abb = new ThreeJS.AbstractBoundingBox();
        abb.setFromTransformation(desiredTransformation, bounds);
        var componentPoints = abb.getPoints();
        var minDistance = Infinity;
        var result;
        try {
            for (var walls_1 = __values(walls), walls_1_1 = walls_1.next(); !walls_1_1.done; walls_1_1 = walls_1.next()) {
                var wall = walls_1_1.value;
                var distance = WallUtilities.distanceAverageOfPointsToWall(componentPoints, wall);
                if (distance < minDistance) {
                    minDistance = distance;
                    result = wall;
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (walls_1_1 && !walls_1_1.done && (_a = walls_1.return)) _a.call(walls_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return result;
    };
    /**
     * Find the distance to the closest wall
     * @param componentId
     * @returns {any}
     */
    WallUtilities.closestWallDistance = function (componentId) {
        var walls = Environment.getInstance().getWalls();
        if (!walls || walls.length === 0) {
            return undefined;
        }
        var componentPoints = Core.State.API.GetAbstractBounds(componentId).getPoints();
        var distances = walls.map(function (wall) {
            return WallUtilities.distanceOfPointsToWall(componentPoints, wall);
        });
        var result = Math.min.apply(Math, __spread(distances));
        return result;
    };
    /**
     * The distance of the point to the wall.
     * Measure the distance of the point to the front and back plane and decide for the shorter distance.
     * @param point
     * @param wallId
     * @returns {number}
     */
    WallUtilities.distanceToWall = function (point, wallId) {
        var tAbstractBounds = Core.State.API.GetAbstractBounds(wallId);
        var result = Core.LinearAlgebra.DistanceToAbstractBounds(tAbstractBounds, point);
        return result;
    };
    /**
     * The shortest distance one of the points has to the wall
     * @param points
     * @param wallId
     * @returns {number}
     */
    WallUtilities.distanceOfPointsToWall = function (points, wallId) {
        var e_2, _a;
        var minDistanceToWall = Infinity;
        try {
            for (var points_1 = __values(points), points_1_1 = points_1.next(); !points_1_1.done; points_1_1 = points_1.next()) {
                var point = points_1_1.value;
                var distanceToWall = WallUtilities.distanceToWall(point, wallId);
                if (distanceToWall < minDistanceToWall) {
                    minDistanceToWall = distanceToWall;
                }
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (points_1_1 && !points_1_1.done && (_a = points_1.return)) _a.call(points_1);
            }
            finally { if (e_2) throw e_2.error; }
        }
        return minDistanceToWall;
    };
    /**
     * The average distance all of the points have to the wall
     * @param points
     * @param wallId
     * @returns {number}
     */
    WallUtilities.distanceAverageOfPointsToWall = function (points, wallId) {
        var e_3, _a;
        if (points == null || points.length == 0) {
            return Infinity;
        }
        var sum = 0;
        try {
            for (var points_2 = __values(points), points_2_1 = points_2.next(); !points_2_1.done; points_2_1 = points_2.next()) {
                var point = points_2_1.value;
                var distanceToWall = WallUtilities.distanceToWall(point, wallId);
                sum += distanceToWall;
            }
        }
        catch (e_3_1) { e_3 = { error: e_3_1 }; }
        finally {
            try {
                if (points_2_1 && !points_2_1.done && (_a = points_2.return)) _a.call(points_2);
            }
            finally { if (e_3) throw e_3.error; }
        }
        var result = sum / points.length;
        return result;
    };
    /**
     * Returns the front and back face of the wall.
     * @param wallId
     * @returns WallFaces
     */
    WallUtilities.faces = function (wallId) {
        var wallMeshes = [];
        Core.State.API.GetComponent(wallId).traverse(function (obj) {
            if (obj.type === "Mesh") {
                wallMeshes.push(obj);
            }
        });
        var interiorMesh = wallMeshes.find(function (mesh) { return mesh.SpecialMeshName === "Interior"; });
        var exteriorMesh = wallMeshes.find(function (mesh) { return mesh.SpecialMeshName === "Exterior"; });
        var result = {
            front: WallUtilities.faceFromMesh(interiorMesh),
            back: WallUtilities.faceFromMesh(exteriorMesh),
        };
        return result;
    };
    /**
     * Create a plane segment from a front/back mesh
     * @param mesh
     * @returns {MathTypes.PlaneSegment}
     */
    WallUtilities.faceFromMesh = function (mesh) {
        var node = mesh;
        while (node.name === undefined || node.name === "") {
            node = node.parent;
        }
        var subRootId = node.name;
        var bounds = Core.State.API.GetBounds(subRootId, "Local", true);
        var converter = new WorldLocalConverter(subRootId);
        var directions = WallUtilities.directions(subRootId);
        var worldPosition = converter.toWorld(bounds.min);
        var result = {
            point: worldPosition,
            borderU: directions.height,
            borderV: directions.length,
        };
        return result;
    };
    /**
     * Determine if a point is close to a wall. I.e. if the point resides within (or close to) the bounding box of the wall
     * @param point
     * @param wallId
     * @param maxOutsideDistance    (optional) number of world units describing closeness,
     *                                e.g. 1.0 -> points less then 1 m from the wall are considered close
     * @returns {boolean}
     */
    WallUtilities.isPointCloseToWall = function (point, wallId) {
        var epsilon = 0.001; // seems only to fix mathematical instability
        var directions = WallUtilities.directions(wallId);
        var tWallPos = Core.State.API.GetPosition(wallId);
        // use solution from http://math.stackexchange.com/a/190373/36189
        var relativePosition = Vector.Sub3(point, tWallPos);
        var widthComponent = Vector.InnerProduct3(directions.width, relativePosition);
        var widthMagnitude = Vector.InnerProduct3(directions.width, directions.width);
        var lengthComponent = Vector.InnerProduct3(directions.length, relativePosition);
        var lengthMagnitude = Vector.InnerProduct3(directions.length, directions.length);
        return (0 <= widthComponent + epsilon &&
            widthComponent <= widthMagnitude + epsilon &&
            0 <= lengthComponent + epsilon &&
            lengthComponent <= lengthMagnitude + epsilon);
    };
    WallUtilities.isPointCloseToWallFront = function (point, wallId, maxDistance) {
        if (maxDistance === void 0) { maxDistance = 0.1; }
        var frontFace = WallUtilities.faces(wallId).front;
        var distance = Core.LinearAlgebra.DistanceToPlaneSegment(frontFace, point);
        return distance <= maxDistance;
    };
    return WallUtilities;
}());
export { WallUtilities };
