/* TODO Make a view constructor/class? */
/*jslint browser:true, devel:true */
/*global window */
/*global jQuery */

/*global Shadowbox */

/*global ut */

/* FIXME Should be namespaced */
/*global base_path */
/*global ut_map_loaded_handler */

/* TODO Namespace these under ut.map.<name> */
/*global UtMapCookiePosition */
/*global UtMapImagemaps */
/*global UtMapIconRepo */
/*global UtWmsTokenClient */
/*global UtMapKartverket */
/*global UtMapAutoLayer */
/*global UtMapBlockRectangle */
/*global UtDntTileLayer */
/*global UtWmsLayer */

/*global MarkerManager */
/*global DragZoomControl */

/* Google Maps stuff, maybe some day refactored out to a new file */
/*global google */
/*global GLatLngBounds */
/*global GLatLng */
/*global G_NORMAL_MAP */
/*global G_PHYSICAL_MAP */
/*global G_SATELLITE_3D_MAP */
/*global G_HYBRID_MAP */
/*global G_ANCHOR_TOP_LEFT */

UtMapCookiePosition.prototype.saveGMapCenter = function(gmap) {
    var center  = gmap.getCenter();
    var zoom    = gmap.getZoom();
    if (center) {
        this.latitude = center.lat();
        this.longitude = center.lng();
    }
    
    this.zoom = zoom;
    
    //console.log('STORED COOKIE LOC: '+center.lat()+', '+center.lng()+', Z:'+zoom);
    
    this.save();
};


/* MapEndinge class
 * @param object params: The paramters to use while initializing the map engine
 */
