/**
 * Created by gaimeng on 15/3/9.
 */

IndoorMap3d = function(mapdiv){
    var _this = this;
    var _theme = null;
    var _mapDiv = mapdiv,
        _canvasWidth = _mapDiv.clientWidth,
        _canvasWidthHalf = _canvasWidth / 2,
        _canvasHeight = _mapDiv.clientHeight,
        _canvasHeightHalf = _canvasHeight / 2;

    var _scene, _controls, _projector, _rayCaster;
    var  _canvasDiv;
    var _selected;
    var _showNames = true, _showPubPoints = true;
    var _curFloorId = 0;
    var _selectionListener = null;
    var _sceneOrtho, _cameraOrtho;//for 2d
    var _spriteMaterials = [], _pubPointSprites=null, _nameSprites = null;

    this.camera = null;
    this.renderer = null;
    this.mall = null;
    this.is3d = true;

    this.init = function(){

        // perspective scene for normal 3d rendering
        _scene = new THREE.Scene();
        _this.camera = new THREE.PerspectiveCamera(20, _canvasWidth / _canvasHeight, 0.1, 2000);

        //orthogonal scene for sprites 2d rendering
        _sceneOrtho = new THREE.Scene();
        _cameraOrtho = new THREE.OrthographicCamera(- _canvasWidthHalf, _canvasWidthHalf, _canvasHeightHalf, -_canvasHeightHalf, 1, 10);
        _cameraOrtho.position.z = 10;

        //controls
        _controls = new THREE.OrbitControls(_this.camera);

        //renderer
        _this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        _this.renderer.autoClear = false;
        _this.renderer.setClearAlpha(0);

        //set up the lights
        var light = new THREE.DirectionalLight(0xffffff);
        light.position.set(-500, 500, -500);
        _scene.add(light);

        var light = new THREE.DirectionalLight(0xffffff);
        light.position.set(500, 500, 500);
        _scene.add(light);

        //canvas div
        _this.renderer.setSize(_mapDiv.clientWidth, _mapDiv.clientHeight);
        _canvasDiv = _this.renderer.domElement
        _mapDiv.appendChild(_canvasDiv);

        _mapDiv.style.overflow = "hidden";
        _canvasDiv.style.width = "100%";
        _canvasDiv.style.height = "100%";
    }

    this.setTheme = function(theme){
        if(_theme == null){
            _theme = theme
        } else if(_theme != theme) {
            _theme = theme;
            _this.parse(_this.mall.jsonData); //parse
        }
        return _this;
    }

    this.theme = function(){
        return _theme;
    }

    //load the map by the json file name
    this.load = function (fileName, callback) {
        var loader = new IndoorMapLoader(true);
        _theme = default3dTheme;
        loader.load(fileName, function(mall){
            _this.mall = mall;
            _scene.add(_this.mall.root);
            _scene.mall = mall;
            if(callback) {
                callback();
            }
            // FixMe: 2020年7月7日00:41:14 透明色配置
            // _this.renderer.setClearColor(_theme.background);
            if(_curFloorId == 0){
                _this.showAllFloors();
            }else{
                _this.showFloor(_curFloorId);
            }

        });
        return _this;
    }

    //parse the json file
    this.parse = function(json){
        if(_theme == null) {
            _theme = default3dTheme;
        }
        _this.mall = ParseModel(json, _this.is3d, _theme);
        _scene.mall = _this.mall;
        _this.showFloor(_this.mall.getDefaultFloorId());
        _this.renderer.setClearColor(_theme.background);
        _scene.add(_this.mall.root);
        _mapDiv.style.background = _theme.background;
        return _this;
    }

    //reset the camera to default configuration
    this.setDefaultView = function () {

        // var camAngle = _this.mall.FrontAngle + Math.PI/2;
        var camAngle = 0;
        var camDir = [Math.cos(camAngle), Math.sin(camAngle)];
        var camLen = 230;
        var tiltAngle = 75.0 * Math.PI/180.0;
        _this.camera.position.set(camDir[1]*camLen, Math.sin(tiltAngle) * camLen, camDir[0]*camLen);//TODO: adjust the position automatically
        _this.camera.lookAt(_scene.position);

        _controls.reset();
        _controls.viewChanged = true;
        return _this;
    }

    //set top view
    this.setTopView = function(){
        _this.camera.position.set(0, 500, 0);
        return _this;
    }

    //TODO:adjust camera to fit the building
    this.adjustCamera = function() {
        _this.setDefaultView();

    }

    this.zoomIn = function(zoomScale){
        _controls.zoomOut(zoomScale);
        redraw();
    }

    this.zoomOut = function(zoomScale){
        _controls.zoomIn(zoomScale);
        redraw();
    }

    //show floor by id
    this.showFloor = function(floorid) {
        _curFloorId = floorid;
        if(_scene.mall == null){
            return;
        }
        _scene.mall.showFloor(floorid);
        _this.adjustCamera();
        if(_showPubPoints) {
            createPubPointSprites(floorid);
        }
        if(_showNames) {
            createNameSprites(floorid);
        }
        redraw();
        return _this;
    }

    //show all floors
    this.showAllFloors = function(){
        _curFloorId = 0; //0 for showing all
        if(_this.mall == null){
            return;
        }
        _this.mall.showAllFloors();
        _this.adjustCamera();
        clearPubPointSprites();
        clearNameSprites();
        return _this;
    }

    //set if the objects are selectable
    this.setSelectable = function (selectable) {
        if(selectable){
            _projector = new THREE.Projector();
            _rayCaster = new THREE.Raycaster();
            _mapDiv.addEventListener('mousedown', onSelectObject, false);
            _mapDiv.addEventListener('touchstart', onSelectObject, false);
        }else{
            _mapDiv.removeEventListener('mousedown', onSelectObject, false);
            _mapDiv.removeEventListener('touchstart', onSelectObject, false);
        }
        return _this;
    }

    //set if the user can pan the camera
    this.setMovable = function(movable){
        _controls.enable = movable;
        return _this;
    }

    //show the labels
    this.showAreaNames = function(show) {
        _showNames = show == undefined ? true : show;
        return _this;
    }

    //show pubPoints(entries, ATM, escalator...)
    this.showPubPoints = function(show){
        _showPubPoints = show == undefined ? true: show;
        return _this;
    }

    //get the selected object
    this.getSelectedId = function(){
        return _selected.id;
    }

    //the callback function when sth is selected
    this.setSelectionListener = function(callback){
        _selectionListener = callback;
        return _this;
    }

    //select object by id
    this.selectById = function(id){
        var floor = _this.mall.getCurFloor();
        for(var i = 0; i < floor.children.length; i++){
            if(floor.children[i].id && floor.children[i].id == id) {
                if (_selected) {
                    _selected.material.color.setHex(_selected.currentHex);
                }
                select(floor.children[i]);
            }
        }
    }

    //select object(just hight light it)
    function select(obj){
        obj.currentHex = _selected.material.color.getHex();
        obj.material.color = new THREE.Color(_theme.selected);
        obj.scale = new THREE.Vector3(2,2,2);
    }

    function onSelectObject(event) {

        // find intersections
        event.preventDefault();
        var mouse = new THREE.Vector2();
        if(event.type == "touchstart"){
            mouse.x = ( event.touches[0].clientX / _canvasDiv.clientWidth ) * 2 - 1;
            mouse.y = -( event.touches[0].clientY / _canvasDiv.clientHeight ) * 2 + 1;
        }else {
            mouse.x = ( event.clientX / _canvasDiv.clientWidth ) * 2 - 1;
            mouse.y = -( event.clientY / _canvasDiv.clientHeight ) * 2 + 1;
        }
        var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
        vector.unproject( _this.camera);

        _rayCaster.set( _this.camera.position, vector.sub( _this.camera.position ).normalize() );

        var intersects = _rayCaster.intersectObjects( _this.mall.root.children[0].children );

        if ( intersects.length > 0 ) {

            if ( _selected != intersects[ 0 ].object ) {

                if ( _selected ) {
                    _selected.material.color.setHex( _selected.currentHex );
                }
                for(var i=0; i<intersects.length; i++) {
                    _selected = intersects[ i ].object;
                    if(_selected.type && _selected.type == "solidroom") {
                        select(_selected);
                        if(_selectionListener) {
                            _selectionListener(_selected.id); //notify the listener
                        }
                        break;
                    }else{
                        _selected = null;
                    }
                    if(_selected == null && _selectionListener != null){
                        _selectionListener(-1);
                    }
                }
            }

        } else {

            if ( _selected ) {
                _selected.material.color.setHex( _selected.currentHex );
            }

            _selected = null;
            if(_selectionListener) {
                _selectionListener(-1); //notify the listener
            }
        }
        redraw();

    }

    function redraw(){
        _controls.viewChanged = true;
    }

    function animate () {
        requestAnimationFrame(animate);
        _controls.update();
        if(_controls.viewChanged) {

            _this.renderer.clear();
            _this.renderer.render(_scene, _this.camera);

            if (_showNames || _showPubPoints) {
                updateLabels();
            }
            _this.renderer.clearDepth();
            _this.renderer.render(_sceneOrtho, _cameraOrtho);

        }

        _controls.viewChanged = false;
    }

    //load Sprites
    function loadSprites(){
        if(_this.mall != null && _spriteMaterials.length == 0){
            var images = _theme.pubPointImg;
            for(var key in images){
                var texture = THREE.ImageUtils.loadTexture(images[key], undefined, redraw);
                var material = new THREE.SpriteMaterial({map:texture});
                _spriteMaterials[key] = material;
            }
        }
        _spriteMaterials.isLoaded = true;
    }

    //labels includes pubPoints and shop names
    function updateLabels() {
        var mall = _this.mall;
        if(mall == null || _controls == null || !_controls.viewChanged){
            return;
        }
        var curFloor = mall.getCurFloor();
        if(curFloor == null){
            return;
        }

        var projectMatrix = null;

        if(_showNames) {
            if(_nameSprites != undefined){
                projectMatrix = new THREE.Matrix4();
                projectMatrix.multiplyMatrices(_this.camera.projectionMatrix, _this.camera.matrixWorldInverse);

                updateSprites(_nameSprites, projectMatrix);
            }

        }

        if(_showPubPoints){
            if(_pubPointSprites != undefined){
                if(!projectMatrix){
                    projectMatrix = new THREE.Matrix4();
                    projectMatrix.multiplyMatrices(_this.camera.projectionMatrix, _this.camera.matrixWorldInverse);
                }
                updateSprites(_pubPointSprites, projectMatrix);
            }
        }
        _controls.viewChanged = false;
    };

    //update sprites
    function updateSprites(spritelist, projectMatrix){
        for(var i = 0 ; i < spritelist.children.length; i++){
            var sprite = spritelist.children[i];
            var vec = new THREE.Vector3(sprite.oriX * 0.1, 0, -sprite.oriY * 0.1);
            vec.applyProjection(projectMatrix);

            var x = Math.round(vec.x * _canvasWidthHalf);
            var y = Math.round(vec.y * _canvasHeightHalf);
            sprite.position.set(x, y, 1);

            //check collision with the former sprites
            var visible = true;
            var visibleMargin = 5;
            for(var j = 0; j < i; j++){
                var img = sprite.material.map.image;
                if(!img){ //if img is undefined (the img has not loaded)
                    visible = false;
                    break;
                }

                var imgWidthHalf1 = sprite.width / 2;
                var imgHeightHalf1 = sprite.height / 2;
                var rect1 = new Rect(sprite.position.x - imgWidthHalf1, sprite.position.y - imgHeightHalf1,
                        sprite.position.x + imgHeightHalf1, sprite.position.y + imgHeightHalf1 );

                var sprite2 = spritelist.children[j];
                var sprite2Pos = sprite2.position;
                var imgWidthHalf2 = sprite2.width / 2;
                var imgHeightHalf2 = sprite2.height / 2;
                var rect2 = new Rect(sprite2Pos.x - imgWidthHalf2, sprite2Pos.y - imgHeightHalf2,
                        sprite2Pos.x + imgHeightHalf2, sprite2Pos.y + imgHeightHalf2 );

                if(sprite2.visible && rect1.isCollide(rect2)){
                    visible = false;
                    break;
                }

                rect1.tl[0] -= visibleMargin;
                rect1.tl[1] -= visibleMargin;
                rect2.tl[0] -= visibleMargin;
                rect2.tl[1] -= visibleMargin;


                if(sprite.visible == false && rect1.isCollide(rect2)){
                    visible = false;
                    break;
                }
            }
            sprite.visible = visible;
        }
    }

    //creat the funcArea Name sprites of a floor
    function createNameSprites(floorId){
        if(!_nameSprites){
            _nameSprites = new THREE.Object3D();
        }else{
            clearNameSprites();
        }
        var funcAreaJson = _this.mall.getFloorJson(_this.mall.getCurFloorId()).FuncAreas;
        for(var i = 0 ; i < funcAreaJson.length; i++){
            var sprite = makeTextSprite(funcAreaJson[i].Name_en, _theme.fontStyle);
            sprite.oriX = funcAreaJson[i].Center[0];
            sprite.oriY = funcAreaJson[i].Center[1];
            _nameSprites.add(sprite);
        }
        _sceneOrtho.add(_nameSprites);
    }

    //create the pubpoint sprites in a floor by the floor id
    function createPubPointSprites(floorId){
        if(!_spriteMaterials.isLoaded){
            loadSprites();
        }

        if(!_pubPointSprites) {

            _pubPointSprites = new THREE.Object3D();
        }else{
            clearPubPointSprites();
        }

        var pubPointsJson = _this.mall.getFloorJson(_this.mall.getCurFloorId()).PubPoint;
        var imgWidth, imgHeight;
        for(var i = 0; i < pubPointsJson.length; i++){
            var spriteMat = _spriteMaterials[pubPointsJson[i].Type];
            if(spriteMat !== undefined) {
                imgWidth = 30, imgHeight = 30;
                var sprite = new THREE.Sprite(spriteMat);
                sprite.scale.set(imgWidth, imgHeight, 1);
                sprite.oriX = pubPointsJson[i].Outline[0][0][0];
                sprite.oriY = pubPointsJson[i].Outline[0][0][1];
                sprite.width = imgWidth;
                sprite.height = imgHeight;
                _pubPointSprites.add(sprite);
            }
        }
        _sceneOrtho.add(_pubPointSprites);
    }

    function clearNameSprites(){
        if(_nameSprites == null){
            return;
        }
        _nameSprites.remove(_nameSprites.children);
        _nameSprites.children.length = 0;
    }
    function clearPubPointSprites(){
        if(_pubPointSprites == null){
            return;
        }
        _pubPointSprites.remove(_pubPointSprites.children);
        _pubPointSprites.children.length = 0;
    }

    function makeTextSprite( message, parameters )
    {
        if ( parameters === undefined ) parameters = {};

        var fontface = parameters.hasOwnProperty("fontface") ?
            parameters["fontface"] : "Arial";

        var fontsize = parameters.hasOwnProperty("fontsize") ?
            parameters["fontsize"] : 18;

        var borderThickness = parameters.hasOwnProperty("borderThickness") ?
            parameters["borderThickness"] : 2;

        var borderColor = parameters.hasOwnProperty("borderColor") ?
            parameters["borderColor"] : { r:0, g:0, b:0, a:1.0 };

        var backgroundColor = parameters.hasOwnProperty("backgroundColor") ?
            parameters["backgroundColor"] : { r:255, g:255, b:255, a:1.0 };

        var fontColor = parameters.hasOwnProperty("color")?
            parameters["color"] : "#000000";

        //var spriteAlignment = parameters.hasOwnProperty("alignment") ?
        //	parameters["alignment"] : THREE.SpriteAlignment.topLeft;

        var spriteAlignment = new THREE.Vector2( 0, 0 );


        var canvas = document.createElement('canvas');
        var context = canvas.getContext('2d');
        context.font = "Bold " + fontsize + "px " + fontface;

        // get size data (height depends only on font size)
        var metrics = context.measureText( message );
//
//        // background color
//        context.fillStyle   = "rgba(" + backgroundColor.r + "," + backgroundColor.g + ","
//            + backgroundColor.b + "," + backgroundColor.a + ")";
//        // border color
        context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + ","
            + borderColor.b + "," + borderColor.a + ")";
//
//        context.lineWidth = borderThickness;
//        context.strokeRect(borderThickness/2, borderThickness/2, metrics.width + borderThickness, fontsize * 1.4 + borderThickness);

        // text color
        context.fillStyle = fontColor;

        context.fillText( message, borderThickness, fontsize + borderThickness);

        // canvas contents will be used for a texture
        var texture = new THREE.Texture(canvas)
        texture.needsUpdate = true;


        var spriteMaterial = new THREE.SpriteMaterial(
            { map: texture, useScreenCoordinates: false } );
        var sprite = new THREE.Sprite( spriteMaterial );
        sprite.scale.set(100,50,1.0);
        sprite.width = metrics.width;
        sprite.height = fontsize * 1.4;
        return sprite;
    }

    //resize the map
    this.resize = function (width, height){
        _this.camera.aspect = width / height;
        _this.camera.updateProjectionMatrix();

        _this.renderer.setSize( width, height );
        _controls.viewChanged = true;
    }

    _this.init();
    animate();

}