var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
import * as THREE from 'three';
import ObjectFactory from './ObjectFactory';
import { isArray } from 'util';
import ObjectsGroup from './ObjectsGroup';
import RepetitionObject from './RepetitionObject';
import BaseGrid from './BaseGrid';
import CompoundObject from './CompoundObject';
import isEqual from 'lodash.isequal';
import cloneDeep from 'lodash.clonedeep';
import Union from './Union';
import Intersection from './Intersection';
import Difference from './Difference';
import TranslationHelper from './TranslationHelper';
import RotationHelper from './RotationHelper';
import PositionCalculator from './PositionCalculator';
var HelperType;
(function (HelperType) {
    HelperType["Rotation"] = "rotation";
    HelperType["Translation"] = "translation";
})(HelperType || (HelperType = {}));
var HelperAxis;
(function (HelperAxis) {
    HelperAxis["X"] = "x";
    HelperAxis["Y"] = "y";
    HelperAxis["Z"] = "z";
})(HelperAxis || (HelperAxis = {}));
var Scene = /** @class */ (function () {
    function Scene() {
        this.objectCollector = [];
        this.objectsInScene = [];
        this.objectsInTransition = [];
        this.setupScene();
        this.objectsGroup = new THREE.Group();
        this.lastJSON = this.toJSON();
        this.historyIndex = 0;
        this.history = [];
        this.setMaterials();
    }
    // TODO. Need to create children before of creating objects!!
    Scene.newFromJSON = function (json) {
        var scene = new Scene();
        try {
            scene.updateSceneFromJSON(json);
        }
        catch (e) {
            throw new Error("Error creating Scene. " + e);
        }
        return scene;
    };
    Scene.prototype.setMaterials = function () {
        this.highlightedMaterial = {
            opacity: 0.8,
            transparent: true,
        };
        this.transitionMaterial = {
            opacity: 0.8,
            transparent: true,
        };
    };
    Scene.prototype.canUndo = function () {
        return this.historyIndex > 0;
    };
    Scene.prototype.canRedo = function () {
        return this.historyIndex > this.history.length - 1;
    };
    /**
     * Returns the Scene JSON descriptor: Array of Objects.
     * It only contains designed by user objects.
     * It does not contain helpers, plane, etc.
     */
    Scene.prototype.toJSON = function () {
        return this.objectsInScene.map(function (object) { return cloneDeep(object.toJSON()); });
    };
    /**
     * Updates all the objects in a Scene, if object is not present. It adds it.
     * @param json json describin all the objects of the Scene
     */
    Scene.prototype.updateSceneFromJSON = function (json) {
        var _this = this;
        if (isEqual(json, this.toJSON()))
            return json;
        json.forEach(function (obj) {
            if (_this.objectInScene(obj))
                _this.getObject(obj).updateFromJSON(obj);
            else
                throw new Error("Object id " + obj.id + " not present in Scene");
        });
        return this.toJSON();
    };
    /**
     * Scene lights and basegrid
     */
    Scene.prototype.getSceneSetup = function () {
        var group = new THREE.Group();
        group.add(this.sceneSetup.ambientLight);
        group.add(this.sceneSetup.spotLight);
        group.add(this.sceneSetup.spotLight2);
        group.add(this.sceneSetup.base);
        return group;
    };
    Scene.prototype.getHelpers = function () {
        var group = new THREE.Group();
        //TODO
        return group;
    };
    /**
     * returns a THREE.Group object containing designed 3D objects .
     */
    Scene.prototype.getObjectsAsync = function () {
        return __awaiter(this, void 0, void 0, function () {
            var meshes;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (isEqual(this.lastJSON, this.toJSON()))
                            return [2 /*return*/, this.objectsGroup];
                        this.objectsGroup = new THREE.Group();
                        return [4 /*yield*/, Promise.all(this.objectsInScene.map(function (object) { return __awaiter(_this, void 0, void 0, function () {
                                var mesh;
                                return __generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0: return [4 /*yield*/, object.getMeshAsync()];
                                        case 1:
                                            mesh = _a.sent();
                                            mesh.userData = object.toJSON();
                                            return [2 /*return*/, mesh];
                                    }
                                });
                            }); }))];
                    case 1:
                        meshes = _a.sent();
                        meshes.forEach(function (mesh) {
                            _this.objectsGroup.add(mesh);
                        });
                        this.lastJSON = this.toJSON();
                        return [2 /*return*/, this.objectsGroup];
                }
            });
        });
    };
    Scene.prototype.getObjectInTransitionAsync = function () {
        return __awaiter(this, void 0, void 0, function () {
            var group_1;
            var _this = this;
            return __generator(this, function (_a) {
                if (this.objectsInTransition.length > 0) {
                    group_1 = new THREE.Group();
                    this.objectsInTransition.forEach(function (object) { return __awaiter(_this, void 0, void 0, function () {
                        var mesh, pos;
                        return __generator(this, function (_a) {
                            switch (_a.label) {
                                case 0: return [4 /*yield*/, object.getMeshAsync()];
                                case 1:
                                    mesh = (_a.sent()).clone();
                                    if (!(mesh instanceof THREE.Mesh)) return [3 /*break*/, 3];
                                    return [4 /*yield*/, this.getPositionAsync(object.toJSON())];
                                case 2:
                                    pos = _a.sent();
                                    mesh.material.setValues(this.transitionMaterial);
                                    mesh.position.set(pos.position.x, pos.position.y, pos.position.z);
                                    mesh.setRotationFromEuler(new THREE.Euler(pos.angle.x * Math.PI / 180, pos.angle.y * Math.PI / 180, pos.angle.z * Math.PI / 180));
                                    mesh.scale.set(pos.scale.x * 1.01, pos.scale.y * 1.01, pos.scale.z * 1.01);
                                    group_1.add(mesh);
                                    _a.label = 3;
                                case 3: return [2 /*return*/];
                            }
                        });
                    }); });
                    return [2 /*return*/, group_1];
                }
                else {
                    return [2 /*return*/, undefined];
                }
                return [2 /*return*/];
            });
        });
    };
    /**
     * Adds floor and lights.
     */
    Scene.prototype.setupScene = function () {
        //@David , esto debería ir en algún sitio de opciones de configuracion
        var gridConfig = {
            size: 200,
            smallGrid: {
                enabled: true,
                step: 2,
                color: 0xededed,
                lineWidth: 1,
            },
            bigGrid: {
                enabled: true,
                step: 10,
                color: 0xcdcdcd,
                lineWidth: 2,
            },
            centerGrid: {
                enabled: true,
                color: 0x9a9a9a,
                lineWidth: 2,
            },
            plane: {
                enabled: false,
                color: 0x98f5ff,
            },
        };
        this.sceneSetup = {
            base: new BaseGrid(gridConfig).getMesh(),
            ambientLight: new THREE.AmbientLight(0x666666),
            spotLight: new THREE.SpotLight(0xdddddd),
            spotLight2: new THREE.SpotLight(0xbbbbbb),
        };
        this.sceneSetup.spotLight.position.set(80, -100, 60);
        this.sceneSetup.spotLight2.position.set(-160, 200, -120);
    };
    /**
     * Adds object to Scene and ObjectCollector. It creates a new object and assings a new id
     * @param json object descriptor (it ignores id)
     */
    Scene.prototype.addNewObjectFromJSON = function (json) {
        try {
            var object = ObjectFactory.newFromJSON(json, this);
            return this.addExistingObject(object);
        }
        catch (e) {
            throw new Error("Cannot add new Object from JSON " + e);
        }
    };
    /**
     * Checks if object is present in ObectCollector Array
     * @param json object descriptor
     */
    Scene.prototype.objectInObjectCollector = function (json) {
        var obj = this.objectCollector.find(function (elem) { return elem.getID() === json.id; });
        if (obj)
            return true;
        else
            return false;
    };
    /**
     * Checks if object is present in Scene array
     * @param json object descriptor
     */
    Scene.prototype.objectInScene = function (json) {
        var obj = this.objectsInScene.find(function (elem) { return elem.getID() === json.id; });
        if (obj)
            return true;
        else
            return false;
    };
    /**
     * Removes Object or Array of Objects from objectCollector array
     * @param json Object or Array of objects
     */
    Scene.prototype.removeFromObjectCollector = function (json) {
        var _this = this;
        if (isArray(json)) {
            json.forEach(function (obj) { return _this.removeFromObjectCollector(obj); });
        }
        else {
            if (!this.objectInObjectCollector(json))
                throw new Error("Object id " + json.id + " not present in Scene");
            this.objectCollector = this.objectCollector.filter(function (obj) { return obj.getID() !== json.id; });
        }
        return this.toJSON();
    };
    /**
     * Removes Object or Array of Objects from BitbloqScene array
     * @param json Object or Array of objects
     */
    Scene.prototype.removeFromScene = function (json) {
        var _this = this;
        if (isArray(json)) {
            json.forEach(function (obj) { return _this.removeFromScene(obj); });
        }
        else {
            if (!this.objectInScene(json))
                throw new Error("Object id " + json.id + " not present in Scene");
            this.objectsInScene = this.objectsInScene.filter(function (obj) { return obj.getID() !== json.id; });
        }
        return this.toJSON();
    };
    /**
     * Clones an object and adds it to the scene (and objectCollector).
     * If object is not in Scene throws Erro
     * @param json object to be cloned
     */
    Scene.prototype.cloneOject = function (json) {
        if (this.objectInScene(json)) {
            var obj = this.getObject(json);
            var newobj = obj.clone();
            newobj.setViewOptions(json.viewOptions);
            this.addExistingObject(newobj);
            return this.toJSON();
        }
        else {
            throw new Error('Cannot clone unknown object');
        }
    };
    Scene.prototype.addExistingObject = function (object) {
        var _this = this;
        if (this.objectInObjectCollector(object.toJSON())) {
            throw Error('Object already in Scene');
        }
        else {
            //In case the object has children, they must be removed from BitbloqScene (remain in ObjectCollector)
            if (object instanceof CompoundObject) {
                var children = object.getChildren();
                children.forEach(function (child) {
                    var obj = child.toJSON();
                    if (_this.objectInScene(obj)) {
                        _this.removeFromScene(obj);
                    }
                    if (!_this.objectInObjectCollector(obj)) {
                        _this.addExistingObject(child);
                        _this.removeFromScene(obj);
                    }
                });
            }
            else if (object instanceof ObjectsGroup) {
                var children = object.getChildren();
                children.forEach(function (child) {
                    var obj = child.toJSON();
                    if (_this.objectInScene(obj)) {
                        _this.removeFromScene(obj);
                    }
                    if (!_this.objectInObjectCollector(obj)) {
                        _this.addExistingObject(child);
                        _this.removeFromScene(obj);
                    }
                });
            }
            else if (object instanceof RepetitionObject) {
                var original = object.getOriginal();
                var obj = original.toJSON();
                if (this.objectInScene(obj)) {
                    this.removeFromScene(obj);
                }
                if (!this.objectInObjectCollector(obj)) {
                    this.addExistingObject(original);
                    this.removeFromScene(obj);
                }
            }
            // finally, add object to scene and collector
            this.objectsInScene.push(object);
            this.objectCollector.push(object);
        }
        var sceneJSON = this.toJSON();
        //Add to history
        this.history = this.history.slice(0, this.historyIndex);
        this.history.push(sceneJSON);
        this.historyIndex = this.history.length - 1;
        return sceneJSON;
    };
    /**
     * Removes Object from both Scene and ObjectCollector.
     * If object is not present is does NOT anything.
     * @param json json object descriptor (only id is important)
     */
    Scene.prototype.removeObject = function (obj) {
        try {
            this.removeFromScene(obj);
            this.removeFromObjectCollector(obj);
        }
        catch (e) {
            throw new Error("Cannot Remove Object from Scene: " + e);
        }
        var sceneJSON = this.toJSON();
        //Add to history if someting has changed
        this.history = this.history.slice(0, this.historyIndex);
        this.history.push(sceneJSON);
        this.historyIndex = this.history.length - 1;
        return sceneJSON;
    };
    /**
     * Returns a reference to the specified Object
     * @param obj Object descriptor to retreive
     */
    Scene.prototype.getObject = function (obj) {
        var id = obj.id;
        var foundObj = this.objectCollector.find(function (object) { return object.getID() === id; });
        if (!foundObj)
            throw new Error("Scene.getObject(). Object " + id + " not found");
        return foundObj;
    };
    /**
     * Updates object if its present on the objects collector.
     * If not it triggers an error exception.
     * @param json json describing object
     */
    Scene.prototype.updateObject = function (obj) {
        var _this = this;
        var id = obj.id;
        var object = this.objectCollector.find(function (obj) { return id === obj.getID(); });
        if (object)
            object.updateFromJSON(obj);
        else
            throw new Error("Object id " + id + " not found");
        //Objects in Transition
        this.objectsInTransition = [];
        var parent = object.getParent();
        if (parent instanceof CompoundObject) {
            parent.getChildren().forEach(function (object) {
                _this.objectsInTransition.push(object);
            });
        }
        var sceneJSON = this.toJSON();
        //Add to history
        this.history = this.history.slice(0, this.historyIndex);
        this.history.push(sceneJSON);
        this.historyIndex = this.history.length - 1;
        return sceneJSON;
    };
    /**
     *
     * @param json object descriptor
     */
    Scene.prototype.getPositionAsync = function (json) {
        return __awaiter(this, void 0, void 0, function () {
            var obj;
            return __generator(this, function (_a) {
                try {
                    obj = this.getObject(json);
                    return [2 /*return*/, new PositionCalculator(obj).getPositionAsync()];
                }
                catch (e) {
                    throw new Error("Cannot find object: " + e);
                }
                return [2 /*return*/];
            });
        });
    };
    /**
     * It removes the CompoundObject from Scene and ObjectCollector.
     * It adds the children to the Scene
     * @param json CompoundObject Descriptor. It only pays attention to id.
     */
    Scene.prototype.undoCompound = function (json) {
        var _this = this;
        try {
            if (![Union.typeName, Difference.typeName, Intersection.typeName].includes(json.type))
                throw new Error("Not a Compound object. " + json.type);
            if (!this.objectInScene(json))
                throw new Error("Object " + json.id + " not present in Scene");
            this.removeFromObjectCollector(json);
            this.removeFromScene(json);
            json.children.forEach(function (childJSON) {
                if (_this.objectInObjectCollector(childJSON))
                    _this.objectsInScene.push(_this.getObject(childJSON));
                else
                    throw new Error("Unexepected Error. Object " + childJSON.id + " not in Object Collector");
            });
        }
        catch (e) {
            throw new Error("undoCompoundError " + e);
        }
        return this.toJSON();
    };
    /**
     * It removes the ObjectsGroup from Scene and ObjectCollector.
     * It adds the members of the group to de Scene.
     * @param json group object descriptor (it only pays attention to id)
     */
    Scene.prototype.unGroup = function (json) {
        var _this = this;
        try {
            var group = this.getObject(json);
            if (!(group instanceof ObjectsGroup))
                throw new Error("Object is not a group");
            var objects = group.unGroup();
            // add the members of the group to the Scene
            objects.forEach(function (object) {
                _this.objectsInScene.push(object);
            });
            //remove ObjectsGroups from Scene and ObjectCollector
            return this.removeObject(json);
        }
        catch (e) {
            throw new Error("Cannog ungroup. Unknown group " + e);
        }
    };
    /**
     * It removes Object from Scene and ObjectCollector.
     * It transform the Object to a ObjectsGroup and add it to the Scene and ObjectCollector.
     * @param json Object descriptor. It only pays attention to id
     */
    Scene.prototype.convertToGroup = function (json) {
        debugger;
        if (json.type === RepetitionObject.typeName) {
            return this.repetitionToGroup(json);
        }
        else {
            throw new Error("Cannot Convert " + json.type + " to Group");
        }
    };
    /**
     * It removes RepetitionObject from Scene and ObjectCollector.
     * It transform the RepetitionObject to a ObjectsGroup and add it to the Scene and ObjectCollector.
     * @param json RepetitionObject descriptor. It only pays attention to id
     */
    Scene.prototype.repetitionToGroup = function (json) {
        try {
            var rep = this.getObject(json);
            if (!(rep instanceof RepetitionObject))
                throw new Error("Object is not a RepetitionObject");
            var objects = rep.getGroup().unGroup();
            // //add objects to ObjectCollector
            // objects.forEach(object => {
            //   this.objectCollector.push(object);
            // });
            var group = new ObjectsGroup(objects);
            // add new group to scene
            this.addExistingObject(group);
            // this.objectCollector.push(group);
            // this.objectsInScene.push(group);
            //remove original object in repetion from ObjectCollector
            var original = rep.getOriginal();
            this.removeFromObjectCollector(original.toJSON());
            //remove ObjectsGroups from Scene and ObjectCollector
            this.removeFromObjectCollector(json);
            this.removeFromScene(json);
        }
        catch (e) {
            throw new Error("Cannot ungroup. Unknown Object " + e);
        }
        return this.toJSON();
    };
    // Establece el helper que debe mostrarse en la vista 3d
    // Si no se le pasa ningún parámetro entonces no mostrar ninguno
    Scene.prototype.setActiveHelperAsync = function (helperDescription) {
        return __awaiter(this, void 0, void 0, function () {
            var type_1, object, axis, relative, obj, mesh, helper, helper, e_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.helpers = [];
                        if (!!helperDescription) return [3 /*break*/, 1];
                        return [2 /*return*/, this.helpers];
                    case 1:
                        type_1 = helperDescription.type, object = helperDescription.object, axis = helperDescription.axis, relative = helperDescription.relative;
                        _a.label = 2;
                    case 2:
                        _a.trys.push([2, 4, , 5]);
                        obj = this.getObject(object);
                        return [4 /*yield*/, obj.getMeshAsync()];
                    case 3:
                        mesh = _a.sent();
                        if (type_1 === 'rotation') {
                            helper = new RotationHelper(mesh, axis, relative);
                            this.helpers.push(helper.mesh);
                            return [2 /*return*/, this.helpers];
                        }
                        else if (type_1 === 'translation') {
                            helper = new TranslationHelper(mesh, axis, relative);
                            this.helpers.push(helper.mesh);
                            return [2 /*return*/, this.helpers];
                        }
                        else {
                            throw new Error("Unknown helper type: " + type_1);
                        }
                        return [3 /*break*/, 5];
                    case 4:
                        e_1 = _a.sent();
                        throw new Error("Unable to make helper: " + e_1);
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    // Deshace la última operación y devuelve la escena después de deshacer
    Scene.prototype.undo = function () { };
    // Rehace la última operación y devuelve la escena después de rehacer
    Scene.prototype.redo = function () { };
    return Scene;
}());
export default Scene;
//# sourceMappingURL=Scene.js.map