"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Core = require("@intelligentgraphics/ig.gfx.core");
var Planning = require("@intelligentgraphics/ig.gfx.planning");
var ThreeJS = require("@intelligentgraphics/ig.gfx.threejs");
var THREE = require("@intelligentgraphics/three");
var Util_1 = require("./Util");
// LinearAlgebra.AsPlane -> convertPlaneSegmentToPlane in Core 3.2.0
var convertPlaneSegmentToPlane = "AsPlane" in Core.LinearAlgebra
    ? Core.LinearAlgebra["AsPlane"]
    : Core.convertPlaneSegmentToPlane;
var HolePuncher = /** @class */ (function () {
    function HolePuncher(roomContainer, api) {
        this.roomContainer = roomContainer;
        this.api = api;
    }
    /**
     * called in move start: remove holes of currently selected articles, then rerender
     */
    HolePuncher.prototype.fillHolesOfSelected = function () {
        var _this = this;
        var tSelecteds = this.api.GetAllSelectedComponents();
        if (tSelecteds) {
            var tWallIds = Planning.Environment.getInstance().getWalls();
            var tAllWalls_1 = [];
            tWallIds.forEach(function (wi) {
                tAllWalls_1.push(_this.api.GetComponent(wi));
            });
            // remove any holes caused by currently selecteds
            tAllWalls_1.forEach(function (w) {
                if (!w.userData || !w.userData.punchedHoles) {
                    return;
                }
                var remainingHoles = w.userData.punchedHoles.filter(function (hole) { return !tSelecteds.some(function (ss) { return ss === hole.causedById; }); });
                w.userData.punchedHoles = remainingHoles;
                //Logger.info("recreate ", w.name, w.userData.punchedHoles);
                _this.recreateWall(w.name, w.userData.punchedHoles);
            });
            this.api.RedrawScene();
        }
    };
    /**
     * Adds a new hole for every explicitId (or selected) into nearest wall (if applicable) and renders only the new changed walls
     */
    HolePuncher.prototype.punchHolesIntoNearestWall = function (componentIds) {
        var _this = this;
        var changedWalls = [];
        componentIds.forEach(function (sid) {
            if (_this.api
                .GetCategories(sid)
                .indexOf(Core.Category.CuttingThroughWall) < 0) {
                return;
            }
            //2 find nearest wall
            var tWallId = Util_1.closestWallWithRotation(sid);
            if (!tWallId) {
                return;
            }
            var tWallDistance = Planning.WallUtilities.closestWallDistance(sid);
            //TODO IMPORTANT  THIS MIGHT BE WRONG -> ASK SH about correct distance calculation for rotated objects
            //or wall connection of a given accessory object
            if (tWallDistance < 0.3) {
                //Logger.warn("close wallid: ", tWallId, " dist: ", tWallDistance);
                //3 calculate hole middle
                var tGAB = ThreeJS.AbstractBoundingBox.CreateFromGfxId(sid);
                // const tGAB = /*this.api.*/ GetAbstractBoundsH.GetAbstractBoundsX(
                // sid
                // );
                var tSelectedBase = tGAB.getDirectionBasePoint();
                var tSelectedCenter_1 = tSelectedBase;
                var tDirs = tGAB.getDirections();
                tDirs.forEach(function (d) {
                    tSelectedCenter_1 = Core.Vector.Add3(tSelectedCenter_1, Core.Vector.ScalarMultiply3(0.5, d));
                });
                var frontFace = Planning.WallUtilities.faces(tWallId).front;
                var tWallPlane = convertPlaneSegmentToPlane(frontFace);
                var backFace = Planning.WallUtilities.faces(tWallId).back;
                var tWallPlaneB = convertPlaneSegmentToPlane(backFace);
                var tIntersect = Core.LinearAlgebra.GetIntersectionPoint({
                    origin: tSelectedCenter_1,
                    direction: tWallPlane.normal,
                }, tWallPlane) ||
                    Core.LinearAlgebra.GetIntersectionPoint({
                        origin: tSelectedCenter_1,
                        direction: Core.Vector.Reverse3(tWallPlane.normal),
                    }, tWallPlane);
                if (!tIntersect) {
                    return;
                }
                try {
                    var tUpperWindowBorder = tIntersect.y +
                        (Core.Vector.Magnitude3(tDirs[1]) / 2 - 0.001);
                    var tWall_1 = _this.api.GetComponent(tWallId);
                    if (tUpperWindowBorder > tWall_1.userData.wallInfo.Height) {
                        return;
                    }
                    var tOR = tGAB.getDirectionBasePoint();
                    var tUp = Core.Vector.Add3(tOR, tDirs[1]);
                    var tRight = Core.Vector.Add3(tOR, tDirs[0]);
                    var tIntersectOR = Core.LinearAlgebra.GetIntersectionPoint({ origin: tOR, direction: tWallPlane.normal }, tWallPlane) ||
                        Core.LinearAlgebra.GetIntersectionPoint({
                            origin: tOR,
                            direction: Core.Vector.Reverse3(tWallPlane.normal),
                        }, tWallPlane);
                    var tIntersectUP = Core.LinearAlgebra.GetIntersectionPoint({ origin: tUp, direction: tWallPlane.normal }, tWallPlane) ||
                        Core.LinearAlgebra.GetIntersectionPoint({
                            origin: tUp,
                            direction: Core.Vector.Reverse3(tWallPlane.normal),
                        }, tWallPlane);
                    var tIntersectRight = Core.LinearAlgebra.GetIntersectionPoint({
                        origin: tRight,
                        direction: tWallPlane.normal,
                    }, tWallPlane) ||
                        Core.LinearAlgebra.GetIntersectionPoint({
                            origin: tRight,
                            direction: Core.Vector.Reverse3(tWallPlane.normal),
                        }, tWallPlane);
                    //find intersect point with backface to make sure hole fits into both planes: front and back, especially for polygonal rooms
                    var tIntersectORB = Core.LinearAlgebra.GetIntersectionPoint({ origin: tOR, direction: tWallPlaneB.normal }, tWallPlaneB) ||
                        Core.LinearAlgebra.GetIntersectionPoint({
                            origin: tOR,
                            direction: Core.Vector.Reverse3(tWallPlaneB.normal),
                        }, tWallPlaneB);
                    var tIntersectUPB = Core.LinearAlgebra.GetIntersectionPoint({ origin: tUp, direction: tWallPlaneB.normal }, tWallPlaneB) ||
                        Core.LinearAlgebra.GetIntersectionPoint({
                            origin: tUp,
                            direction: Core.Vector.Reverse3(tWallPlaneB.normal),
                        }, tWallPlaneB);
                    var tIntersectRightB = Core.LinearAlgebra.GetIntersectionPoint({
                        origin: tRight,
                        direction: tWallPlaneB.normal,
                    }, tWallPlaneB) ||
                        Core.LinearAlgebra.GetIntersectionPoint({
                            origin: tRight,
                            direction: Core.Vector.Reverse3(tWallPlaneB.normal),
                        }, tWallPlaneB);
                    tIntersectOR.y += 0.001;
                    tIntersectUP.y += 0.001;
                    tIntersectRight.y += 0.001;
                    tIntersectORB.y += 0.001;
                    tIntersectUPB.y += 0.001;
                    tIntersectRightB.y += 0.001;
                    if (Core.LinearAlgebra.IsInsidePlaneSegment(frontFace, tIntersectOR) &&
                        Core.LinearAlgebra.IsInsidePlaneSegment(frontFace, tIntersectUP) &&
                        Core.LinearAlgebra.IsInsidePlaneSegment(frontFace, tIntersectRight) &&
                        Core.LinearAlgebra.IsInsidePlaneSegment(backFace, tIntersectORB) &&
                        Core.LinearAlgebra.IsInsidePlaneSegment(backFace, tIntersectUPB) &&
                        Core.LinearAlgebra.IsInsidePlaneSegment(backFace, tIntersectRightB)) {
                        // everything inside smaller wall mesh --> OK
                    }
                    else {
                        //Logger.info("new test found conflict (despite added height) -> canceled");
                        return;
                    }
                    //Logger.info("inside test finished ok");
                }
                catch (e) {
                    //Logger.info("wall: ", tWall);
                }
                var tHole_1 = {
                    causedById: sid,
                    position: new THREE.Vector3(tIntersect.x, tIntersect.y, tIntersect.z),
                    halfSize: new THREE.Vector3(Core.Vector.Magnitude3(tDirs[0]) / 2 - 0.001, 
                    //height slightly less to fix doors , width slightly less to fix 2 neighbors
                    Core.Vector.Magnitude3(tDirs[1]) / 2 - 0.001, 1),
                };
                var tWall = _this.api.GetComponent(tWallId);
                if (!tWall.userData) {
                    tWall.userData = {};
                }
                if (!tWall.userData.punchedHoles) {
                    tWall.userData.punchedHoles = [];
                }
                //remove hole if it already existed -> necessary for depth change via property editor
                var tWasAlready_1 = false;
                tWall.userData.punchedHoles.forEach(function (ph) {
                    if (ph.causedById === tHole_1.causedById) {
                        ph.halfSize = tHole_1.halfSize;
                        ph.position = tHole_1.position;
                        tWasAlready_1 = true;
                    }
                });
                if (!tWasAlready_1) {
                    tWall.userData.punchedHoles.push(tHole_1);
                }
                if (!changedWalls.some(function (w) { return w.name === tWallId; })) {
                    //not already inside, so push into
                    changedWalls.push(tWall);
                }
            }
        });
        // data updated, now render only changed walls
        changedWalls.forEach(function (w) {
            _this.recreateWall(w.name, w.userData.punchedHoles);
        });
        this.api.RedrawScene();
    };
    /**
     *
     * @param wallComponentId
     * @param wallItems
     *
     * Often called like (w.name, w.userData.punchedHoles) for redraw
     */
    HolePuncher.prototype.recreateWall = function (wallComponentId, wallItems) {
        var _this = this;
        var w = this.api.GetComponent(wallComponentId);
        var ms = [];
        // find meshes interior and exterior
        w.traverse(function (node) {
            if (node instanceof THREE.Mesh) {
                // insert your code here, for example:
                //node.material = new THREE.MeshNormalMaterial()
                if (node["SpecialMeshName"] === "Exterior" ||
                    node["SpecialMeshName"] === "Interior") {
                    ms.push(node);
                }
            }
        });
        ms.forEach(function (m) {
            var tStart = m["SpecialMeshOriginalStart"];
            var tEnd = m["SpecialMeshOriginalEnd"];
            var tTransform = m["SpecialMeshOriginalTransform"];
            var tInvTransform = m["SpecialMeshOriginalInvTransform"];
            var tHeight = m["SpecialMeshOriginalHeight"];
            // z is thickness :-/
            var MX = _this.makeWallX(tStart, tEnd, tTransform, tInvTransform, undefined, tHeight, wallItems);
            m.geometry = MX.geometry; //  new THREE.ShapeGeometry(shape);
            var tSpecialImportMatrix = m["SpecialImportMatrix"];
            var tSpecialMeshToOriginRotateMatrix = m["SpecialMeshToOriginRotateMatrix"];
            //Logger.info(tSpecialImportMatrix, tSpecialMeshToOriginRotateMatrix);
            if (tSpecialImportMatrix != undefined) {
                m.geometry.applyMatrix(tSpecialImportMatrix); //tMatrix
                m.geometry.verticesNeedUpdate = true;
                m.updateMatrix();
                m.geometry.applyMatrix(tSpecialMeshToOriginRotateMatrix); //MeshToOriginRotate
                m.geometry.verticesNeedUpdate = true;
                m.updateMatrix();
            }
        });
    };
    HolePuncher.prototype.toVec3 = function (pos, height) {
        height = height || 0;
        return new THREE.Vector3(pos.x, height, pos.y);
    };
    /** Gets the distance of two points.
     * @param x1 X part of first point.
     * @param y1 Y part of first point.
     * @param x2 X part of second point.
     * @param y2 Y part of second point.
     * @returns The distance.
     */
    HolePuncher.prototype.calculateDistanceBetweenPoints = function (x1, y1, x2, y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    };
    // start, end have x and y attributes (i.e. corners)
    HolePuncher.prototype.makeWallX = function (start, end, transform, invTransform, material, wallHeight, wallItems) {
        var _this = this;
        //Logger.info("makeWall ", start, end, invTransform, material);
        var v1 = this.toVec3(start);
        var v2 = this.toVec3(end);
        var v3 = v2.clone();
        v3.y = wallHeight;
        var v4 = v1.clone();
        v4.y = wallHeight;
        var points = [v1.clone(), v2.clone(), v3.clone(), v4.clone()];
        points.forEach(function (p) {
            p.applyMatrix4(transform);
        });
        var shape = new THREE.Shape([
            new THREE.Vector2(points[0].x, points[0].y),
            new THREE.Vector2(points[1].x, points[1].y),
            new THREE.Vector2(points[2].x, points[2].y),
            new THREE.Vector2(points[3].x, points[3].y),
        ]);
        // add holes for each wall item
        wallItems.forEach(function (item) {
            var pos = item.position.clone();
            //Logger.info("addWall punching hole ", item.position, item.halfSize);
            pos.applyMatrix4(transform);
            var halfSize = item.halfSize;
            var min = halfSize.clone().multiplyScalar(-1);
            var max = halfSize.clone();
            min.add(pos);
            max.add(pos);
            var holePoints = [
                new THREE.Vector2(min.x, min.y),
                new THREE.Vector2(max.x, min.y),
                new THREE.Vector2(max.x, max.y),
                new THREE.Vector2(min.x, max.y),
            ];
            //Logger.info("holepoints:" + JSON.stringify(holePoints));
            shape.holes.push(new THREE.Path(holePoints)); //reverse doesnt fix problem
        });
        var geometry = new THREE.ShapeGeometry(shape);
        geometry.vertices.forEach(function (v) {
            v.applyMatrix4(invTransform);
        });
        var translation = new THREE.Vector3();
        var rotationq = new THREE.Quaternion();
        var scale = new THREE.Vector3();
        invTransform.decompose(translation, rotationq, scale);
        var rotation = new THREE.Euler(0, 0, 0, "XYZ");
        rotation.setFromQuaternion(rotationq, "XYZ");
        // make UVs
        var totalDistance = this.calculateDistanceBetweenPoints(v1.x, v1.z, v2.x, v2.z);
        var height = wallHeight;
        geometry.faceVertexUvs[0] = [];
        var vertexToUv = function (vertex) {
            var x = _this.calculateDistanceBetweenPoints(v1.x, v1.z, vertex.x, vertex.z) /*  /  totalDistance */; //NM experiment
            var y = vertex.y /*  / height */; //NM experiment
            return new THREE.Vector2(x, y);
        };
        geometry.faces.forEach(function (face) {
            var vertA = geometry.vertices[face.a];
            var vertB = geometry.vertices[face.b];
            var vertC = geometry.vertices[face.c];
            geometry.faceVertexUvs[0].push([
                vertexToUv(vertA),
                vertexToUv(vertB),
                vertexToUv(vertC),
            ]);
            //Logger.info("UV generated: write UV: ", JSON.stringify(geometry.faceVertexUvs[0]));
        });
        geometry.faceVertexUvs[1] = geometry.faceVertexUvs[0];
        geometry.computeFaceNormals();
        geometry.computeVertexNormals();
        var mesh = new THREE.Mesh(geometry, material);
        mesh["SpecialMeshOriginalShape"] = shape;
        mesh["SpecialMeshOriginalStart"] = start;
        mesh["SpecialMeshOriginalEnd"] = end;
        mesh["SpecialMeshOriginalTransform"] = transform;
        mesh["SpecialMeshOriginalInvTransform"] = invTransform;
        mesh["SpecialMeshOriginalHeight"] = wallHeight;
        //material not exported , instead use already existing material, or create with null and extract geometry
        return mesh;
    };
    return HolePuncher;
}());
exports.HolePuncher = HolePuncher;
