"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var lodash_1 = tslib_1.__importDefault(require("lodash"));
var Core = tslib_1.__importStar(require("@intelligentgraphics/ig.gfx.core"));
var Environment_1 = tslib_1.__importDefault(require("../Environment"));
var SnapCalculator_1 = tslib_1.__importDefault(require("../Interactor/SnapCalculator"));
var Associates = tslib_1.__importStar(require("../Associates"));
var AtWallPlacement_1 = tslib_1.__importDefault(require("./AtWallPlacement"));
var MatchPlacement_1 = tslib_1.__importDefault(require("./MatchPlacement"));
var MiddleOfRoomPlacement_1 = tslib_1.__importDefault(require("./MiddleOfRoomPlacement"));
var Placement_1 = require("./Placement");
var RightOfItemPlacement_1 = tslib_1.__importDefault(require("./RightOfItemPlacement"));
var RightOfScenePlacement_1 = tslib_1.__importDefault(require("./RightOfScenePlacement"));
/** Class for deciding which placement should be applied to the passed component */
var PlacementDecision = /** @class */ (function () {
    /**
     * The constructor for the decision
     * @param insertedId
     * @param selection
     */
    function PlacementDecision(insertedId, selection) {
        var e_1, _a;
        var _this = this;
        this.insertedId = insertedId;
        this.otherObjects = lodash_1.default.without(Core.State.API.GetRoots(), this.insertedId);
        this.otherObjects = Core.State.API.GetRoots().filter(function (root) { return root !== _this.insertedId; });
        var parentsOfSelectedWithoutInserted = new Array();
        var allSelectedComponents = Core.State.API.GetAllSelectedComponents();
        try {
            for (var allSelectedComponents_1 = tslib_1.__values(allSelectedComponents), allSelectedComponents_1_1 = allSelectedComponents_1.next(); !allSelectedComponents_1_1.done; allSelectedComponents_1_1 = allSelectedComponents_1.next()) {
                var id = allSelectedComponents_1_1.value;
                if (id !== this.insertedId) {
                    var parent_1 = Associates.parent(id);
                    if (parent_1) {
                        parentsOfSelectedWithoutInserted.push(parent_1);
                    }
                    else {
                        parentsOfSelectedWithoutInserted.push(id);
                    }
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (allSelectedComponents_1_1 && !allSelectedComponents_1_1.done && (_a = allSelectedComponents_1.return)) _a.call(allSelectedComponents_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        this.selectedRoots = selection || parentsOfSelectedWithoutInserted;
    }
    PlacementDecision.bestPlacement = function (placements, componentId, selection) {
        var firstValidTransformation = PlacementDecision.firstValidTransformation(placements, componentId, selection);
        return firstValidTransformation;
    };
    /**
     * Gets the initial placement that should be used for the newly inserted object.
     * This will always be valid, i.e. there will be no collision with walls or other objects.
     * The order in which the this... methods are called, will be the order of priority for that method of placement.
     * If no placement is found, return a position to the right of the scene (guaranteed to be free of collision).
     * @returns {Transformation}
     */
    PlacementDecision.prototype.getTransformation = function () {
        var tPlacementAtWall = this.atWall();
        var tPlacementChild = this.child();
        var tPlacementSnap = this.snap();
        var tPlacementNextToSelection = this.nextToSelection();
        var tPlacementMiddleOfRoom = this.middleOfRoom();
        var tAllPlacements = tslib_1.__spread(tPlacementAtWall, tPlacementChild, tPlacementSnap, tPlacementNextToSelection, tPlacementMiddleOfRoom);
        var tFirstValidPlacement = PlacementDecision.firstValidTransformation(tAllPlacements, this.insertedId, this.selectedRoots);
        if (tFirstValidPlacement != null) {
            return tFirstValidPlacement;
        }
        var tRightOfScenePlacement = new RightOfScenePlacement_1.default(this.insertedId);
        var tRightOfSceneTransform = tRightOfScenePlacement.getTransformation();
        return tRightOfSceneTransform;
    };
    /**
     * Loop through the placements and find the first valid transformation provided by them.
     * @param placements
     * @param componentId
     * @param selection
     * @returns {Core.Transformation}
     */
    PlacementDecision.firstValidTransformation = function (placements, componentId, selection) {
        var e_2, _a;
        var tFirstValidTransform;
        try {
            for (var placements_1 = tslib_1.__values(placements), placements_1_1 = placements_1.next(); !placements_1_1.done; placements_1_1 = placements_1.next()) {
                var tPlacement = placements_1_1.value;
                var tPlacementTransformation = tPlacement.getTransformation();
                if (tPlacementTransformation == null) {
                    continue;
                }
                if (Placement_1.PlacementUtilities.isPositionViable(componentId, tPlacementTransformation, selection)) {
                    tFirstValidTransform = tPlacementTransformation;
                    break;
                }
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (placements_1_1 && !placements_1_1.done && (_a = placements_1.return)) _a.call(placements_1);
            }
            finally { if (e_2) throw e_2.error; }
        }
        return tFirstValidTransform;
    };
    /**
     * Add the placements responsible for positions to the right of selected items.
     */
    PlacementDecision.prototype.nextToSelection = function () {
        var _this = this;
        var rightFrontCorner = function (x) {
            return Core.State.API.GetBounds(x, "World").max.x;
        };
        var sortedRoots = this.selectedRoots.sort(function (a, b) { return rightFrontCorner(a) - rightFrontCorner(b); });
        var rightOfItemPlacements = sortedRoots.map(function (root) { return new RightOfItemPlacement_1.default(_this.insertedId, root); });
        return rightOfItemPlacements;
    };
    /**
     * Add the placements responsible for placement to objects that have strong snap points with the inserted id.
     */
    PlacementDecision.prototype.snap = function () {
        var e_3, _a;
        var _this = this;
        var snapCalc = new SnapCalculator_1.default({ id: this.insertedId }, true);
        snapCalc.onlyValidMatches = false;
        var allMatches = new Array();
        try {
            for (var _b = tslib_1.__values(this.otherObjects), _c = _b.next(); !_c.done; _c = _b.next()) {
                var root = _c.value;
                var neighborMatches = snapCalc.getStrongNeighborMatches({
                    id: root,
                });
                neighborMatches = neighborMatches.filter(function (match) { return match.isCoreMatch && !match.isTopBottom; });
                allMatches.push.apply(allMatches, tslib_1.__spread(neighborMatches));
            }
        }
        catch (e_3_1) { e_3 = { error: e_3_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_3) throw e_3.error; }
        }
        var result = lodash_1.default.orderBy(allMatches, [
            function (match) {
                return match.firstType === "Right" ? 1 : 0;
            },
            function (match) {
                return Math.abs(match.positionDifference.y);
            },
            function (match) {
                return match.distance;
            },
        ]).map(function (match) { return new MatchPlacement_1.default(_this.insertedId, match); });
        return result;
    };
    /**
     * Add the placement responsible for the insertion in the middle of the room.
     */
    PlacementDecision.prototype.middleOfRoom = function () {
        return [new MiddleOfRoomPlacement_1.default(this.insertedId)];
    };
    /**
     * Add the placements responsible for snapping to parents.
     */
    PlacementDecision.prototype.child = function () {
        var e_4, _a, e_5, _b;
        var snapCalc = new SnapCalculator_1.default({ id: this.insertedId }, true);
        snapCalc.onlyValidMatches = false;
        var result = new Array();
        try {
            for (var _c = tslib_1.__values(this.selectedRoots), _d = _c.next(); !_d.done; _d = _c.next()) {
                var root = _d.value;
                var strongChildMatches = snapCalc.getStrongChildMatches({
                    id: root,
                });
                try {
                    for (var strongChildMatches_1 = (e_5 = void 0, tslib_1.__values(strongChildMatches)), strongChildMatches_1_1 = strongChildMatches_1.next(); !strongChildMatches_1_1.done; strongChildMatches_1_1 = strongChildMatches_1.next()) {
                        var match = strongChildMatches_1_1.value;
                        if (match.isCoreMatch) {
                            var placement = new MatchPlacement_1.default(this.insertedId, match);
                            result.push(placement);
                        }
                    }
                }
                catch (e_5_1) { e_5 = { error: e_5_1 }; }
                finally {
                    try {
                        if (strongChildMatches_1_1 && !strongChildMatches_1_1.done && (_b = strongChildMatches_1.return)) _b.call(strongChildMatches_1);
                    }
                    finally { if (e_5) throw e_5.error; }
                }
            }
        }
        catch (e_4_1) { e_4 = { error: e_4_1 }; }
        finally {
            try {
                if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
            }
            finally { if (e_4) throw e_4.error; }
        }
        return result;
    };
    /**
     * Adds the placements responsible for placing objects close to walls.
     * For this, we traverse the wall in incremental steps.
     */
    PlacementDecision.prototype.atWall = function () {
        var _this = this;
        if (this.selectedRoots.length > 0) {
            var result = lodash_1.default.flatten(this.selectedRoots
                .filter(function (root) {
                return lodash_1.default.includes(Environment_1.default.getInstance().getWalls(), root);
            })
                .map(function (root) {
                return lodash_1.default.range(0, 1, 0.05).map(function (offset) {
                    return new AtWallPlacement_1.default(_this.insertedId, root, offset);
                });
            }));
            return result;
        }
        return [];
    };
    return PlacementDecision;
}());
exports.default = PlacementDecision;
