import { appClearAngles, appClearEdges, appClearNodes, appDeleteAngle, appDeleteEdge, appDeleteNode, appSelectAngle, appSelectEdge, appSelectNode, appSetAngle, appSetBoundingBoxVisible, appSetCamera, appSetControlsEnabled, appSetDisplayMode, appSetEdge, appSetNode, appSetNodesEnabled, appSetOrientation, appSetSlicesIndex, appSetSlicesWindowCenter, appSetSlicesWindowWidth, appSetSrc, appSetSrcLoaded, appSetVolumeSteps, appSetVolumeWindowCenter, appSetVolumeWindowWidth } from "../../redux/actions";
import { configureStore } from "../../redux/store";
import { GetUtils, ThreeUtils, CreateUtils } from "../../utils";
import { Constants } from "../../Constants";
import { MeshFileType, DisplayMode } from "../../enums";
import { AlGltfModelEvents, AlNodeEvents, AlNodeSpawnerEvents, AlOrbitControlEvents } from "../../aframe";
export class Aleph {
    constructor() {
        this._intersectingNode = null;
        this._isShiftDown = false;
        this.width = "640px";
        this.height = "480px";
        this.debug = false;
        this.spinnerColor = "#fff";
    }
    async load(src) {
        const fileExtension = GetUtils.getFileExtension(src);
        if (Object.values(MeshFileType).includes(fileExtension)) {
            if (this.displayMode !== DisplayMode.MESH) {
                throw new Error("When setting 'src' to a mesh file you must set 'displayMode' to 'mesh'");
            }
        }
        else {
            if (this.displayMode === DisplayMode.MESH) {
                throw new Error("When setting 'src' to a non-mesh file you must set 'displayMode' to either 'slices' or 'volume'");
            }
        }
        if (this.src) {
            this._setSrc(null);
            setTimeout(() => {
                this._setSrc(src);
            }, Constants.minLoadingMS);
        }
        else {
            this._setSrc(src);
        }
    }
    resize() {
        if (this.srcLoaded) {
            const camera = this._scene.sceneEl
                .camera;
            const renderer = this._scene.sceneEl.renderer;
            camera.aspect =
                this._container.offsetWidth / this._container.offsetHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(this._container.offsetWidth, this._container.offsetHeight);
        }
    }
    async setNode(node) {
        this._setNode(node);
    }
    async setNodes(nodes) {
        this._setNodes(nodes);
    }
    async deleteNode(nodeId) {
        this._deleteNode(nodeId);
    }
    async clearNodes() {
        this._clearNodes();
    }
    async selectNode(nodeId) {
        this._selectNode(nodeId, true);
    }
    async setEdge(edge) {
        this._setEdge(edge);
    }
    async setDisplayMode(displayMode) {
        this.appSetDisplayMode(displayMode);
    }
    async setNodesEnabled(enabled) {
        this._setNodesEnabled(enabled);
    }
    async setBoundingBoxVisible(visible) {
        this._setBoundingBoxVisible(visible);
    }
    componentWillLoad() {
        CreateUtils.createAframeComponents();
        this.store.setStore(configureStore({}));
        this.store.mapStateToProps(this, state => {
            const { app: { angles, boundingBoxVisible, camera, controlsEnabled, displayMode, edges, nodes, nodesEnabled, orientation, selected, slicesIndex, slicesWindowCenter, slicesWindowWidth, src, srcLoaded, volumeSteps, volumeWindowCenter, volumeWindowWidth } } = state;
            return {
                angles,
                boundingBoxVisible,
                camera,
                controlsEnabled,
                displayMode,
                edges,
                nodes,
                nodesEnabled,
                orientation,
                selected,
                slicesIndex,
                slicesWindowCenter,
                slicesWindowWidth,
                src,
                srcLoaded,
                volumeSteps,
                volumeWindowCenter,
                volumeWindowWidth
            };
        });
        this.store.mapDispatchToProps(this, {
            appClearAngles,
            appClearEdges,
            appClearNodes,
            appDeleteAngle,
            appDeleteEdge,
            appDeleteNode,
            appSelectAngle,
            appSelectEdge,
            appSelectNode,
            appSetAngle,
            appSetBoundingBoxVisible,
            appSetCamera,
            appSetControlsEnabled,
            appSetDisplayMode,
            appSetEdge,
            appSetNode,
            appSetNodesEnabled,
            appSetOrientation,
            appSetSlicesIndex,
            appSetSlicesWindowCenter,
            appSetSlicesWindowWidth,
            appSetSrc,
            appSetSrcLoaded,
            appSetVolumeSteps,
            appSetVolumeWindowCenter,
            appSetVolumeWindowWidth
        });
        this._animationFinished = this._animationFinished.bind(this);
        this._canvasMouseDown = this._canvasMouseDown.bind(this);
        this._canvasMouseUp = this._canvasMouseUp.bind(this);
        this._controlsDisabledHandler = this._controlsDisabledHandler.bind(this);
        this._controlsEnabledHandler = this._controlsEnabledHandler.bind(this);
        this._controlsUpdated = this._controlsUpdated.bind(this);
        this._intersectingNodeEventHandler = this._intersectingNodeEventHandler.bind(this);
        this._intersectionClearedEventHandler = this._intersectionClearedEventHandler.bind(this);
        this._nodeMovedEventHandler = this._nodeMovedEventHandler.bind(this);
        this._nodeSelectedEventHandler = this._nodeSelectedEventHandler.bind(this);
        this._setNodeEventHandler = this._setNodeEventHandler.bind(this);
        this._srcLoaded = this._srcLoaded.bind(this);
        this._validTargetEventHandler = this._validTargetEventHandler.bind(this);
    }
    _renderSpinner() {
        if (!this.srcLoaded) {
            return (h("a-entity", { "al-fixed-to-orbit-camera": `
          distanceFromTarget: 20;
          target: ${this.camera ? this.camera.position : new THREE.Vector3(0, 0, 0)};
        ` },
                h("a-entity", { animation: "property: rotation; to: 0 120 0; loop: true; dur: 1000; easing: easeInOutQuad", geometry: "primitive: al-spinner;", material: `color: ${this.spinnerColor};` })));
        }
        return null;
    }
    _renderSrc() {
        if (!this.src) {
            return null;
        }
        let backScale = 0;
        let backBoard = null;
        if (this._boundingSphereRadius) {
            backScale = this._boundingSphereRadius * Constants.splashBackSize;
            backBoard = (h("a-entity", { class: "collidable", id: "back-board", geometry: `primitive: plane; height: ${backScale}; width: ${backScale}`, "al-fixed-to-orbit-camera": `
          distanceFromTarget: ${this._boundingSphereRadius};
          target: ${this.camera ? this.camera.target : new THREE.Vector3(0, 0, 0)};
        `, material: `
          wireframe: true;
          side: double;
        `, ref: el => (this._backBoard = el) }));
        }
        switch (this.displayMode) {
            case DisplayMode.MESH: {
                return [
                    h("a-entity", { "al-node-spawner": `
              nodesEnabled: ${this.nodesEnabled};
            `, "al-edge-spawner": true, class: "collidable", id: "target-entity", "al-gltf-model": `
              src: url(${this.src});
              dracoDecoderPath: ${this.dracoDecoderPath};
            `, position: "0 0 0", scale: "1 1 1", ref: (el) => (this._targetEntity = el) }),
                    backBoard
                ];
            }
            default: {
                return [
                    h("a-entity", { "al-node-spawner": `
              nodesEnabled: ${this.nodesEnabled};
            `, "al-edge-spawner": true, class: "collidable", id: "target-entity", "al-volumetric-model": `
              src: url(${this.src});
            `, position: "0 0 0", scale: "1 1 1", ref: (el) => (this._targetEntity = el) }),
                    backBoard
                ];
            }
        }
    }
    _renderNodes() {
        return [...this.nodes].map((n) => {
            const [nodeId, node] = n;
            let textOffset = new THREE.Vector3(0, 2.5, 0);
            textOffset.multiplyScalar(node.scale);
            return (h("a-entity", { class: "collidable", id: nodeId, position: node.position, "al-node": `
            target: ${node.target};
            scale: ${node.scale};
            selected: ${this.selected === nodeId};
            nodesEnabled: ${this.nodesEnabled};
          ` },
                h("a-entity", { text: `
              value: ${node.text};
              side: double;
              align: center;
              baseline: bottom;
              anchor: center;
            `, "al-look-to-camera": true, "al-render-overlaid-text": true, position: ThreeUtils.vector3ToString(textOffset), id: `${nodeId}-label` })));
        });
    }
    _renderEdges() {
        return [...this.edges].map((n) => {
            const [edgeId, edge] = n;
            let line = "";
            let centoid = new THREE.Vector3(0, 0, 0);
            let dist = 0;
            if (this.nodes.get(edge.node2Id)) {
                const start = this.nodes.get(edge.node1Id).position;
                const end = this.nodes.get(edge.node2Id).position;
                line = `
        start: ${this.nodes.get(edge.node1Id).position};
        end: ${this.nodes.get(edge.node2Id).position};
        `;
                const sv = ThreeUtils.stringToVector3(start);
                const ev = ThreeUtils.stringToVector3(end);
                dist = sv.clone().distanceTo(ev.clone());
                centoid.copy(sv
                    .clone()
                    .sub(ev.clone())
                    .normalize()
                    .multiplyScalar(dist / 2));
            }
            return (h("a-entity", { class: "collidable", id: edgeId, line: line, "al-render-overlaid-line": true },
                h("a-entity", { id: `${edgeId}-title`, text: `
              value: ${dist + " units"};
              side: double;
              align: center;
              baseline: bottom;
              anchor: center;
            `, "al-look-to-camera": true, "al-render-overlaid-text": true, position: ThreeUtils.vector3ToString(centoid) })));
        });
    }
    _renderLights() {
        return [
            h("a-entity", { id: "light-1", light: "type: directional; color: #ffffff; intensity: 0.75", position: "1 1 1" }),
            h("a-entity", { id: "light-2", light: "type: directional; color: #002958; intensity: 0.5", position: "-1 -1 -1" }),
            h("a-entity", { id: "light-3", light: "type: ambient; color: #d0d0d0; intensity: 1" })
        ];
    }
    _renderCamera() {
        return (h("a-camera", { fov: Constants.cameraValues.fov, near: Constants.cameraValues.near, "look-controls": "enabled: false", far: Constants.cameraValues.far, id: "mainCamera", cursor: "rayOrigin: mouse", raycaster: "objects: .collidable", "al-orbit-control": `
          maxPolarAngle: ${Constants.cameraValues.maxPolarAngle};
          minDistance: ${Constants.cameraValues.minDistance};
          screenSpacePanning: true;
          rotateSpeed: ${Constants.cameraValues.rotateSpeed};
          zoomSpeed: ${Constants.cameraValues.zoomSpeed};
          enableDamping: true;
          dampingFactor: ${Constants.cameraValues.dampingFactor};
          controlTarget: ${ThreeUtils.vector3ToString(this.camera ? this.camera.target : new THREE.Vector3(0, 0, 0))};
          controlPosition: ${ThreeUtils.vector3ToString(this.camera ? this.camera.position : new THREE.Vector3(0, 0, 0))};
          enabled: ${this.controlsEnabled};
          animating: ${this.camera && this.camera.animating ? this.camera.animating : false}
        `, ref: el => (this._camera = el) }, this._renderSpinner()));
    }
    _renderScene() {
        return (h("a-scene", { embedded: true, renderer: "colorManagement: true;", "vr-mode-ui": "enabled: false", ref: el => (this._scene = el), ortho: true },
            this._renderSrc(),
            this._renderNodes(),
            this._renderEdges(),
            this._renderLights(),
            this._renderCamera()));
    }
    render() {
        return (h("div", { id: "al-container", style: {
                width: this.width,
                height: this.height
            }, ref: el => (this._container = el) }, this._renderScene()));
    }
    _createEdge(node1, node2) {
        const newEdge = {
            node1Id: node1,
            node2Id: node2
        };
        const edgeId = GetUtils.getNextEdgeId(this.edges);
        this._setEdge([edgeId, newEdge]);
    }
    _getAppState() {
        return this.store.getState().app;
    }
    _clearNodes() {
        this.appClearNodes();
        this.onChanged.emit(this._getAppState());
    }
    _setNodes(nodes) {
        nodes.forEach((value, key) => {
            this.appSetNode([key, value]);
        });
        this.onChanged.emit(this._getAppState());
    }
    _deleteNode(nodeId) {
        this.appDeleteNode(nodeId);
        this.onChanged.emit(this._getAppState());
    }
    _setNode(node) {
        ThreeUtils.waitOneFrame(() => {
            this.appSetNode(node);
            this.onChanged.emit(this._getAppState());
        });
    }
    _setEdge(edge) {
        this.appSetEdge(edge);
        this.onChanged.emit(this._getAppState());
    }
    _selectNode(nodeId, animate) {
        if (animate && nodeId !== this.selected) {
            let animationStart = {
                position: this.camera.position.clone(),
                target: this.camera.target.clone()
            };
            let animationEnd = {
                position: new THREE.Vector3(-1, -1, -1),
                target: new THREE.Vector3(-1, -1, -1)
            };
            let result = GetUtils.getCameraStateFromNode(this.nodes.get(nodeId), this._boundingSphereRadius);
            if (result) {
                const diffPos = result.position.distanceTo(this.camera.position);
                let diffTarg;
                this.camera.target
                    ? (diffTarg = result.target.distanceTo(this.camera.target))
                    : (diffTarg = 0);
                if (diffPos > 0 || diffTarg > 0) {
                    diffPos > 0
                        ? animationEnd.position.copy(result.position)
                        : animationEnd.position.copy(this.camera.position);
                    diffTarg > 0
                        ? animationEnd.target.copy(result.target)
                        : animationEnd.target.copy(this.camera.target);
                    ThreeUtils.sendAnimationCache(this._scene, animationStart, animationEnd, diffPos > 0, diffTarg > 0);
                    ThreeUtils.waitOneFrame(() => {
                        this.appSetCamera({
                            animating: true
                        });
                        this.appSelectNode(nodeId);
                        this.onChanged.emit(this._getAppState());
                    });
                }
            }
        }
        else {
            this.appSelectNode(nodeId);
            this.onChanged.emit(this._getAppState());
        }
    }
    _setNodesEnabled(enabled) {
        this.appSetNodesEnabled(enabled);
        this.onChanged.emit(this._getAppState());
    }
    _setBoundingBoxVisible(visible) {
        this.appSetBoundingBoxVisible(visible);
        this.onChanged.emit(this._getAppState());
    }
    _setSrc(src) {
        this.appSetSrc(src);
        this.onChanged.emit(this._getAppState());
    }
    _srcLoaded() {
        const mesh = this._targetEntity.object3DMap.mesh;
        mesh.geometry.computeBoundingSphere();
        this._boundingSphereRadius = mesh.geometry.boundingSphere.radius;
        let result = GetUtils.getCameraStateFromEntity(this._targetEntity);
        if (result) {
            this.appSetCamera({
                position: result.position,
                target: result.target
            });
        }
        this.appSetSrcLoaded(true);
        this.onChanged.emit(this._getAppState());
    }
    _canvasMouseDown(event) {
        this._isShiftDown = event.shiftKey;
    }
    _canvasMouseUp(_event) {
        this._isShiftDown = false;
    }
    _controlsUpdated(event) {
        this.appSetCamera(event.detail.cameraSerial);
    }
    _controlsEnabledHandler(_event) {
        this.appSetControlsEnabled(true);
        ThreeUtils.enableCamera(this._camera, true);
    }
    _controlsDisabledHandler(_event) {
        this.appSetControlsEnabled(false);
        ThreeUtils.enableCamera(this._camera, false);
    }
    _intersectionClearedEventHandler(_event) {
        this._intersectingNode = null;
    }
    _intersectingNodeEventHandler(event) {
        this._intersectingNode = event.detail.id;
    }
    _setNodeEventHandler(event) {
        if (this.nodesEnabled &&
            this._validTarget &&
            this._intersectingNode === null) {
            let intersection = event.detail.detail.intersection;
            const nodeId = GetUtils.getNextNodeId(this.nodes);
            const newNode = {
                target: ThreeUtils.vector3ToString(this._targetEntity.object3D.position),
                position: ThreeUtils.vector3ToString(intersection.point),
                scale: this._boundingSphereRadius / Constants.nodeSizeRatio,
                text: nodeId
            };
            const previousSelected = this.selected;
            this._setNode([nodeId, newNode]);
            if (this._isShiftDown &&
                this.selected) {
                this._createEdge(previousSelected, nodeId);
            }
        }
    }
    _validTargetEventHandler(event) {
        this._validTarget = event.detail.valid;
    }
    _animationFinished(_event) {
        this.appSetCamera({
            animating: false
        });
    }
    _nodeSelectedEventHandler(event) {
        if (this._intersectingNode !== null &&
            this.nodesEnabled &&
            this.selected !== null &&
            this.selected !== this._intersectingNode &&
            this._isShiftDown) {
            this._createEdge(this.selected, this._intersectingNode);
        }
        this._selectNode(event.detail.id, false);
    }
    _nodeMovedEventHandler(event) {
        const nodeId = event.detail.id;
        const raycaster = this._camera.components.raycaster;
        let intersection = raycaster.getIntersection(this._targetEntity);
        if (!intersection) {
            intersection = raycaster.getIntersection(this._backBoard);
        }
        if (intersection) {
            this._setNode([
                nodeId,
                {
                    position: ThreeUtils.vector3ToString(intersection.point)
                }
            ]);
            const eventName = nodeId + Constants.movedEventString;
            this._scene.emit(eventName, {}, true);
        }
        else {
            console.log("No intersection!");
        }
    }
    _addEventListeners() {
        this._scene.canvas.addEventListener("mousedown", this._canvasMouseDown, {
            capture: false,
            once: false,
            passive: true
        });
        this._scene.canvas.addEventListener("mouseup", this._canvasMouseUp, {
            capture: false,
            once: false,
            passive: true
        });
        this._scene.addEventListener(AlOrbitControlEvents.ANIMATION_FINISHED, this._animationFinished, false);
        this._scene.addEventListener(AlOrbitControlEvents.UPDATED, this._controlsUpdated, false);
        this._scene.addEventListener(AlNodeEvents.CONTROLS_ENABLED, this._controlsEnabledHandler, false);
        this._scene.addEventListener(AlNodeEvents.CONTROLS_DISABLED, this._controlsDisabledHandler, false);
        this._scene.addEventListener(AlNodeEvents.DRAGGING, this._nodeMovedEventHandler, false);
        this._scene.addEventListener(AlNodeSpawnerEvents.ADD_NODE, this._setNodeEventHandler, false);
        this._scene.addEventListener(AlNodeEvents.SELECTED, this._nodeSelectedEventHandler, false);
        this._scene.addEventListener(AlNodeSpawnerEvents.VALID_TARGET, this._validTargetEventHandler, false);
        this._scene.addEventListener(AlGltfModelEvents.LOADED, this._srcLoaded, false);
        this._scene.addEventListener(AlNodeEvents.INTERSECTION, this._intersectingNodeEventHandler, false);
        this._scene.addEventListener(AlNodeEvents.INTERSECTION_CLEARED, this._intersectionClearedEventHandler, false);
    }
    componentDidLoad() { }
    componentDidUpdate() {
        if (this._scene) {
            this._addEventListeners();
        }
        if (this.debug) {
            try {
                const mat = this._camera.object3DMap.text
                    .material;
                if (mat) {
                    mat.transparent = true;
                }
            }
            catch (_a) { }
        }
    }
    static get is() { return "uv-aleph"; }
    static get properties() { return {
        "angles": {
            "state": true
        },
        "boundingBoxVisible": {
            "state": true
        },
        "camera": {
            "state": true
        },
        "clearNodes": {
            "method": true
        },
        "controlsEnabled": {
            "state": true
        },
        "debug": {
            "type": Boolean,
            "attr": "debug"
        },
        "deleteNode": {
            "method": true
        },
        "displayMode": {
            "state": true
        },
        "dracoDecoderPath": {
            "type": String,
            "attr": "draco-decoder-path"
        },
        "edges": {
            "state": true
        },
        "height": {
            "type": String,
            "attr": "height"
        },
        "load": {
            "method": true
        },
        "nodes": {
            "state": true
        },
        "nodesEnabled": {
            "state": true
        },
        "nodesVisible": {
            "state": true
        },
        "optionsEnabled": {
            "state": true
        },
        "optionsVisible": {
            "state": true
        },
        "orientation": {
            "state": true
        },
        "resize": {
            "method": true
        },
        "selected": {
            "state": true
        },
        "selectNode": {
            "method": true
        },
        "setBoundingBoxVisible": {
            "method": true
        },
        "setDisplayMode": {
            "method": true
        },
        "setEdge": {
            "method": true
        },
        "setNode": {
            "method": true
        },
        "setNodes": {
            "method": true
        },
        "setNodesEnabled": {
            "method": true
        },
        "slicesIndex": {
            "state": true
        },
        "slicesWindowCenter": {
            "state": true
        },
        "slicesWindowWidth": {
            "state": true
        },
        "spinnerColor": {
            "type": String,
            "attr": "spinner-color"
        },
        "src": {
            "state": true
        },
        "srcLoaded": {
            "state": true
        },
        "store": {
            "context": "store"
        },
        "volumeSteps": {
            "state": true
        },
        "volumeWindowCenter": {
            "state": true
        },
        "volumeWindowWidth": {
            "state": true
        },
        "width": {
            "type": String,
            "attr": "width"
        }
    }; }
    static get events() { return [{
            "name": "onChanged",
            "method": "onChanged",
            "bubbles": true,
            "cancelable": true,
            "composed": true
        }]; }
    static get style() { return "/**style-placeholder:uv-aleph:**/"; }
}
