/**
 * Copyright (c) 2018 Bitbloq (BQ)
 *
 * License: MIT
 *
 * long description for the file
 *
 * @summary short description for the file
 * @author David García <https://github.com/empoalp>, Alberto Valero <https://github.com/avalero>
 *
 * Created at     : 2018-11-09 09:31:03
 * Last modified  : 2018-11-28 12:46:25
 */
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    }
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
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 Object3D from './Object3D';
import ObjectsCommon from './ObjectsCommon';
import cloneDeep from 'lodash.clonedeep';
import isEqual from 'lodash.isequal';
import * as THREE from 'three';
import './custom.d';
import CompoundWorker from './compound.worker';
var CompoundObject = /** @class */ (function (_super) {
    __extends(CompoundObject, _super);
    function CompoundObject(children, operations, viewOptions) {
        if (children === void 0) { children = []; }
        if (operations === void 0) { operations = []; }
        if (viewOptions === void 0) { viewOptions = ObjectsCommon.createViewOptions(); }
        var _this = _super.call(this, viewOptions, operations) || this;
        if (children.length === 0)
            throw new Error('Compound Object requires at least one children');
        _this.children = children;
        //children will have this CompoundObject as Parent
        _this.children.forEach(function (child) { return child.setParent(_this); });
        _this._meshUpdateRequired = true;
        return _this;
    }
    CompoundObject.prototype.getChildren = function () {
        return this.children;
    };
    Object.defineProperty(CompoundObject.prototype, "meshUpdateRequired", {
        get: function () {
            var _this = this;
            this.children.forEach(function (child) {
                _this._meshUpdateRequired =
                    _this._meshUpdateRequired ||
                        child.meshUpdateRequired ||
                        child.pendingOperation;
            });
            return this._meshUpdateRequired;
        },
        set: function (a) {
            this._meshUpdateRequired = a;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(CompoundObject.prototype, "pendingOperation", {
        get: function () {
            var _this = this;
            this.children.forEach(function (child) {
                _this._pendingOperation = _this._pendingOperation || child.pendingOperation;
            });
            return this._pendingOperation;
        },
        set: function (a) {
            this._pendingOperation = a;
        },
        enumerable: true,
        configurable: true
    });
    CompoundObject.prototype.applyOperationsAsync = function () {
        return __awaiter(this, void 0, void 0, function () {
            var ch_mesh;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.children[0].getMeshAsync()];
                    case 1:
                        ch_mesh = _a.sent();
                        this.mesh.position.x = ch_mesh.position.x;
                        this.mesh.position.y = ch_mesh.position.y;
                        this.mesh.position.z = ch_mesh.position.z;
                        this.mesh.quaternion.set(ch_mesh.quaternion.x, ch_mesh.quaternion.y, ch_mesh.quaternion.z, ch_mesh.quaternion.w);
                        this.mesh.scale.x = 1;
                        this.mesh.scale.y = 1;
                        this.mesh.scale.z = 1;
                        this.operations.forEach(function (operation) {
                            // Translate operation
                            if (operation.type === Object3D.createTranslateOperation().type) {
                                _this.applyTranslateOperation(operation);
                            }
                            else if (operation.type === Object3D.createRotateOperation().type) {
                                _this.applyRotateOperation(operation);
                            }
                            else if (operation.type === Object3D.createScaleOperation().type) {
                                _this.applyScaleOperation(operation);
                            }
                            else if (operation.type === Object3D.createMirrorOperation().type) {
                                _this.applyMirrorOperation(operation);
                            }
                            else {
                                throw Error('ERROR: Unknown Operation');
                            }
                        });
                        this.mesh.updateMatrixWorld(true);
                        this.mesh.updateMatrix();
                        this._pendingOperation = false;
                        return [2 /*return*/];
                }
            });
        });
    };
    CompoundObject.prototype.computeMeshAsync = function () {
        return __awaiter(this, void 0, void 0, function () {
            var _this = this;
            return __generator(this, function (_a) {
                this.meshPromise = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
                    var _this = this;
                    return __generator(this, function (_a) {
                        switch (_a.label) {
                            case 0:
                                if (!this.meshUpdateRequired) return [3 /*break*/, 1];
                                if (this.worker) {
                                    this.worker.terminate();
                                    this.worker = null;
                                }
                                this.worker = new CompoundWorker();
                                // listen to events from web worker
                                this.worker.onmessage = function (event) {
                                    if (event.data.status !== 'ok') {
                                        _this.worker.terminate();
                                        _this.worker = null;
                                        reject(new Error('Compound Object Error'));
                                    }
                                    var message = event.data;
                                    //recompute object form vertices and normals
                                    _this.fromBufferData(message.vertices, message.normals).then(function (mesh) {
                                        _this.mesh = mesh;
                                        if (_this.mesh instanceof THREE.Mesh) {
                                            _this.applyOperationsAsync().then(function () {
                                                _this._meshUpdateRequired = false;
                                                if (_this.viewOptionsUpdateRequired) {
                                                    _this.applyViewOptions();
                                                    _this._viewOptionsUpdateRequired = false;
                                                }
                                                _this.worker.terminate();
                                                _this.worker = null;
                                                resolve(_this.mesh);
                                            });
                                        }
                                        else {
                                            _this.worker.terminate();
                                            _this.worker = null;
                                            reject(new Error('Mesh not computed correctly'));
                                        }
                                    });
                                };
                                // END OF EVENT HANDLER
                                //Lets create an array of vertices and normals for each child
                                this.toBufferArrayAsync().then(function (bufferArray) {
                                    var message = {
                                        type: _this.getTypeName(),
                                        numChildren: _this.children.length,
                                        bufferArray: bufferArray,
                                    };
                                    _this.worker.postMessage(message, bufferArray);
                                });
                                return [3 /*break*/, 4];
                            case 1:
                                if (!this.pendingOperation) return [3 /*break*/, 3];
                                return [4 /*yield*/, this.applyOperationsAsync()];
                            case 2:
                                _a.sent();
                                _a.label = 3;
                            case 3:
                                this.applyViewOptions();
                                resolve(this.mesh);
                                _a.label = 4;
                            case 4: return [2 /*return*/];
                        }
                    });
                }); });
                return [2 /*return*/, this.meshPromise];
            });
        });
    };
    CompoundObject.prototype.fromBufferData = function (vertices, normals) {
        var _this = this;
        return new Promise(function (resolve, reject) {
            var buffGeometry = new THREE.BufferGeometry();
            buffGeometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
            buffGeometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
            var mesh = new THREE.Mesh(buffGeometry, new THREE.MeshLambertMaterial());
            _this.applyViewOptions(mesh);
            resolve(mesh);
        });
    };
    CompoundObject.prototype.toBufferArrayAsync = function () {
        var _this = this;
        return new Promise(function (resolve, reject) {
            var bufferArray = [];
            Promise.all(_this.children.map(function (child) { return child.getMeshAsync(); })).then(function (meshes) {
                meshes.forEach(function (mesh) {
                    var geom = mesh.geometry;
                    var bufferGeom;
                    if (geom instanceof THREE.BufferGeometry) {
                        bufferGeom = geom;
                    }
                    else {
                        bufferGeom = new THREE.BufferGeometry().fromGeometry(geom);
                    }
                    var verticesBuffer = new Float32Array(bufferGeom.getAttribute('position').array).buffer;
                    var normalsBuffer = new Float32Array(bufferGeom.getAttribute('normal').array).buffer;
                    var positionBuffer = Float32Array.from(mesh.matrixWorld.elements).buffer;
                    bufferArray.push(verticesBuffer);
                    bufferArray.push(normalsBuffer);
                    bufferArray.push(positionBuffer);
                });
                resolve(bufferArray);
            });
        });
    };
    CompoundObject.prototype.addChildren = function (child) {
        this.children.push(child);
        this._meshUpdateRequired = true;
    };
    CompoundObject.prototype.setChildren = function (children) {
        if (!isEqual(children, this.children)) {
            this.children = children.slice();
            this._meshUpdateRequired = true;
        }
    };
    CompoundObject.prototype.toJSON = function () {
        return cloneDeep(__assign({}, _super.prototype.toJSON.call(this), { children: this.children.map(function (obj) { return obj.toJSON(); }) }));
    };
    /**
     * Returns Object Reference if found in children. If not, throws Error.
     * @param obj Object descriptor
     */
    CompoundObject.prototype.getChild = function (obj) {
        var result = this.children.find(function (object) { return object.getID() === obj.id; });
        if (result)
            return result;
        else
            throw new Error("Object id " + obj.id + " not found in group");
    };
    CompoundObject.prototype.updateFromJSON = function (object) {
        var _this = this;
        if (this.id !== object.id)
            throw new Error('Object id does not match with JSON id');
        var newchildren = [];
        //update children
        try {
            object.children.forEach(function (obj) {
                var objToUpdate = _this.getChild(obj);
                newchildren.push(objToUpdate);
                objToUpdate.updateFromJSON(obj);
            });
            if (!isEqual(this.children, newchildren)) {
                this.meshUpdateRequired = true;
                this.children = newchildren.slice(0);
            }
        }
        catch (e) {
            throw new Error("Cannot update Compound Object: " + e);
        }
        //update operations and view options
        var vO = __assign({}, ObjectsCommon.createViewOptions(), object.viewOptions);
        this.setOperations(object.operations);
        this.setViewOptions(vO);
        // if anything has changed, recompute mesh
        if (!isEqual(this.lastJSON, this.toJSON())) {
            this.lastJSON = this.toJSON();
            this.meshPromise = this.computeMeshAsync();
            var obj = this.getParent();
            while (obj) {
                obj.meshUpdateRequired = true;
                obj.computeMeshAsync();
                obj = obj.getParent();
            }
        }
    };
    return CompoundObject;
}(Object3D));
export default CompoundObject;
//# sourceMappingURL=CompoundObject.js.map