import { __assign, __awaiter, __extends, __generator, __read, __spread, __values } from "tslib";
import * as Core from "@intelligentgraphics/ig.gfx.core";
import * as MultiArticle from "@intelligentgraphics/ig.gfx.multiarticle";
import * as ThreeJS from "@intelligentgraphics/ig.gfx.threejs";
import * as THREE from "@intelligentgraphics/three";
import Environment from "./Environment";
import SnapCalculator from "./Interactor/SnapCalculator";
var GhostBoxMaterial = new THREE.LineBasicMaterial({ color: "#dd33dd" });
var GhostInsertMode = /** @class */ (function () {
    /**
     * The constructor.
     * @param id
     * @param addCopy
     * @param postDeletion
     */
    function GhostInsertMode(id, addCopy, postDeletion, articleConfiguration) {
        var _this = this;
        this.blockedPositions = [];
        this.examinedRoots = [];
        this.cachedMatches = [];
        /** The action to trigger after the mode has stoppend */
        this.postDeletion = function () { };
        /** Callback for cleaning up after the ghost mode ended. */
        this.deleteOriginal = function () {
            return Core.State.API.RemoveComponent(Core.Hierarchy.getSuperRootGfxId(_this.componentId));
        };
        this.componentId = id;
        var bounds = Core.State.API.GetBounds(this.componentId, "Local", true);
        this.componentConfiguration = articleConfiguration;
        this.componentDimensions = {
            Width: bounds.max.x - bounds.min.x,
            Height: bounds.max.y - bounds.min.y,
            Depth: bounds.max.z - bounds.min.z,
        };
        this.addCopy = addCopy;
        this.postDeletion = postDeletion || this.postDeletion;
        this.ghosts = new ThreeJS.Ghosts(this.componentId);
        this.snapCalculator = new SnapCalculator({ id: this.componentId }, true);
        this.snapCalculator.onlyValidMatches = false;
        this.snapCalculator.onlyGhostRepresentationMatches = true;
        Core.State.API.SetVisibility(this.componentId, false);
        GhostInsertMode.instance = this;
    }
    GhostInsertMode.prototype.addBlockedPosition = function (trafo) {
        this.blockedPositions.push(trafo);
    };
    /**
     * Creates a simple copy mode, for use in demos and tests.
     * No commercial information is updated!
     * @param templateGfxId
     * @returns {GhostInsertMode}
     */
    GhostInsertMode.makeSimpleCopyMode = function (templateGfxId) {
        var addCopy = function (trafo) {
            var superRootId = Core.State.API.CreateSuperRoot();
            var templateComponent = Core.State.API.GetComponent(templateGfxId);
            templateComponent.visible = true;
            var IGXC = Core.State.API.GetAsIGXC(templateGfxId);
            Core.State.API.ProcessIGXC(superRootId, IGXC, undefined).then(function () {
                var rootId = Core.Hierarchy.getRootGfxId(superRootId);
                Core.State.API.SetTransformation(rootId, trafo);
                var newComponent = Core.State.API.GetComponent(rootId);
                newComponent.userData.points = templateComponent.userData.points.slice(0);
                newComponent.userData.cachedScaledBounds = null;
                newComponent.userData.cachedBounds = null;
                templateComponent.visible = false;
            });
        };
        GhostInsertMode.instance = new GhostInsertMode(templateGfxId, addCopy);
        return GhostInsertMode.instance;
    };
    /**
     * Check if for the given id it is necessary to start a ghost mode.
     * This is the case if there is a viable snap match, e.g. an match to a point with Representation = "Ghost"
     * @param componentId
     * @returns {boolean}
     */
    GhostInsertMode.isGhostModeNecessary = function (componentId, componentConfiguration) {
        var snapCalculator = new SnapCalculator({ id: componentId }, true);
        snapCalculator.onlyTakeFirstNonCollidingMatch = true;
        snapCalculator.onlyGhostRepresentationMatches = true;
        snapCalculator.onlyValidMatches = false;
        var allRoots = Core.State.API.GetRoots();
        var environmentRoot = Environment.getInstance().getRoot();
        var allOtherRoots = allRoots.filter(function (rootId) { return rootId !== componentId && rootId !== environmentRoot; });
        var resultMatches = snapCalculator.getViableMatches(componentId, null, componentConfiguration, allOtherRoots);
        return resultMatches && resultMatches.length > 0;
        // let potentialRoots = allOtherRoots.filter(
        // 	root => Core.State.API.GetBounds(root, "Local", true) !== null
        // );
        // const isViableFilter = (
        // 	match: StrongChildMatch | StrongNeighborMatch
        // ) => match.isCoreMatch && match.hasGhostModeRepresentation;
        // const matches = new Array<StrongChildMatch | StrongNeighborMatch>();
        // for (const root of potentialRoots) {
        // 	let rootState = { id: root };
        // 	let childMatches = snapCalculator.getStrongChildMatches(rootState);
        // 	let neighborMatches = snapCalculator.getStrongNeighborMatches(
        // 		rootState
        // 	);
        // 	const filteredMatches = [
        // 		...childMatches,
        // 		...neighborMatches
        // 	].filter(isViableFilter);
        // 	matches.push(...filteredMatches);
        // }
        // let isGhostModeNecessaryResult = false;
        // for (const match of matches) {
        // 	if (
        // 		GhostInsertMode.isViableForInsertedId(
        // 			componentId,
        // 			match,
        // 			componentConfiguration
        // 		)
        // 	) {
        // 		isGhostModeNecessaryResult = true;
        // 		break;
        // 	}
        // }
        // return isGhostModeNecessaryResult;
    };
    /**
     * Refresh the displayed ghosts to reflect the current situation
     */
    GhostInsertMode.prototype.refreshGhosts = function () {
        return __awaiter(this, void 0, void 0, function () {
            var viableMatches, ghostsToKeep, oldGhosts, componentTransformation, ghostsToDelete, allGhosts, newGhostIDs;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        Core.State.API.SetVisibility(this.componentId, true);
                        viableMatches = this.viableMatches();
                        Core.State.API.SetVisibility(this.componentId, false);
                        if (viableMatches.length === 0) {
                            this.stop();
                            return [2 /*return*/];
                        }
                        this.ghosts.stopRedraw();
                        ghostsToKeep = [];
                        oldGhosts = this.ghosts.ghostIds();
                        componentTransformation = Core.getWorldTransformation(this.componentId);
                        return [4 /*yield*/, Promise.all(viableMatches.map(function (match) {
                                var trafo = match.adaptTransformation(componentTransformation);
                                var areTransformationsClose = function (t1, t2) {
                                    return Core.Vector.Distance3(t1.position, t2.position) <= 0.01 &&
                                        Core.Vector.Distance3(t1.rotation, t2.rotation) <= 0.01;
                                };
                                var currentGhostIds = _this.ghosts.ghostIds();
                                var ghostAtPosition = currentGhostIds.find(function (ghostId) {
                                    var ghostTransformation = Core.getWorldTransformation(ghostId);
                                    return areTransformationsClose(ghostTransformation, trafo);
                                });
                                if (ghostAtPosition) {
                                    ghostsToKeep.push(ghostAtPosition);
                                }
                                else {
                                    var actions = match.getActions();
                                    var rules_1 = new Array();
                                    actions.forEach(function (action) { return rules_1.push.apply(rules_1, __spread(action.Rules)); });
                                    var overwriteDimensions = GhostInsertMode.getEvaluatedBBox(match.secondId, rules_1, _this.componentConfiguration);
                                    var ghostConfig = __assign(__assign({}, _this.componentDimensions), overwriteDimensions);
                                    var ghostMode = match.representation === "Ghost"
                                        ? Core.EGhostMode.Geometric
                                        : Core.EGhostMode.Box;
                                    return _this.placeGhost(trafo, ghostMode, ghostConfig);
                                }
                            }))];
                    case 1:
                        _a.sent();
                        ghostsToDelete = oldGhosts.filter(function (oldGhostId) { return ghostsToKeep.indexOf(oldGhostId) === -1; });
                        allGhosts = this.ghosts.ghostIds();
                        newGhostIDs = allGhosts.filter(function (ghostId) { return oldGhosts.indexOf(ghostId) === -1; });
                        ghostsToDelete.forEach(function (ghostId) {
                            _this.ghosts.deleteSingleGhost(ghostId);
                            // remove ghost interactors to avoid caching of actions
                            // this is necessary, because in different phases of the GhostMode
                            // different ghosts will have the same id, caching the Interactor would result in
                            // wrong actions
                            Core.InteractorHandler.InteractorList = Core.InteractorHandler.InteractorList.filter(function (interactor) {
                                return interactor.GetComponentId().indexOf(ghostId) === -1;
                            });
                        });
                        this.ghosts.startRedraw();
                        newGhostIDs.forEach(function (ghostId, index) {
                            Core.State.API.Highlight(ghostId, GhostInsertMode.ghostColor, 
                            /** no redraw necessary */ true);
                            var component = Core.State.API.GetComponent(ghostId);
                            // make children non selectable, so that clicking on a ghost always triggers the parents interactor
                            component.traverse(function (obj) { return (obj.userData.selectable = "Parent"); });
                            component.userData.selectable = "Self";
                            component.userData.interactor =
                                "IG_GFX_Planning.CreateCopyInteractor";
                            component.userData.interactorParameters = {
                                actions: [],
                            };
                            if (viableMatches[index]) {
                                var actionHolders = viableMatches[index].actionHolders;
                                component.userData.interactorParameters = {
                                    firstHolder: actionHolders[0],
                                    secondHolder: actionHolders[1],
                                    snapTo: viableMatches[index].secondId,
                                };
                            }
                        });
                        // bug: sometimes materials of ghosts are set to invisible (maybe due to some bug in
                        // @intelligentgraphics/ig.gfx.threejs/Ghosts ?) so we will set them back to visible
                        this.ghosts.ghostIds().forEach(function (g) {
                            return Core.State.API.GetComponent(g).traverse(function (obj) {
                                if (obj["material"] && !obj["material"].visible) {
                                    obj["material"].visible = true;
                                }
                            });
                        });
                        Core.State.API.RedrawScene();
                        return [2 /*return*/];
                }
            });
        });
    };
    GhostInsertMode.prototype.viableMatches = function () {
        var e_1, _a;
        var allRoots = Core.State.API.GetRoots();
        var environmentRoot = Environment.getInstance().getRoot();
        var unexaminedRoots = new Array();
        try {
            for (var allRoots_1 = __values(allRoots), allRoots_1_1 = allRoots_1.next(); !allRoots_1_1.done; allRoots_1_1 = allRoots_1.next()) {
                var rootId = allRoots_1_1.value;
                if (rootId !== this.componentId &&
                    rootId !== environmentRoot &&
                    this.examinedRoots.indexOf(rootId) === -1) {
                    unexaminedRoots.push(rootId);
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (allRoots_1_1 && !allRoots_1_1.done && (_a = allRoots_1.return)) _a.call(allRoots_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        var viableMatches = this.snapCalculator.getViableMatches(this.componentId, null, this.componentConfiguration, unexaminedRoots);
        return viableMatches;
    };
    /**
     * Place a ghost at the given place
     * @param ghostPlacement
     */
    GhostInsertMode.prototype.placeGhost = function (ghostPlacement, mode, ghostConfig) {
        return __awaiter(this, void 0, void 0, function () {
            var oldGhosts, freshGhosts, theGhostId, offset, compDims, newScale, offsetVector, euler, rotatedOffset, ghostNode, bb, ghostMesh, meshOffset, shiftedTrafo, abb, line, component;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        oldGhosts = this.ghosts.ghostIds();
                        return [4 /*yield*/, this.ghosts.create(mode, true)];
                    case 1:
                        _a.sent();
                        freshGhosts = this.ghosts.ghostIds();
                        theGhostId = freshGhosts.find(function (id) { return oldGhosts.indexOf(id) === -1; });
                        // only boxes are scaled
                        if (ghostConfig && mode === Core.EGhostMode.Box) {
                            offset = GhostInsertMode.ghostPadding;
                            compDims = this.componentDimensions;
                            newScale = {
                                x: (ghostConfig.Width + 2 * offset) / compDims.Width,
                                y: (ghostConfig.Height + 2 * offset) / compDims.Height,
                                z: (ghostConfig.Depth + 2 * offset) / compDims.Depth,
                            };
                            offsetVector = new THREE.Vector3(offset, offset, offset);
                            euler = new THREE.Euler(ghostPlacement.rotation.x, ghostPlacement.rotation.y, ghostPlacement.rotation.z, "XYZ");
                            rotatedOffset = offsetVector.applyEuler(euler);
                            Core.State.API.SetScaling(theGhostId, newScale);
                            ghostNode = Core.State.API.GetComponent(theGhostId);
                            bb = Core.State.API.GetBounds(this.componentId, "Local");
                            ghostMesh = ghostNode.children[0];
                            if (bb && ghostMesh) {
                                meshOffset = new THREE.Vector3(bb.min.x / newScale.x, bb.min.y / newScale.y, bb.min.z / newScale.z);
                                ghostMesh.position.copy(meshOffset);
                                ghostMesh.updateMatrix();
                            }
                            shiftedTrafo = {
                                rotation: ghostPlacement.rotation,
                                position: {
                                    x: ghostPlacement.position.x - rotatedOffset.x,
                                    y: ghostPlacement.position.y - rotatedOffset.y,
                                    z: ghostPlacement.position.z - rotatedOffset.z,
                                },
                            };
                            Core.State.API.SetTransformation(theGhostId, shiftedTrafo);
                            if (ThreeJS.State.debugging.showBoxInGhostInsertMode) {
                                abb = new ThreeJS.AbstractBoundingBox();
                                abb.setFromComponent(theGhostId);
                                line = ThreeJS.AbstractBoundingBox.CreateLineObject(abb, GhostBoxMaterial);
                                ThreeJS.State.scene.add(line);
                                Core.State.API.RedrawScene(true);
                                ThreeJS.State.scene.remove(line);
                            }
                        }
                        else {
                            Core.State.API.SetTransformation(theGhostId, ghostPlacement);
                        }
                        component = Core.State.API.GetComponent(theGhostId);
                        component.userData.ghostDisplayMode = mode;
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Start the ghost mode.
     */
    GhostInsertMode.prototype.start = function () {
        this.refreshGhosts();
    };
    Object.defineProperty(GhostInsertMode.prototype, "GhostIds", {
        /**
         * Returns all GhostIds
         */
        get: function () {
            return this.ghosts.ghostIds();
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Stop the mode, cleaning up all ghosts and deleting the invisible component.
     */
    GhostInsertMode.prototype.stop = function () {
        Core.State.API.SelectComponent(null);
        this.ghosts.deleteAll();
        this.deleteOriginal();
        this.postDeletion();
        GhostInsertMode.instance = null;
    };
    GhostInsertMode.stopInstance = function () {
        if (GhostInsertMode.instance) {
            GhostInsertMode.instance.stop();
        }
    };
    GhostInsertMode.getEvaluatedBBox = function (actionHolder, actionRules, componentConfiguration, relativeSnapPosition) {
        if (relativeSnapPosition === void 0) { relativeSnapPosition = { x: 0, y: 0, z: 0 }; }
        var bboxData = {};
        var context = Object.assign({}, componentConfiguration, MultiArticle.Rule.createAdditionalRuleParameters({
            context: actionHolder,
            hasBottomNeighbor: false,
            hasLeftNeighbor: false,
            hasRightNeighbor: false,
            hasTopNeighbor: false,
            relativeSnapPosition: relativeSnapPosition,
        }));
        var len = actionRules.length;
        for (var idx = 0; idx < len; idx++) {
            var action = actionRules[idx];
            var _a = MultiArticle.Rule.evalActionRule(action, context), isActive = _a.isActive, ghostProperty = _a.ghostProperty, value = _a.value;
            if (isActive && ghostProperty !== undefined) {
                bboxData[ghostProperty] = parseFloat(value);
            }
        }
        return bboxData;
    };
    /** The color that will be used in the ghost mode. Default: a light blue shade */
    GhostInsertMode.ghostColor = new Core.ColorRGB(2 / 256, 166 / 256, 228 / 256);
    /** The additional space to make a ghost larger than the actual article */
    GhostInsertMode.ghostPadding = 0.01;
    return GhostInsertMode;
}());
export { GhostInsertMode };
// used for insertion if ghost is clicked
var CreateCopyInteractor = /** @class */ (function (_super) {
    __extends(CreateCopyInteractor, _super);
    /**
     * The constructor.
     * @param parameters
     */
    function CreateCopyInteractor(parameters) {
        var _this = _super.call(this, parameters) || this;
        _this.copyLastPos = Core.Vector.CreateVec3(999999, 999999, 999999);
        _this.copyLastRotation = Core.Vector.CreateVec3(999999, 999999, 999999);
        _this.actionHolders = [parameters.firstHolder, parameters.secondHolder];
        return _this;
    }
    CreateCopyInteractor.prototype.SingleTouchStart = function (touch) {
        var tComponentId = this.GetComponentId();
        var ghostTrafo = Core.getWorldTransformation(tComponentId);
        var component = Core.State.API.GetComponent(tComponentId);
        if (component.userData.ghostDisplayMode === Core.EGhostMode.Box) {
            var offset = GhostInsertMode.ghostPadding;
            var offsetVector = new THREE.Vector3(offset, offset, offset);
            // so far not implemented for other rotations
            var euler = new THREE.Euler(ghostTrafo.rotation.x, ghostTrafo.rotation.y, ghostTrafo.rotation.z, "XYZ");
            var rotatedOffset = offsetVector.applyEuler(euler);
            ghostTrafo.position.x += rotatedOffset.x;
            ghostTrafo.position.y += rotatedOffset.y;
            ghostTrafo.position.z += rotatedOffset.z;
        }
        var equalPos = Core.Vector.IsEqual3(this.copyLastPos, ghostTrafo.position, 0.0001);
        var equalRot = Core.Vector.IsEqual3(this.copyLastRotation, ghostTrafo.rotation, 0.0001);
        if (!(equalPos && equalRot)) {
            Core.State.API.SelectComponent(null);
            this.copyLastPos = ghostTrafo.position;
            this.copyLastRotation = ghostTrafo.rotation;
            var actionsFromActionHolders_1 = new Array();
            this.actionHolders.forEach(function (x) {
                var addActions = Core.State.API.GetActions(x);
                actionsFromActionHolders_1.push.apply(actionsFromActionHolders_1, __spread(addActions));
            });
            var filteredAction = actionsFromActionHolders_1.filter(function (action) {
                return action.Mode === "ParentDependency";
            });
            var allActions_1 = new Array();
            filteredAction.forEach(function (rawAction) {
                return allActions_1.push.apply(allActions_1, __spread(rawAction.Rules));
            });
            GhostInsertMode.instance.addCopy(ghostTrafo, allActions_1, this.parameters.snapTo || "");
        }
        else {
            //console.log("copy already done for this ghost position");
        }
    };
    return CreateCopyInteractor;
}(Core.Interactor));
export { CreateCopyInteractor };
