/// <reference path="FDParserFactory.ts" />
/// <reference path="FDVisualisationFactory.ts" />
/// <reference path="FDEventHandler.ts" />
/// <reference path="resources/tween.js.d.ts" />
var FD;
(function (FD) {
    var FluidDiagrams = (function () {
        /**
        * Creates a new instance of FluidDiagrams
        * @class FluidDiagrams
        * @classdesc Fluid Diagrams Main Class.
        * @author Benedict Wright
        * @param {bool} [debug]
        * @param {String} [forceRenderer]. WebGL(Default), SVG(Not good), Canvas
        */
        function FluidDiagrams(debug, forceRenderer) {
            if (typeof debug === "undefined") { debug = true; }
            if (typeof forceRenderer === "undefined") { forceRenderer = "WebGL"; }
            this.debug = true;
            this.NodeMeshMap = {};
            this.trackBallControls = null;
            this.debug = debug;
            FD.debugPrinter.print("init", this.debug);
            this.parserFactory = new FD.FDParserFactory(debug);
            FD.debugPrinter.print("init parser factory", this.debug);
            this.visualisationFactory = new FD.FDVisualisationFactory(debug, this);
            FD.debugPrinter.print("init vis factory", this.debug);

            this.lights = [];
            this.geometryObjects = [];
            this.nodeSceneGraph = new THREE.Object3D();
            FD.debugPrinter.print("init scenegraph", this.debug);
            this.forceRenderer = forceRenderer;
            this.parserType = "";
            this.visualisationType = "";
        }
        /**
        * Sets dimension of the visualisation
        * @memberof FluidDiagrams
        * @function setDimesions
        * @param {int} width
        * @param {int} height
        */
        FluidDiagrams.prototype.setDimensions = function (width, height, scale, keepAspectRatio) {
            if (typeof scale === "undefined") { scale = false; }
            if (typeof keepAspectRatio === "undefined") { keepAspectRatio = true; }
            this.width = width;
            this.height = height;
            this.keepAspectRatio = keepAspectRatio;
            this.scale = scale;
            if (this.keepAspectRatio == true) {
                this.aspectRatio = height / width;
            }
        };

        /**
        * Sets the containing HTML element
        * @memberof FluidDiagrams
        * @function setContainer
        * @param {string} container
        */
        FluidDiagrams.prototype.setContainer = function (container) {
            this.container = $(container);
        };

        /**
        * Sets the parser type
        * @memberof FluidDiagrams
        * @function setParserType
        * @param {string} pType
        */
        FluidDiagrams.prototype.setParserType = function (pType) {
            this.parserType = pType;
            this.parser = this.parserFactory.getParser(pType);
        };

        /**
        * Returns the parser
        * @memberof FluidDiagrams
        * @function getParser
        */
        FluidDiagrams.prototype.getParser = function () {
            if (this.parser == null) {
                if (this.parserType == "")
                    return null;
                this.parser = this.parserFactory.getParser(this.parserType);
                return this.parser;
            } else {
                return this.parser;
            }
        };

        /**
        * Sets the visualisation type
        * @memberof FluidDiagrams
        * @function setVisualisationType
        * @param {string} vType
        */
        FluidDiagrams.prototype.setVisualisationType = function (vType) {
            this.visualisationType = vType;
            this.visualisation = this.visualisationFactory.getVisualisation(vType);
        };

        /**
        * Returns the visualisation
        * @memberof FluidDiagrams
        * @function getVisualisation
        */
        FluidDiagrams.prototype.getVisualisation = function () {
            if (this.visualisation == null) {
                if (this.visualisationType == "")
                    return null;
                this.visualisation = this.visualisationFactory.getVisualisation(this.visualisationType);
                return this.visualisation;
            } else {
                return this.visualisation;
            }
        };

        /**
        * Sets the Camera for the Scene
        * @memberof FluidDiagrams
        * @function setCamera
        * @param {THREE.Camera} camera
        */
        FluidDiagrams.prototype.setCamera = function (camera) {
            this.camera = camera;
        };

        /**
        * Returns the camera
        * @memberof FluidDiagrams
        * @function getCamera
        * @returns {THREE.Camera}
        */
        FluidDiagrams.prototype.getCamera = function () {
            return this.camera;
        };

        FluidDiagrams.prototype.activateTrackballControls = function () {
            this.trackBallControls = new THREE.TrackballControls(this.camera, this.domElement);

            this.trackBallControls.addEventListener('change', this.renderer);
        };

        /**
        * Sets the event handler
        * @memberof FluidDiagrams
        * @function setEventHandler
        * @param {FDEventHandler} eventHander
        */
        FluidDiagrams.prototype.setEventHandler = function (eventHander) {
            this.eventHandler = eventHander;
        };

        FluidDiagrams.prototype.getEventHandler = function () {
            return this.eventHandler;
        };

        /**
        * Adds a light source to the scene
        * @memberof FluidDiagrams
        * @function addLight
        * @param {THREE.Light} light
        */
        FluidDiagrams.prototype.addLight = function (light) {
            this.lights.push(light);
        };

        /**
        * executes the parser, executes the visualisation algorithm, calls createScene()
        * @memberof FluidDiagrams
        * @function run
        */
        FluidDiagrams.prototype.run = function () {
            this.height = this.container.height();
            this.width = this.container.width();
            FD.debugPrinter.print(this.width + " " + this.height, this.debug);

            this.parser.parse();
            this.nodes = this.parser.getAllNodes();
            this.rootNodes = this.parser.getRootNodes();

            this.visualisation.setDimensions(this.width, this.height);
            this.visualisation.setNodes(this.rootNodes);
            this.visualisation.setAllNodes(this.nodes);
            this.visualisation.visualise();

            //print to WEBGL
            this.createScene();
            //debugPrinter.print("created scene no Links", this.debug);
        };

        /**
        * calls the eventhandlers update method, gets called in every frame of the visualisation
        * @memberof FluidDiagrams
        * @function updateScene
        */
        FluidDiagrams.prototype.updateScene = function () {
            if (this.eventHandler != undefined) {
                this.eventHandler.update();
            }
            this.renderer.render(this.scene, this.camera);
            //if (this.trackBallControls != null) {
            //    this.trackBallControls.update();
            //}
        };

        /**
        * creates the render enginge (Canvas or WebGL). Adds the Nodes from the Parser to the scene. Adds all Scene Elements. And initialises the main loop
        * @memberof FluidDiagrams
        * @function createScene
        */
        FluidDiagrams.prototype.createScene = function () {
            var container = this.container;
            this.scene = new THREE.Scene();

            if (this.forceRenderer == "Canvas") {
                this.renderer = new THREE.CanvasRenderer();
            }
            if (this.forceRenderer == "SVG") {
                this.renderer = new (THREE).SVGRenderer();
            }

            if (this.forceRenderer != "SVG" && this.forceRenderer != "Canvas") {
                if (!window.WebGLRenderingContext) {
                    //NO WEBGL
                    this.renderer = new THREE.CanvasRenderer();

                    //alert("canvas Renderer decativated. ");
                    FD.debugPrinter.print("canvas rederer: using Software Acceleration!!SLOW", this.debug);
                } else {
                    try  {
                        var context = document.createElement('canvas').getContext('experimental-webgl');
                        if (context) {
                            this.renderer = new THREE.WebGLRenderer({ antialias: true });
                            FD.debugPrinter.print("WebGL Renderer", this.debug);
                        } else {
                            this.renderer = new THREE.CanvasRenderer();

                            //alert("canvas Renderer decativated. ");
                            FD.debugPrinter.print("canvas rederer: using Software Acceleration!!SLOW", this.debug);
                        }
                    } catch (e) {
                        FD.debugPrinter.print("some error occured during init of WebGL,retry canvas: " + e.message, this.debug);
                        this.renderer = new THREE.CanvasRenderer();
                        return;
                    }
                }
            }

            this.renderer.setSize(this.width, this.height);
            this.renderer.shadowMapEnabled = true;
            this.renderer.shadowMapSoft = true;
            this.renderer.setClearColor(new THREE.Color(this.visualisation.backgroundColor));
            this.scene.add(this.camera);

            if (this.debug) {
                var axes = new THREE.AxisHelper(20);
                axes.position = new THREE.Vector3(2, 2, 1);
                this.scene.add(axes);
            }

            for (var node in this.rootNodes) {
                this.scene.add(this.rootNodes[node].getNodeSceneGraph());
                //debugPrinter.print("adding: " + this.nodes[node].getUniqueId()+" at:"+this.nodes[node].getPosition().x+"/"+this.nodes[node].getPosition().y+"/"+this.nodes[node].getPosition().z, this.debug);
                //adding node to Map for later retrieval
            }

            for (var node in this.nodes) {
                //debugPrinter.print(this.nodes[node].getUniqueId()+"has ids: ",this.debug);
                var ids = this.nodes[node].getMeshIds();
                for (var id in ids) {
                    this.NodeMeshMap[ids[id]] = this.nodes[node];
                    //debugPrinter.print(ids[id],this.debug);
                }
                //debugPrinter.print("node: "+this.nodes[node].getUniqueId()+"length: " + this.nodes[node].getAllGeometry().length, this.debug);
            }

            for (var l in this.lights) {
                //debugPrinter.print("adding lights", this.debug);
                this.scene.add(this.lights[l]);
            }

            //debugPrinter.print("added lights", this.debug);
            this.scene.add(this.nodeSceneGraph);

            container.append(this.renderer.domElement);

            //debugPrinter.print("appended to container", this.debug);
            if (this.eventHandler != undefined) {
                this.addEventsHandlers();
            }

            this.mainloop();
        };

        /**
        * the visualisations main loop, calls the TWEEN.update function for all TWEEN animations. calls updateScene.
        * Requests a new animation Frame and calls itsself recursivly
        * @memberof FluidDiagrams
        * @function mainloop
        */
        FluidDiagrams.prototype.mainloop = function () {
            var self = this;

            //debugPrinter.print("updateScene", this.debug);
            TWEEN.update();
            this.updateScene();

            //debugPrinter.print("request new animation frame", this.debug);
            requestAnimationFrame(function () {
                self.mainloop();
            });
        };

        /**
        * Adds eventlisteners to the DOM element
        * @memberof FluidDiagrams
        * @function addEventsHandlers
        */
        FluidDiagrams.prototype.addEventsHandlers = function () {
            this.domElement = this.renderer.domElement;
            var that = this;

            //add many more event listeners
            this.domElement.addEventListener('mouseup', function (e) {
                that.onMouseUp(e);
            }, false);
            this.domElement.addEventListener('mousedown', function (e) {
                that.onMouseDown(e);
            }, false);
            this.domElement.addEventListener('mousemove', function (e) {
                that.onMouseMove(e);
            }, false);
            this.domElement.addEventListener('click', function (e) {
                that.onMouseClick(e);
            }, false);

            //IE9, Chrome, Sfari, Opera
            this.domElement.addEventListener("mousewheel", function (e) {
                that.onMouseWheel(e);
            }, false);

            //Firefox
            this.domElement.addEventListener("DOMMouseScroll", function (e) {
                that.onMouseWheel(e);
            }, false);

            this.domElement.addEventListener("mouseout", function (e) {
                that.onMouseOut(e);
            }, false);
            window.addEventListener("resize", function (e) {
                that.onWindowResize(e);
            }, false);
        };

        FluidDiagrams.prototype.onMouseOut = function (e) {
            this.onMouseUp(e);
        };

        FluidDiagrams.prototype.onMouseUp = function (e) {
            e.preventDefault();
            this.eventHandler.onMouseUp(e);
        };

        FluidDiagrams.prototype.onMouseClick = function (e) {
            e.preventDefault();
            this.eventHandler.onMouseClick(e, this.objectFromMouse(e.clientX, e.clientY));
        };

        FluidDiagrams.prototype.onMouseDown = function (e) {
            e.preventDefault();
            this.eventHandler.onMouseDown(e);
        };

        FluidDiagrams.prototype.onMouseMove = function (e) {
            e.preventDefault();
            this.eventHandler.onMouseMove(e);
        };

        FluidDiagrams.prototype.onMouseWheel = function (e) {
            e.preventDefault();
            this.eventHandler.mouseWheel(e);
        };

        /**
        * Casts a ray in positiv z direction from the camera to find objects at pagex/pagey coordinates (Converted to local space)
        * If hit object is a FDNode the node is returned else the mesh that was hit or null is returned
        * @memberof FluidDiagrams
        * @function objectFromMouse
        * @param {int} pagex x coordinates in page space
        * @param {int} pagey y coordinates in page space
        * @return {FDNode | THREE.Mesh}
        */
        FluidDiagrams.prototype.objectFromMouse = function (pagex, pagey) {
            //translate page coordinates to element coordinates
            var offsetLeft = this.domElement.getBoundingClientRect().left;
            var offsetTop = this.domElement.getBoundingClientRect().top;
            var eltx = pagex - offsetLeft;
            var elty = pagey - offsetTop;

            //debugPrinter.print("offset x/y:"+offsetLeft+"/"+offsetTop+ "  pageXY: "+pagex+"/"+pagey, true);
            var vpx = ((eltx / this.domElement.width) * 2 - 1);
            var vpy = (-(elty / this.domElement.height) * 2 + 1);

            if (this.camera instanceof THREE.PerspectiveCamera || this.camera.inPerspectiveMode) {
                var vector = new THREE.Vector3(vpx, vpy, 0.5);
                var projector = new THREE.Projector();
                projector.unprojectVector(vector, this.camera);
                var direction = vector.sub(this.camera.position).normalize();
                var ray = new THREE.Raycaster(this.camera.position, direction);
                var intersects = ray.intersectObjects(this.scene.children, true);

                if (intersects.length > 0) {
                    var i = 0;
                    while (!intersects[i].object.visible) {
                        i++;
                    }
                    var intersected = intersects[i].object;

                    var clickedNode = this.NodeMeshMap[intersected.geometry.id.toString()];

                    if (clickedNode != null) {
                        return clickedNode;
                    }

                    //sceneObject was clicked
                    return intersected;
                }
            } else if (this.camera instanceof THREE.OrthographicCamera) {
                var projector = new THREE.Projector();
                var ray = projector.pickingRay(new THREE.Vector3(vpx, vpy, 0.5), this.camera);

                var intersects = ray.intersectObjects(this.scene.children, true);

                if (intersects.length > 0) {
                    var visibleElement = 0;
                    for (var i = 0; i < intersects.length; i++) {
                        if (intersects[i].object.visible) {
                            visibleElement = i;
                        }
                    }
                    var intersected = intersects[visibleElement].object;
                    var clickedNode = this.NodeMeshMap[intersected.geometry.id.toString()];

                    if (clickedNode != null) {
                        return clickedNode;
                    }

                    //sceneObject was clicked
                    return intersected;
                }
            } else if (this.camera.inOrthographicMode) {
                var projector = new THREE.Projector();
                var ray = projector.pickingRay(new THREE.Vector3(vpx, vpy, 0.5), this.camera);

                var intersects = ray.intersectObjects(this.scene.children, true);

                if (intersects.length > 0) {
                    var visibleElement = 0;
                    var i = 0;
                    while (!intersects[i].object.visible) {
                        i++;
                    }

                    var intersected = intersects[i].object;
                    var clickedNode = this.NodeMeshMap[intersected.geometry.id.toString()];

                    if (clickedNode != null) {
                        return clickedNode;
                    }

                    //sceneObject was clicked
                    return intersected;
                }
            }

            return null;
        };

        /**
        * Add a non Node related 3D object to the scene
        * @memberof FluidDiagrams
        * @function addSceneObject
        * @param {THREE.Mesh} geomObject
        */
        FluidDiagrams.prototype.addSceneObject = function (geomObject) {
            this.geometryObjects.push(geomObject);
            this.nodeSceneGraph.add(geomObject);
        };

        /**
        * Returns the node defined by it's unique id
        * @memberof FluidDiagrams
        * @function getNodeFromId
        * @param {int} id
        */
        FluidDiagrams.prototype.getNodeFromId = function (id) {
            for (var node in this.nodes) {
                if (this.nodes[node].getUniqueId() == id)
                    return this.nodes[node];
            }
        };
        FluidDiagrams.prototype.recalculateSize = function () {
            if (this.scale == true) {
                this.width = this.container.width();
                if (this.keepAspectRatio == true) {
                    this.height = this.container.width() * this.aspectRatio;
                } else {
                    this.height = this.container.height();
                }
                this.container.css("height", this.height);
                this.renderer.setSize(this.width, this.height);
            }
        };

        FluidDiagrams.prototype.onWindowResize = function (e) {
            if (this.scale == true) {
                this.width = this.container.width();
                if (this.keepAspectRatio == true) {
                    this.height = this.container.width() * this.aspectRatio;
                } else {
                    this.height = this.container.height();
                }
                this.container.css("height", this.height);
                this.renderer.setSize(this.width, this.height);
            }
        };
        return FluidDiagrams;
    })();
    FD.FluidDiagrams = FluidDiagrams;
})(FD || (FD = {}));
