//
// mapInterface.js
//
// Master file for AJAX map interface
//
//


function loadScript(url)
{
    /*
   var e = document.createElement("script");
   e.src = url;
   e.type="text/javascript";
   document.getElementsByTagName("head")[0].appendChild(e);
   */
   document.write("<" + "script src=\"" + url + "\"></" + "script>"); 
}

loadScript("javascript/util.js");
loadScript("javascript/dynamicBox.js");
loadScript("javascript/miMapBorder.js");
loadScript("javascript/miToolSelector.js");
loadScript("javascript/miToolManager.js");
loadScript("javascript/miLegend.js");
loadScript("javascript/miLayerList.js");
loadScript("javascript/miLoadIndicator.js");

function MapInterface(mapDivId, mapURL, config)
{
    if(arguments.length > 3)
    {
        alert('use config method to create map interface');
        return;
    }
    
    /******** default config *********/
    var defaultConfig = {
        cursorStyle: "crosshair",
        mapBorder: true,
        mapBorderWidth: 9,
        layerChangeDelay: 2000,
        defaultLayers: "",
        minDragAmount: 2
    };
    /*********************************/

    this.config = defaultConfig;

    this.setConfig = function(config)
    {
        this.config = util.mixin(this.config, config);
    }

    this.setConfig(config);

    /* set up components */
    this.components = new Array();
    if(this.config.mapBorder)
        this.components.push(new MapBorder(this));
    this.components.push(new ToolManager(this));
    this.components.push(new LoadIndicator(this));
    if(this.config.toolSelectId)
        this.components.push(new ToolSelector(this.config.toolSelectId, this));
    if(this.config.legendListId)
        this.components.push(new Legend(this.config.legendListId, this));
    if(this.config.layerListId)
        this.components.push(new LayerList(this.config.layerListId, this));

    /* map service/interface information */
    this.msInfo = null;
    this.state = null;
    
    /* mouse event information */
    this.mouseDownX = null;
    this.mouseDownY = null;
    this.mouseX = null;
    this.mouseY = null;
    this.mouseIsDown = null;
    
    /* DOM reference to the div containing the map */
    this.mapDiv = util.byId(mapDivId);

    this.mapURL = mapURL;
    this.clientLayerDiv = this.mapDiv;
    
    /* map image dimensions */
    this.width = this.clientLayerDiv.clientWidth;
    this.height = this.clientLayerDiv.clientHeight;
    this.mapPosX = findPos(this.clientLayerDiv)[0];
    this.mapPosY = findPos(this.clientLayerDiv)[1];
    
    /* the scale of the current map (map units per pixel) */
    this.imgScaleX = null;
    this.imgScaleY = null;
    
    /* map extent */
    this.minX = null;
    this.minY = null;
    this.maxX = null;
    this.maxY = null;
    
    /* array of client layer divs */
    this.layerImages = new Object();
    
    this.extraParameters = new Object();
    
    this.callComponents = function(fname, args)
    {
        util.callComponents(this.components, fname, args);
    }

    this.callComponentsChained = function(fname, arg)
    {
        return util.callComponentsChained(this.components, fname, arg);
    }

    this.callFirstComponent = function(fname, args)
    {
        return util.callFirstComponent(this.components, fname, args);
    }
    
    this.getMapX = function(pX)
    {
        var mapX = Math.round((this.minX + ((pX) * this.imgScaleX)) * 100000);
        mapX = mapX/100000;
        return mapX
    }
    this.getMapY = function(pY)
    {
        var mapY = Math.round((this.maxY - ((pY) * this.imgScaleY)) * 100000);
        mapY = mapY/100000;
        return mapY
    }

    /* updates mouseX and mouseY */
    this.setMouseXY = function(evt)
    {
        if (BrowserDetect.browser == "Explorer")
        {
            this.mouseX = evt.clientX - this.mapPosX - 3;
            this.mouseY = evt.clientY - this.mapPosY - 3;
        }
        else
        {
            this.mouseX = evt.pageX - this.mapPosX - 1;
            this.mouseY = evt.pageY - this.mapPosY - 1;
        }
    }

    /* updates the status bar with the mouse and map coordinates */
    this.updateStatusBar = function()
    {
        window.status = dec2dms(this.getMapX(this.mouseX)) + ", " + dec2dms(this.getMapY(this.mouseY)) + " [ " + this.scale + " ]";
        // + " Mouse: " + this.mouseX + ", " + this.mouseY;
    }
    
    this.updateInterfaceInfo = function()
    {
        var pos = findPos(this.clientLayerDiv)
        this.mapPosX = pos[0];
        this.mapPosY = pos[1];
        this.width = this.clientLayerDiv.clientWidth;
        this.height = this.clientLayerDiv.clientHeight;
        this.imgScaleX = (this.maxX - this.minX) / this.width;
        this.imgScaleY = (this.maxY - this.minY) / this.height;
    }
    
    this.processResizeWindow = function(evt)
    {
        var oldwidth = this.width;
        var oldheight = this.height;

        this.updateInterfaceInfo();
        
        if(oldwidth != this.width || oldheight != this.height)
            this.refreshMap();
    }

    /* called whenever there is a mouseDown on the map */
    this.processMouseDown = function(evt)
    {
        /* for dojo: allows dragging by making event handler return false */
        evt.preventDefault();
        
        /* make sure map div position & dimensions are up to date */
        this.updateInterfaceInfo();
        
        /* set mouse variables */
        if(this.mouseIsDown)
        {
            this.mouseIsDown = false;
        }
        else
        {
            this.mouseIsDown = true;
            this.setMouseXY(evt);
            this.mouseDownX = this.mouseX;
            this.mouseDownY = this.mouseY;
        }
        
        /* for an aborted mouse drag */
        if(this.drag)
        {
            this.drag = false;
            this.callComponents("processDragCancel", []);
        }

        this.callComponents("processMouseDown", [evt]);

        return false
    }
        
    /* called whenever the mouse pointer moves across the map */
    this.processMouseMove = function(evt)
    {
        /* for dojo: allows dragging by making event handler return false */
        evt.preventDefault();
        this.setMouseXY(evt);
        this.updateStatusBar();
        
        this.callComponents("processMouseMove", [evt]);
        
        /* detect start of mouse drag */
        if(!this.drag && this.mouseIsDown) {
            var dragWidth = Math.abs(this.mouseX - this.mouseDownX);
            var dragHeight = Math.abs(this.mouseY - this.mouseDownY);
            if((dragWidth > this.config.minDragAmount) ||
               (dragHeight > this.config.minDragAmount)) {
                this.drag = true;
            }
        }
        
        if(this.drag)
        {
            this.callComponents("processDrag",
                                [this.mouseX, this.mouseY,
                                 this.mouseDownX, this.mouseDownY]);
        }

        return false
    }
    
    /* called whenever there is a mouseUp on the map */
    this.processMouseUp = function(evt)
    {
        this.callComponents("processMouseUp", [evt]);
        
        /* ignore any mouse up, if the mouse was not clicked down on the map */
        if(!this.mouseIsDown) return;
        this.mouseIsDown = false;
        
        /* end of a mouse drag */
        if(this.drag)
        {
            this.callComponents("processDragEnd",
                                [this.mouseX, this.mouseY,
                                 this.mouseDownX, this.mouseDownY]);
            this.drag = false;
        }
        /* a mouse click */
        else
        {
            this.callComponents("processClick", [this.mouseDownX, this.mouseDownY]);
        }
    }
    
    this.moveMap = function(x, y)
    {
        if(!this.clientLayerDivTop)
            this.clientLayerDivTop = parseInt(this.clientLayerDiv.style.top);
        if(!this.clientLayerDivLeft)
            this.clientLayerDivLeft = parseInt(this.clientLayerDiv.style.left);

        this.clientLayerDiv.style.top = (this.clientLayerDivTop - y) + "px";
        this.clientLayerDiv.style.left = (this.clientLayerDivLeft - x) + "px";
        this.clientLayerDiv.style.clip = "rect(" + y + "px, "
                                                 + (x + this.width) + "px, "                             
                                                 + (y + this.height) + "px, "
                                                 + x + "px)";
    }

    this.resetMapPos = function()
    {
        this.moveMap(0,0);
    }

    this.refreshMap = function()
    {
        this.sendRequest({
            mode:   "layer"
        });
    }
    
    this.getSelectedTool = function()
    {
        return this.callFirstComponent("getSelectedTool", []);
    }
    
    this.processResponse = function(data, ioArgs)
    {
        if(data.errors) {
            for(var i=0; i<data.errors.length; i++)
                alert(data.errors[i]);
            return;
        }

        this.state = data.state;

        this.callComponents("processResponse", [data]);
        
        if(data.msInfo)
        {
            this.msInfo = data.msInfo;

            /* CLIENT LAYERS (i.e. map image layers) */
            for(var i=0; i < data.msInfo.clientLayers.length; i++)
            {
                var layer = data.msInfo.clientLayers[i];
                
                var layerDiv = document.createElement("div");
                layerDiv.style.position = "absolute";
                layerDiv.style.left = "0px";
                layerDiv.style.top = "0px";
                layerDiv.style.width = "100%";
                layerDiv.style.height = "100%";
                layerDiv.style.cursor = this.config.cursorStyle;
                
                var layerImg = document.createElement("img");
                layerImg.src = "images/transp_pixel.gif";
                layerImg.style.border = "0px";
                layerImg.style.width = this.width + "px";
                layerImg.style.height = this.height + "px";
                layerImg.style.cursor = this.config.cursorStyle;
                
                layerDiv.appendChild(layerImg);
                this.clientLayerDiv.appendChild(layerDiv);
                
                this.layerImages[layer] = layerImg;
            }
            
            
            /* REGISTER EVENT HANDLERS */
            dojo.connect(this.clientLayerDiv, "onmousemove", this, this.processMouseMove);
            dojo.connect(this.clientLayerDiv, "onmousedown", this, this.processMouseDown);
            dojo.connect(this.clientLayerDiv, "onmouseup", this, this.processMouseUp);
            dojo.connect(window, "onresize", this, this.processResizeWindow);
        }

        /********* END OF HANDLING INIT RESPONSE **************/
        
        /* MAP IMAGES */
        for(var i=0; i < this.msInfo.clientLayers.length; i++)
        {
            var layer = this.msInfo.clientLayers[i];
            var image = data.msResponse.clientLayers[layer];
            if(image)
            {
                if(image=="blank")
                    this.layerImages[layer].src = "images/transp_pixel.gif";
                else
                    this.layerImages[layer].src = image;
            }
        }

        /* MAP EXTENT INFO */
        this.maxX = data.msResponse.extent.maxX;
        this.maxY = data.msResponse.extent.maxY;
        this.minX = data.msResponse.extent.minX;
        this.minY = data.msResponse.extent.minY;
        this.imgScaleX = (this.maxX - this.minX) / this.width;
        this.imgScaleY = (this.maxY - this.minY) / this.height;
        this.scale = Math.round(this.imgScaleX*314073680);
    }
    
    this.sendRequest = function(request)
    {
        request = this.callComponentsChained("processSendRequest", request);

        if(this.state)
            request.state = this.state;

        /* these parameters sent with every request */
        request.width = this.width;
        request.height = this.height;
        request.minx = this.minX;
        request.miny = this.minY;
        request.maxx = this.maxX;
        request.maxy = this.maxY;

        var xhrArgs = {
            url:        this.mapURL,
            handleAs:   "javascript",
            content:    util.mixin(request,this.extraParameters),
            error:      function(response, ioArgs){
                alert(response + ", " + ioArgs);
            },
            load:       util.hitch(this, this.processResponse)
        };
        dojo.xhrPost(xhrArgs);
    }

    this.addComponent = function(comp)
    {
        if(comp)
        {
            if(!comp.name) comp.name = "anonymous-component";
            this.components.push(comp);
        }
    }
    
    this.init = function()
    {
        this.callComponents("init", []);

        var request = {
            mode:   "init"
        };

        if(this.config.initJumpToX && this.config.initJumpToY)
        {
            request.jump_to_x = this.config.initJumpToX;
            request.jump_to_y = this.config.initJumpToY;
            if(this.config.initJumpToScale)
            {
                request.jump_to_scale = this.config.initJumpToScale;
            }
        }
        else if(this.config.initJumpToMinX &&
                this.config.initJumpToMinY &&
                this.config.initJumpToMaxX &&
                this.config.initJumpToMaxY)
        {
            request.jump_to_minx = this.config.initJumpToMinX;
            request.jump_to_miny = this.config.initJumpToMinY;
            request.jump_to_maxx = this.config.initJumpToMaxX;
            request.jump_to_maxy = this.config.initJumpToMaxY;
        }

        this.sendRequest(request);
    }

    /*
     * Takes a variable number of arguments:
     *
     * 2 arguments(x,y): new centre coordinate with default scale
     * 3 arguments(x,y,scale): new centre coordinate with specified scale
     * 4 arguments(x,y,x2,y2): new bounding box
     */
    this.sendJumpToRequest = function(x, y, arg3, arg4)
    {
        var request = {
            mode: "layer",
            jump_to_x: x,
            jump_to_y: y
        };
        if(arg3 && !arg4)
        {
            request.jump_to_scale = arg3;
        }
        else if(arg3 && arg4)
        {
            request.jump_to_x2 = arg3;
            request.jump_to_y2 = arg4;
        }
        this.sendRequest(request);
    }

    this.setExtraParameter = function(key, value)
    {
        this.extraParameters['extra_' + key] = value;
    }

    this.clearExtraParameter = function(key)
    {
        delete this.extraParameters['extra_' + key];
    }
}