function MapUI(params) {
    
    //Privileged this (avoid this refering to function only)
    var that=this;
    
    //Constructor (run at the bottom of this class)
    that.ctor = function (params){
        //Trivial reject
        if (!google.maps.BrowserIsCompatible()) {
            that.alert("Din nettleser er desverre ikke støttet for visning av kart. Vi støtter siste versjon av følgende nettlesere: Opera, Internet Explorer, Firefox, Safari og Chrome. For lenker til disse, gå her: http://www.browserchoice.eu/BrowserChoice/browserchoice_nb.htm");
            return;
        }
        
        //Set up safe unload to prevent memory leaks in IE and other browsers with poor memory management
        jQuery(window).unload( function () { google.maps.Unload(); } );

        that.params=params;
        that.imagemaps = new UtMapImagemaps();
        that.wms_token = null;
        that.poimgrs = new ut.collection.Map();
        that.gmap=undefined;
        that.cookie_base='ut.map.location';
        that.dnttile_overlay=null;
        that.nvetile_overlay=null;
        that.dnttile_manager = null;
        that.nvelayer = null;
        that.copyright=null;
        that.copyrightCollection=null;
        that.markerCache = new ut.collection.Map();
        that.custom_poi_marker=null;
        that.dntarea_overlay = null;
        that.icons = null;
        that.debug_str='';
        that.zoom_default=6;
        that.zoom_focus=13;
        that.zoom_max=14;
        that.main_marker=false;
        that.zoom_min=5;
        that.center_default = new google.maps.LatLng(65.618905, 11.915359);
        that.last_dnt_type='not_a_dnt_type';
        that.params.main_marker=true;
        that.lastPosition = null;
        that.sel = null;
        that.maps = [];
    };
    
    
    //Call at document ready
    that.init=function (){
        that.icons = new UtMapIconRepo();
        that.icons.web_root = that.getWebRoot();
        
        var wms_token_params = that.params.wms_token;
        if (wms_token_params) {
            that.wms_token = new UtWmsTokenClient(wms_token_params);
        }
        
        //This will in turn call all other init functions in its onLoad event handler
        that.initGMap();
        
// Start : Viser main marker
        if (that.params.main_marker){
        	var center_marker=that.gmap.getCenter();

        	var center = new GLatLng(center_marker.y, center_marker.x);
       	    that.gmap.setCenter(center, that.zoom_focus);
       	
            var letterIcon = new GIcon();
            letterIcon.image =that.params.web_root+"gfx/ikoner/kart/stor/sted.png";
            letterIcon.shadow = that.params.web_root+"gfx/ikoner/kart/stor/skygge.png";

            letterIcon.iconSize = new GSize(24, 32);
            letterIcon.shadowSize = new GSize(36, 32);
            letterIcon.iconAnchor = new GPoint(12, 32);
            letterIcon.infoWindowAnchor = new GPoint(5, 1);



        	var marker = new GMarker(center, {icon:letterIcon});
        	
        	that.gmap.addOverlay(marker);

        }
     // End : Viser main marker        		

        		
        		
    };
    

    //Overloadable event handler called once map load is complete
    that.load=function(){
    };
    
    
    that.getLatLng = function(){
        return that.gmap.getCenter();
    };
    
    that.getMaxZoom = function(){
        return that.zoom_max;
    };
    
    that.getMinZoom = function(){
        return that.zoom_min;
    };
    
    
    /**
     * Returns the map tool jQuery object
     */
    that.getMapTool = function() {
        return that.sel;
    };
    
    
    /**
     * Helper function to create a latlng object from an array.
     */
    that.createLatLng_ = function(data) {
        return new google.maps.LatLng(data[0], data[1]);
    };
    
    
    //Called on gmap load complete
    that.initAfterGMapLoad = function() {
        that.gmap.addMapType(G_NORMAL_MAP);
        that.gmap.addMapType(G_PHYSICAL_MAP);

        that.initCustomMapLayers();

        that.initMarkerManagers();

        //Configure ui
        if (undefined === that.params.show_controls) {
            that.params.show_controls = that.params.options.show_controls;
        }
        if (undefined === that.params.use_cookie) {
            that.params.use_cookie = that.params.options.use_cookie;
        }
        
        //Override default zoom level
        if (undefined !== that.params.options.zoom_default) {
            that.zoom_default = that.params.options.zoom_default;
        }
        if (undefined !== that.params.options.center_default) {
            that.center_default = that.createLatLng_(that.params.options.center_default);
        }
        
        // base_path is a global variable
        var pos = new UtMapCookiePosition({
                cookie_base: that.cookie_base,
                options: {
                    path:   base_path+"/"
                }
            });
        that.lastPosition = pos;
        
        that.initLastPosition();

        //Hook up event listeners for moving of the map to make sure the last location is saved
        google.maps.Event.addListener(that.gmap, "zoomend", that.saveLastLocationInCookie);
        google.maps.Event.addListener(that.gmap, "moveend", that.saveLastLocationInCookie);

        //Set min and max zoom level for map
        that.restrictMapZoom(that.zoom_min, that.zoom_max);
        
        
        that.initCenterPosition();
        
        
        //NEEDED FOR IE
        that.updateDNTTileLayers(that.params.options.dnt_tile_layers);
        that.updateNVETileLayers(that.params.options.nve_tile_layers);
        
        if(that.params.options.hack_init_maptool_ui) {
            that.initMaptoolUI();
        }
        
        that.initCrosshairIcon();
        
        that.initUI(that.params.options.show_controls);
        
        // TODO event/observer-pattern
        // TODO Remove this temporary hack as soon as possible (reliance upon global function)
        //Tell the world that we are complete!
        if ('function' === typeof ut_map_loaded_handler) {
            ut_map_loaded_handler(that);
        }
        
        that.load(that);
    };
    
    
    that.initCrosshairIcon = function() {
        //Add crosshair
        that.addCrosshairs(
            that.params.options.crosshair_icon_url,
            that.params.options.crosshair_icon_w,
            that.params.options.crosshair_icon_h,
            that.params.options.crosshair_icon_offset_x,
            that.params.options.crosshair_icon_offset_y
        );
    };
    
    
    that.initLastPosition = function() {
        var pos = this.lastPosition;
        pos.load();
        
        // If the use_cookie param is set, use the cookie location instead
        if (that.params.use_cookie) {
            if (pos.hasPosition()) {
                that.params.mapfocus = null;
            }
        }
        
        if (!that.params.mapfocus && pos.hasPosition()) {
            that.params.mapfocus = {};
            that.params.mapfocus.items = [];
            that.params.mapfocus.items[0]=pos;
        }
    };
    
    
    that.initCenterPosition = function() {
        // When only one point is present, set default zoom level
        var bestZoomLevel = that.zoom_default;
        
        //Default is to center map on vega
        var center = that.center_default;
        
        var mapfocus = that.params.mapfocus;

        //that.debug('mapfocus time 2' + mapfocus +', LOL: '+ mapfocus.items.length);
        //Use mapfocus parameter as a basis for the initial sentering of the map
        if (mapfocus && mapfocus.items.length > 0) {
            var items = mapfocus.items;
            
            var item = null;
            
            //Single point with zoom
            if (1 === items.length) {
                item = items[0];
                
                center = new google.maps.LatLng(parseFloat(item.latitude), parseFloat(item.longitude));
                
                if (item.zoom) {
                    bestZoomLevel = parseFloat(item.zoom);
                }
                else {
                    bestZoomLevel = that.zoom_focus;
                }
            }
            else {
                //Create a view that encompasses all points mentioned in the mapfocus parameter
                var bounds = new google.maps.LatLngBounds();
                for (var i = 0; i < items.length; i++) {
                    item = items[i];
                    bounds.extend(new google.maps.LatLng(parseFloat(item.latitude), parseFloat(item.longitude)));
                    //that.debug('Extended bounds with: '+item.latitude+','+item.longitude);
                }
                
                //More than one point means we calculate zoom level from bounds
                bestZoomLevel=that.gmap.getBoundsZoomLevel(bounds);
                
                center = bounds.getCenter();
            }
        }
        
        //console.log('Final position and zoom:'+center+', '+bestZoomLevel+' ('+boundBestZoomLevel+')');
        
        //Put in bounds
        bestZoomLevel = Math.max(Math.min(bestZoomLevel, that.zoom_max), that.zoom_min);
        
        that.setCenter(center, bestZoomLevel, that.maps.auto);
    };
    
    
    that.initMarkerManagers=function(){
        that.map_manager_opts = { borderPadding: 100, trackMarkers: false, maxZoom: that.zoom_max};
        that.poimgrs.empty();
    };


    that.initGMap = function() {
        if (!that.params.sel) {
            that.params.sel = that.params.options.map_class;
        }
        
        if (undefined === that.params.poi_tool_enabled) {
            that.params.poi_tool_enabled = that.params.options.poi_tool_enabled;
        }
        
        var sel = jQuery(that.params.sel);
        that.sel = sel;
        
        var map_element = jQuery(".map", sel);
        
        //Handle errors in selector BEFORE we try to use it
        
        if (map_element.length <= 0) {
            that.alert('Feil i kartkoden (selector "'+that.params.sel+'" bonkers). vennligst rapporter til redaksjonen@ut.no');
            return;
        }

        that.gmap = new google.maps.Map2(map_element[0]);

        //Set onLoad event
        google.maps.Event.addListener(that.gmap, "load", that.initAfterGMapLoad);
        
        //Trigger map load. The position here is not important
        that.setCenter(new google.maps.LatLng(60,5));
    };
    
    
    that.isEnabledKartverket = function() {
        return that.params.use_kartverket ? true : false;
    };
    
    
    //Event listener that saves last map center in cookies on pan and zoom
    that.saveLastLocationInCookie=function(){
        var pos = that.lastPosition;
        
        if (pos && that.params.options.show_controls) {
            pos.saveGMapCenter(that.gmap);
        }
    };
    
    
    that.getCopyright = function() {
        if (that.copyright) {
            var latLngBounds = new google.maps.LatLngBounds(new google.maps.LatLng(-90, -180), new google.maps.LatLng(90, 180));
            that.copyright = new google.maps.Copyright(2, latLngBounds, that.zoom_min, '&copy; Statens Kartverk');
        }
        
        return that.copyright;
    };
    
    
    that.getCopyrightCollection = function() {
        if (!that.copyrightCollection) {
            that.copyrightCollection = new google.maps.CopyrightCollection('Kartdata');
        }
        
        that.copyrightCollection.addCopyright(that.getCopyright());
        return that.copyrightCollection;
    };
    
    
    that.initCustomMapLayers=function(){
        var kartverket = new UtMapKartverket({
            tileUrlTemplate: that.params.wms_url_template,
            wms_token: that.wms_token
        });
        
        // N50-kart
        var n50TileLayer = kartverket.createTileLayer({
            name: 'Turkart',
            service: 'topo2',
            error_message: 'Turkart ikke tilgjengelig'
        });
        
        that.maps.turkart = n50TileLayer.getMapType();
        
        
        var toporasterLayer = kartverket.createTileLayer({
            name: 'Turkart raster',
            service: 'toporaster2',
            error_message: 'Turkart raster ikke tilgjengelig'
        });
        
        
        // Auto-kart
        var auto = new UtMapAutoLayer({
            name: 'Auto',
            error_message: 'Kartdata ikke tilgjengelig',
            isPng : false,
            opacity : 1
        });
        
        var gTerrainTileLayers = G_PHYSICAL_MAP.getTileLayers();
        auto.addLayer(gTerrainTileLayers[0], 0);
        
        if (that.isEnabledKartverket()) {
            auto.addLayer(n50TileLayer, 9);
            auto.addLayer(toporasterLayer, 12);
        }
        
        that.maps.auto = auto.getMapType();
        
        
        var dnttile_manager = new ut.map.dnt.TileManager({
            name: 'dntlayers',
            tileUrlTemplate: that.params.URLtpl,
            isPng : true,
            opacity : 1.0,
            zoom_from: 6, // We dont have tiles on lower zoom
            zoom_to: that.zoom_max
        });
        that.dnttile_manager = dnttile_manager;
    };


    that.restrictMapZoom=function(minResolution,maxResolution){
        // Restrict minimum map zoom
        G_PHYSICAL_MAP.getMinimumResolution = 
        G_NORMAL_MAP.getMinimumResolution = 
        G_HYBRID_MAP.getMinimumResolution = 
        that.maps.turkart.getMinimumResolution = 
        that.maps.auto.getMinimumResolution = function() {
            return minResolution;
        };

        // Restrict maximum map zoom
        G_PHYSICAL_MAP.getMaximumResolution = 
        G_NORMAL_MAP.getMaximumResolution = 
        G_HYBRID_MAP.getMaximumResolution = 
        that.maps.turkart.getMaximumResolution = 
        that.maps.auto.getMaximumResolution = function() {
            return maxResolution;
        };
    };

    that.initMapTypes=function(){
        // Add maptypes in correct order
        that.gmap.addMapType(that.maps.auto);
        that.gmap.addMapType(that.maps.turkart);
        that.gmap.addMapType(G_NORMAL_MAP);
        that.gmap.addMapType(G_HYBRID_MAP);
        that.gmap.addMapType(G_PHYSICAL_MAP);
        that.gmap.addMapType(G_SATELLITE_3D_MAP);
        
        /*
        // XXX For testing GeoServer and tiles
        var ut_geodata_layer = [new google.maps.TileLayer(that.getCopyrightCollection(), that.zoom_min, that.zoom_max, {
            tileUrlTemplate: 'http://vmalxtest03:8080/geowebcache/service/gmaps?layers=cite:norway_natural&zoom={Z}&x={X}&y={Y}&format=image/png',
            isPng:true,
            opacity:0.5 } )];
        var ut_geodata_type = new google.maps.MapType(ut_geodata_layer, new google.maps.MercatorProjection(19), 'Geodata', {
            errorMessage : 'Geodata ikke tilgjengelig'
        });
        that.gmap.addMapType(ut_geodata_type);
        */
    };



    that.getIconSet=function(base_name){
        return that.icons.getIconSet(base_name);
    };
    
    
    that.getMarkerManagerByPOIType = function(ctype) {
        if (!that.poimgrs.is_set(ctype)) {
            //console.log('new mgr for '+ctype);
            var mgr = new MarkerManager(that.gmap, that.map_manager_opts);
            that.poimgrs.put(ctype, mgr);
        }
        
        return that.poimgrs.get(ctype);
    };
    
    
    that.getKeyForPOI=function(item){
        var key='ubrukt';
        if (item) {
            //Exception for user-defined poi types:
            if ('poi' === item.type) {
                if (item.custom_type) {
                    key='custom_'+item.custom_type;
                }
            }
            else if (item.type) {
                key = item.type;
            }
        }
        
        //Debug
        //that.debug_str+=', '+item.type+'('+item.custom_type+') ='+key;
        return key;
    };
    
    that.getIconForPOI=function(item,small_icons){
        var key=that.getKeyForPOI(item);
        var icon_set=that.getIconSet(key);
        //that.debug('getIconForPOI:'+key+', icon_set:'+icon_set.large.image);
        return small_icons ? icon_set.small : icon_set.large;
    };
    
    
    that.createMarkerForPOI = function(item, small_icons, zoom) {
        var icon = that.getIconForPOI(item,small_icons);
        var marker = new google.maps.Marker(new google.maps.LatLng(item.lat, item.lng), { icon: icon, title: item.title });
        var mgr = that.getMarkerManagerByPOIType(item.type);
        //that.debug_str+=', '+marker.getIcon().image;
        //that.debug('callback mgr2: '+ctype+', zoom:'+zoom+', marker:'+marker.getIcon().image+'');
        //TODO: This will add the same markers over and over for different zoom levels instead of remembering which zoom levels the marker will be present for.
        mgr.addMarker(marker,zoom,zoom);
        return marker;
    };

    
    that.initOverviewMapControl=function(){
        //Create the overview map, and make sure the zoom level is satisfactory
        that.overview_map_control=new google.maps.OverviewMapControl(new google.maps.Size(174, 98));
        that.gmap.addControl(that.overview_map_control);
        /*
        //Hack! Undocumented features
        var omap=that.overview_map_control.getOverviewMap();
        if(omap) {
            google.maps.Event.addListener(that.gmap, "zoomend", function() {
                omap.setZoom(7); 
            });
        }
        */
    };
    
    
    that.getWebRoot = function() {
        return that.params.web_root;
    };


    that.initDragZoomControl=function(){
        var options = {
            buttonStartingStyle : {
                border : '0',
                padding : '0'
            },
            buttonHTML : '<img src="' + that.getWebRoot() + 'gfx/ikoner/kart/kontrol/zoom.png">',
            buttonStyle : {
                width : '24px',
                height : '24px'
            },
            buttonZoomingHTML : '<img src="' + that.getWebRoot() + 'gfx/ikoner/kart/kontrol/zoom.png">',
            buttonZoomingStyle : {
                padding : '0'
            },
            overlayRemoveTime : 1500
        };
        that.gmap.addControl(new DragZoomControl( {}, options, {}), new google.maps.ControlPosition(G_ANCHOR_TOP_LEFT, new google.maps.Size(25, 210)));
    };
    
    
    that.createUiOptions = function() {
        var opts = new google.maps.MapUIOptions(new google.maps.Size(401,301));
        opts.maptypes.normal = false;
        opts.maptypes.satellite = false;
        opts.maptypes.hybrid = false;
        opts.maptypes.physical = false;
        opts.zoom.scrollwheel = true;
        opts.zoom.doubleclick = true;
        opts.keyboard = true;
        opts.controls.largemapcontrol3d = true;
        opts.controls.maptypecontrol = false;
        return opts;
    };


    that.initUI= function(ui_enabled){
        if (ui_enabled) {
            //Create ui controls
            var customUI = that.createUiOptions();
            that.gmap.setUI(customUI);
            that.gmap.enableContinuousZoom();
            
            //Prepare map types
            that.initMapTypes();
            
            that.initOverviewMapControl();
            //FIXME: After this has been debugged, put it back into the mix
            //that.initDragZoomControl();
        }
        else {
            that.gmap.disableDragging();
            that.gmap.disableDoubleClickZoom();
            that.gmap.disableScrollWheelZoom();
            that.gmap.hideControls();
            var blockURL = that.getWebRoot() + that.params.maptool_base + that.params.urlpostfix;
            that.blockMap(blockURL);
        }
    };
    
    that.panToCustomPOI=function(){
        if(that.custom_poi_marker) {
            that.panTo(that.custom_poi_marker.getLatLng());
        }
    };

    that.updateCustomPOIPosition=function(lat,long){
        if(that.custom_poi_marker) {
            that.custom_poi_marker.setLatLng(new google.maps.LatLng(lat,long));
        }
    };
    
    that.clearCustomPOI=function(){
        if(that.custom_poi_marker) {
            that.gmap.removeOverlay(that.custom_poi_marker);
        }
    };
    
    that.panTo=function(position){
        that.gmap.panTo(position);
    };

    // TODO refactor this POI stuff to new files
    that.addCustomPOIToolMarker = function(latlng, poiTool) {
        if (!poiTool) {
            return;
        }
        
        var icon_set = that.getIconSet('bruker');
        var icon = null;
        if (icon_set.extra_large) {
            icon = icon_set.extra_large;
        }
        that.custom_poi_marker = new google.maps.Marker(latlng,{draggable:true, autoPan:false, icon: icon});
        google.maps.Event.addListener(that.custom_poi_marker, "drag",  function(){ poiTool.dragCallback(that.custom_poi_marker);});
        google.maps.Event.addListener(that.custom_poi_marker, "click", function(){ poiTool.clickCallback(that.custom_poi_marker);});
        google.maps.Event.addListener(that.custom_poi_marker, "dragend",  function(){ poiTool.dropCallback( that.custom_poi_marker);});
        that.gmap.addOverlay(that.custom_poi_marker);
    };
    
    
    that.isPoiToolEnabled = function() {
        return that.params.poi_tool_enabled;
    };
    

    //Go into put poi mode
    that.followPOI=function(initialPosition,poiTool){
        if (!that.isPoiToolEnabled()) {
            return;
        }
        
        var dog = true;
        var noMore = false;
        //Just put the marker
        if (initialPosition) {
            that.addCustomPOIToolMarker(initialPosition, poiTool);
            poiTool.finishCallback(that.custom_poi_marker);
        }
        else {
            var mouseMove = google.maps.Event.addListener(that.gmap, 'mousemove', function(latlng){
                //Put the marker at first sign of movement
                if(!noMore){
                    that.addCustomPOIToolMarker(latlng,poiTool);
                    noMore = true;
                }
                //Move marker at any sign of movement
                if(dog) {
                    that.custom_poi_marker.setLatLng(latlng);
                    poiTool.dragCallback(that.custom_poi_marker);
                }
            });
            
            //Let go of marker once click is observed
            var mapClick = google.maps.Event.addListener(that.gmap, 'click', function(){
                dog = false;
                //'mousemove' event listener is deleted to save resources
                google.maps.Event.removeListener(mouseMove);
                google.maps.Event.removeListener(mapClick);
                poiTool.finishCallback(that.custom_poi_marker);
            });
        }
    };


    //Make sure layers are added in the proper order
    that.updateTileLayers = function(){
        if (that.use_nve_tilemap) {
            that.showTileOverlay(that.nvetile_overlay);
        }
        else {
            that.hideTileOverlay(that.nvetile_overlay);
        }
        
        if (that.use_dnt_tilemap) {
            that.showTileOverlay(that.dnttile_overlay);
        }
        else {
            that.hideTileOverlay(that.dnttile_overlay);
        }
    };
    
    
    that.addTileOverlay = function(overlay) {
        if (overlay) {
            that.gmap.addOverlay(overlay);
        }
    };
    
    
    that.removeTileOverlay = function(overlay) {
        if (overlay) {
            that.gmap.removeOverlay(overlay);
        }
    };
    
    
    that.hideTileOverlay = function(overlay) {
        if (overlay && !overlay.isHidden()) {
            overlay.hide();
        }
    };
    
    
    that.showTileOverlay = function(overlay) {
        if (overlay && overlay.isHidden()) {
            overlay.show();
        }
    };

    
    that.updateDNTTileLayers = function(type) {
        if (!type) {
            type = '';
        }
        that.imagemaps.empty();
        type = type.replace(/layers=/gi, '');
        type = type.replace(/&/gi, '+');
        
        that.use_dnt_tilemap=true;
        
        if ('' === type) {
            that.use_dnt_tilemap = false;
        }
        else if (that.last_dnt_type !== type) {
            //Remove the last one
            that.removeTileOverlay(that.dnttile_overlay);
            
            var layer = that.dnttile_manager.getTileLayer(type);
            
            that.dnttile_overlay = layer.getTileLayerOverlay();
            that.addTileOverlay(that.dnttile_overlay);
            
            //that.debug('DNTLAYERS REBUILT WITH TYPE:'+type);
        }
        
        that.last_dnt_type = type;
        
        that.updateTileLayers();
    };
    
    
    that.updateNVETileLayers=function (type) {
        if (!type) {
            type = '';
        }
        type = type.replace(/nvelayers=/gi, '');
        type = type.replace(/&/gi, '+');
        that.use_nve_tilemap=false;
        // Add overlay only if map zoom is between 6 - 13 (no tiles for other zoom levels)
        if ('snow.ski' === type && that.isValidZoom()) {
            if (!that.nvetile_overlay) {
                // Lazy loading
                var layer = that.createNveSnowSkiTileLayer();
                
                //that.addMapOverlay(layer.getMapType());
                //that.addTileOverlay(layer.getTileLayerOverlay());
                
                that.nvetile_overlay = layer.getTileLayerOverlay();
                that.addTileOverlay(that.nvetile_overlay);
                //that.debug('NVELAYERS REBUILT WITH TYPE:'+type);
            }
            
            that.use_nve_tilemap = true;
        }
        
        that.updateTileLayers();
    };
    
    
    /**
     * @return UtWmsLayer
     */
    that.createNveSnowSkiTileLayer = function() {
        var layer = new UtWmsLayer({
            name:'Skiføre',
            url_base:'http://webcache.oslo.dnmi.no/verportal/ut_snowmaps.map',
            layers:'snow.ski,coastlines', //Available layers: background,snow.ski,coastlines,snow.fsw,snow.sd
            format:'image/png',
            error_message:'NVE Kartdata ikke tilgjengelig',
            copyright: 'NVE'
        });
        
        return layer;
    };
    
    
    that.isValidZoom = function() {
        var zoom = that.gmap.getZoom();
        
        if (zoom < that.zoom_min || zoom > that.zoom_max) {
            return false;
        }
        return true;
    };


    that.setMapType = function(maptype) {
        var map;
        
        //that.debug("Setting maptype: "+maptype);
        switch (maptype) {
            case 'google-kart':
                map = G_NORMAL_MAP;
                break;
                
            case 'google-terreng':
                map = G_PHYSICAL_MAP;
                break;
                
            case 'google-hybrid':
                map = G_HYBRID_MAP;
                break;
                
            case 'google-earth':
                map = G_SATELLITE_3D_MAP;
                break;
            
            default:
                map = that.maps[maptype];
                break;
        }
        
        if (!map) {
            map = that.maps.auto;
        }
        
        that.gmap.setMapType(map);
    };

    
    that.refreshPoiMarkersVisibility = function() {
        //Show/hide layers based on UI
        jQuery('.layerselector input.pois', that.getMapTool()).each(function() {
            var el=jQuery(this);
            var poi_type = el.attr('value');
            //that.debug("Poi type: "+poi_type);
            //Create missing marker manager when needed
            var mgr = that.getMarkerManagerByPOIType(poi_type);
            if (el.is(":checked")) {
                if (mgr.isHidden()) {
                    mgr.show();
                }
            }
            else if (!mgr.isHidden()) {
                mgr.hide();
            }
        });
    };
    
    
    that.webservicePOICallback=function(data) {
        that.debug_str='';
        
        var newCount=0;
        var total=0;
        var poi_icons=[];
        //Update the markers themselves
        if (data) {
            var zoom = that.gmap.getZoom();
            var small_icons = (zoom < 7);
            var icon = null;
            var click_listener = null;
            
            total = data.items.length;
            
            // Add markers to their managers
            jQuery.each(data.items, function(i, item) {
                //that.debug('poi:'+item.type+', custom:'+item.custom_type);
                
                //Trivial reject
                if (!item.uuid) {
                    return;
                }
                 
                //Generate unique id
                var id = item.uuid+'-'+item.type+'-'+zoom;
                
                //Cache hit
                if (that.markerCache.is_set(id)) {
                    return;
                }
                
                //Cache miss, add to cache
                var marker = that.createMarkerForPOI(item,small_icons,zoom);
                that.markerCache.put(id, marker);
                newCount++;
                google.maps.Event.addListener(marker, "click", function(){
                    var tab=that.getBubbleTabAjax(item.title, that.params.bubbleURL + item.type+'/'+item.uuid);
                    that.openTabbedBubbleForPoint(this.getLatLng(), [tab]);
                });
            });
        }
        //that.debug(''+(total-newCount)+' of '+total+' fetched pois were already in the cache. Debugstring: '+that.debug_str);
        jQuery('.ut-map-activity-indicator').removeClass('active');
    };


    that.getGoogleMap = function() {
        return that.gmap;
    };


    that.updatePoiLayers=function(type) {
        if (!type) {
            type='';
        }
        
        type = type.replace(/pois=/gi, '');
        type = type.replace(/&/gi, '|');
        
        that.refreshPoiMarkersVisibility();
        
        if ('' === type) {
            return;
        }
        
        var mapsize = that.gmap.getSize();
        var nw = that.gmap.fromContainerPixelToLatLng(new google.maps.Point(0, 0));
        var se = that.gmap.fromContainerPixelToLatLng(new google.maps.Point(mapsize.width, mapsize.height));
        
        var query = 'nwlat=' + nw.lat() + '&nwlong=' + nw.lng() + '&selat=' + se.lat() + '&selong=' + se.lng() + '&pois=' + type;
        var ws_query=that.getWebRoot() + "map/ws/poi/?" + query;
        //that.debug(ws_query);
        jQuery('.ut-map-activity-indicator').addClass('active');
        jQuery.getJSON(ws_query, that.webservicePOICallback);
    };
    
    
    that.addShadowbox=function(marker, item) {
        google.maps.Event.addListener(marker, 'click', function() {
            Shadowbox.open( {
                content : item.url,
                player : item.type,
                title : item.title,
                width : item.width,
                height : item.height
            });
        });
    };
    
    that.refresh=function(){
        //Get current center and zoom
        var center = that.gmap.getCenter();
        var zoom = that.gmap.getZoom();
        //Refresh map
        that.gmap.checkResize();
        //Reapply center and zoom
        that.setCenter(center,zoom);
    };
    
    
    that.alert = function(msg) {
        // TODO make an area on the maptool to display these messages instead of alert
        alert("UT.no: "+msg);
    };
    
    
    that.debug = function(msg) {
        console.log(msg);
    };
    
    
    /**
     * @param center
     * @param zoom if undefined, the default zoom is used
     * @param map if undefined, the map is not changed (gmaps functionality)
     */
    that.setCenter = function(center, zoom, map) {
        if (undefined === zoom) {
            zoom = that.zoom_default;
        }
        
        that.gmap.setCenter(center, zoom, map);
    };
    
    
    that.getBubbleTabAjax=function(label,url){
        var tab_data='<h2>Beklager</h2><p>En feil har oppstått. Vennligst prøv igjen seinere!</p>';
        jQuery.ajax( {
             type : 'GET'
            ,url : url
            ,async : false
            ,dataType : 'html'
            ,success : function(data, status) {
                tab_data=data;
            }
        });
        return new google.maps.InfoWindowTab(label, tab_data);
    };
    

    that.getBubbleOpts=function(tabs, onComplete,w,h,marker){
        var tabWidth = 300;
        if (tabWidth < (tabs.length * 87)) {
            tabWidth = tabs.length * 87;
        }
        if (!w) {
            w=0;
        }
        if (!h) {
            h=0;
        }
        var po = new google.maps.Size(w,h);
        var opts = {
            maxWidth : tabWidth,
            onOpenFn: function(){
                if ("function" === typeof onComplete) {
                    onComplete(that.gmap.getInfoWindow(),marker);
                }
            },
            pixelOffset: po
        };
        return opts;
    };
    
    
    that.openTabbedBubbleForMarker=function(marker, tabs, onComplete){
        that.gmap.openInfoWindowTabsHtml(marker.getPoint(), tabs, that.getBubbleOpts(tabs, onComplete,0,-50,marker));
        //NOTE: This does not work because the callback only gets called  for map/point and not for marker:
        //marker.openInfoWindowTabsHtml(tabs, that.getBubbleOpts(tabs, function(){alert('c');}));
        
    };
    
    
    that.openTabbedBubbleForPoint=function(point, tabs, onComplete,w,h){
        that.gmap.openInfoWindowTabsHtml(point, tabs, that.getBubbleOpts(tabs, null,0,0));
    };
    
    
    that.closeBubbles=function(){
        that.gmap.closeInfoWindow();
    };
    
    // Add support for crosshair
    that.addCrosshairs = function(icon_url, size_w, size_h, offset_x, offset_y) {
        if (icon_url) {
            var container = that.gmap.getContainer();
            var crosshairs = document.createElement("img");
            
            offset_x = that.parseInt(offset_x);
            offset_y = that.parseInt(offset_y);
            
            size_w  = that.parseInt(size_w);
            size_h  = that.parseInt(size_h);
            
            var containerWidth  = that.parseInt(container.clientWidth);
            var containerHeight = that.parseInt(container.clientHeight);
            
            var left = that.parseInt( (containerWidth  - size_w) / 2) + offset_x;
            var top  = that.parseInt( (containerHeight - size_h) / 2) + offset_y;
            
            crosshairs.src = icon_url;
            crosshairs.style.width    = size_w + 'px';
            crosshairs.style.height   = size_h + 'px';
            crosshairs.style.border   = '0';
            crosshairs.style.position = 'relative';
            crosshairs.style.left     = left+'px';
            crosshairs.style.top      = top+'px';
            crosshairs.style.zIndex   = '5000';
            container.appendChild(crosshairs);
            that.gmap.crosshairs      = crosshairs;
            return crosshairs;
        }
        
        return null;
    };
    
    that.parseInt = function(val, radix) {
        if (undefined === radix) {
            radix = 10;
        }
        return parseInt(val, radix);
    };

    //Add an invisible layer above map that captures all mouse events, to block user from interacting with map.
    //URL is which url to show the user on click (if any)
    that.blockMap=function(blockUrl){
        
        // Display a rectangle in the center of the map at about a quarter of
        // the size of the main map
        
        var bounds = that.gmap.getBounds();
        var blockRect = that.createBlockFromBounds(bounds, blockUrl);
        that.gmap.addOverlay(blockRect);
    };
    
    
    that.createBlockFromBounds = function(bounds, url) {
        var southWest = bounds.getSouthWest();
        var northEast = bounds.getNorthEast();
        var lngDelta = (northEast.lng() - southWest.lng());
        var latDelta = (northEast.lat() - southWest.lat());
        var rectBounds = new GLatLngBounds(
             new GLatLng(southWest.lat() + latDelta, southWest.lng() + lngDelta),
             new GLatLng(northEast.lat() - latDelta, northEast.lng() - lngDelta)
        );
        
        var blockRect=new UtMapBlockRectangle(rectBounds, 2, url);
        return blockRect;
    };
    

    // /  /   /    /     /      /     /    /   /  / //////
    //// /  /   /    /  User Interface  /    /   /  / ////
    ////// /  /   /    /     /      /     /    /   /  / //
    

    that.initMaptoolUI = function(){
        that.initNVETileLayersUI();
        that.initDNTTileLayersUI();
        that.initAreasUI();
        that.initMapTypesUI();
        that.initPOIsUI();
        that.initBubblesUI();
    };
    
    that.zoom=function(oldLevel,newLevel,dnt_layers){
        that.imagemaps.empty();
        jQuery('.autolabel', that.getMapTool()).attr('title', newLevel);
    };

    /* TILE LAYERS (hytter, ruter) ********************************************/
    that.initDNTTileLayersUI = function(){
        var dntlayers_input = jQuery('.layerselector form input.dntlayers', that.getMapTool());

        var _updateDntTileLayers = function() {
            that.updateDNTTileLayers(jQuery(dntlayers_input).serialize());
        };
        
        // Initial refresh
        _updateDntTileLayers();

        // Zoom handler
        google.maps.Event.addListener(that.gmap, 'zoomend', function(oldLevel, newLevel) {
            that.zoom(oldLevel,newLevel,jQuery(dntlayers_input).serialize());
        });
        
        // (IE-only) On layerselector check: update tile layers
        if (jQuery.browser.msie) {
            jQuery(dntlayers_input).live('click', _updateDntTileLayers);
        }
        else {
            // On layerselector check: update tile layers
            jQuery(dntlayers_input).change(_updateDntTileLayers);
        }
    
        // FIXME should only load image maps for DNT tiles
        //Event listener that imports clickable image maps over tile layers.
        if (that.params.options.dnt_layers_clickable) {
            that.getMapTool().mousemove(function(e){
                if (!that.isValidZoom()) {
                    return;
                }
                
                that.addImageMapToTileImage(e.target);
            });
        }
    };
    

    that.addImageMapToTileImage = function (el) {
        that.imagemaps.initTileImage(el);
    };
    
    

    /* TILE LAYER FOR SNOWCONDITIONS ********************************************/
    that.initNVETileLayersUI = function(){
        var nvelayers_input = jQuery('.layerselector form input.nvelayers', that.getMapTool());

        var _updateNVETileLayers = function() {
            that.updateNVETileLayers(jQuery(nvelayers_input).serialize());
        };
        
        // Initial refresh
        _updateNVETileLayers();

        // Zoom handler
        google.maps.Event.addListener(that.gmap, 'zoomend', function(oldLevel, newLevel) {
            that.zoom(oldLevel,newLevel,jQuery(nvelayers_input).serialize());
        });
        
        // (IE-only) On layerselector check: update tile layers
        if (jQuery.browser.msie) {
            jQuery(nvelayers_input).live('click', _updateNVETileLayers);
        }
        else {
            // On layerselector check: update tile layers
            jQuery(nvelayers_input).change(_updateNVETileLayers);
        }
    };
    
    
    /* DNT AREAS **********************************************************/
    that.initAreasUI = function(){
        var layerselector_dntareas=jQuery('.layerselector form input.dntareas', that.getMapTool());
        // Toggles overlay on/off
        var _dntareas_event_handler = function() {
            if (!that.dntarea_overlay) {
                that.dntarea_overlay = new google.maps.GeoXml('http://static-1.ut.no/turomrader/turomrader.kml');
            }
            
            if (jQuery('input.dntareas:checked', that.getMapTool()).val()) {
                that.gmap.addOverlay(that.dntarea_overlay);
            }
            else {
                that.gmap.removeOverlay(that.dntarea_overlay);
            }
        };

        // IE-only event listener for single checkbox
        if (jQuery.browser.msie) {
            layerselector_dntareas.live('click', _dntareas_event_handler);
        }
        else {
            // Event listener for single checkbox
            layerselector_dntareas.change(_dntareas_event_handler);
        }
    };

    /* MAP TYPES **********************************************************/
    that.initMapTypesUI = function() {
        var maptypeselector=jQuery('.maptypeselector', that.getMapTool());
        
        // Magic function that switches between differen map types :O
        var _maptypes_event_handler = function() {
            that.setMapType(maptypeselector.val());
        };
        
        // Event listener for idiot browsers
        if (jQuery.browser.msie) {
            maptypeselector.live('click', _maptypes_event_handler);
        }
        else {
            // Event listener for proper browsers
            maptypeselector.change(_maptypes_event_handler);
        }
    };

    /* POIs ***************************************************************/
    that.initPOIsUI = function(){
        var poilayers_input = jQuery('.layerselector input.pois', that.getMapTool());
        
        var _updatePoiLayers = function() {
            that.updatePoiLayers(jQuery(poilayers_input).serialize());
        };
        
        _updatePoiLayers();
        
        // Move handler (since this doesnt work like tiles)
        google.maps.Event.addListener(that.gmap, 'moveend', _updatePoiLayers);
        
        // Zoom handler
        google.maps.Event.addListener(that.gmap, 'zoomend', function(oldLevel, newLevel) {
            that.zoom(oldLevel,newLevel,jQuery(poilayers_input).serialize());
        });
        
        // (IE-only) On layerselector check: update tile layers
        if (jQuery.browser.msie) {
            jQuery(poilayers_input).live('click', _updatePoiLayers);
        }
        else {
            // On layerselector check: update tile layers
            jQuery(poilayers_input).change(_updatePoiLayers);
        }
    };
    

    /* BUBBLES ***************************************************************/
    
    that.initBubblesUI = function(){
        /*
         * Click event listener on area elements. Makes infowindows (aka. bubbles)
         * pop up at the correct spot and with the correct info when you click a
         * track or cabin on the map.
         */
        var clickListener = function(el, e) {
            var link = jQuery(el);
            var href = link.attr("href");
            if (!href) {
                return false;
            }
            var labels = href.replace('#', '').split('|');
            var offset = jQuery('.map', that.getMapTool()).offset();
            //that.debug("sel:"+that.params.sel+" offset:"+offset.left+", "+offset.top+" page:"+e.pageX+", "+e.pageY);
            var x = e.pageX - that.parseInt(offset.left);
            var y = e.pageY - that.parseInt(offset.top);
            
            var point = that.gmap.fromContainerPixelToLatLng(new google.maps.Point(x, y));

            var tabs = [];
            
            for (var i in labels) {
                if (labels.hasOwnProperty(i)) {
                    var label = labels[i];
                    var type = label === ""+parseFloat(label) ? "cottage" : "trail";
                    
                    var url = that.params.bubbleURL + type + "/" + label;
                    tabs[i] = that.getBubbleTabAjax(label, url);
                }
            }
            
            that.openTabbedBubbleForPoint(point, tabs);
            return false;
        };
        
        this.imagemaps.registerEventListener('click', function(e){
            try {
                clickListener(e.target, e);
            }
            catch (err) {
                console.log(err);
            }
            
            return false;
        });
        

        // area-hover cursor for IE
        if (jQuery.browser.msie) {
            // TODO check if there are ways to use the context of that.sel
            jQuery('area').live('mouseover', function(e) {
                jQuery(that.params.sel + ' > div > div > div > div').css('cursor', 'pointer');
            }).live('mouseout', function(e) {
                jQuery(that.params.sel + ' > div > div > div > div').css('cursor', 'auto');
            });
        }
    };
    

    
    ///////////////////
    
    //Run ctor
    that.ctor(params);

}//Endof MapUI 