/* pads a number with preceding zeros so it is at least len characters long */
function padNum(num, len)
{
    var str = num.toString();
    var neg = false;

    if(str.charAt(0) == "-")
    {
        str = str.substring(1);
        neg = true;
    }

    while(str.length < len) str = "0" + str;

    if(neg == true) str = "-" + str;

    return str;
}

/* converts decimal degrees to degrees, minutes, seconds (as a string) */
function dec2dmsarray(val, allneg)
{
    var neg = false;
    if(val < 0)
    {
        neg = true;
        val = -val;
    }
    
    var deg = Math.floor(val);
    var min = Math.floor(val*60) % 60;
    var sec = Math.round(val*3600) % 60;
    if (sec==0) min = Math.round(val*60) % 60;
    if ((sec==0)&&(min==0)) deg = Math.round(val);

    if(neg)
    {
        if(allneg)
            return [ -deg, -min, -sec ];
        else
            return [ -deg, min, sec ];
    }

    return [ deg, min, sec ];
}

/* converts decimal degrees to degrees, minutes, seconds (as a string) */
function dec2dms(val)
{
    dms = dec2dmsarray(val);
    var deg = dms[0];
    var min = padNum(dms[1],2);
    var sec = padNum(dms[2],2);

    var nt5 = navigator.userAgent.indexOf("Windows NT 5");
    var ie = navigator.userAgent.indexOf("MSIE");
    
    if(nt5 != -1 && ie != -1)
    {
        var str = deg + "d " + min + "' " + sec + "\"";
    }else{
        var str = deg + "� " + min + "' " + sec + "\"";
    }

    return str;
}

/* http://www.quirksmode.org/js/findpos.html */
function findPos(obj) {
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		curleft = obj.offsetLeft
		curtop = obj.offsetTop
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft - obj.scrollLeft
			curtop += obj.offsetTop - obj.scrollTop
		}
	}
	return [curleft,curtop];
}
