/**
 * Displays a list of jobs on a Google Map, hashed by zip code.
 * Different types of jobs can have different colors, and if more than one type
 * of job appear on the same zip code, a dual-color pushpin will be used to
 * represent them.
 *
 * The input data takes the following format:
 *  { url: <url>,                       The URL from which to fetch all jobs
 *    zips: {
 *        <zip>: {                      A unique zip code
 *          lat: <lat>,                 The centroid latitude of the zip code
 *          lon: <lon>,                 The centroid longitude of the zip code
 *          class: <class>,             The classes of jobs at that zip code
 *          jobs: [ {                   A list of the jobs available at the zip code
 *              id: <job_id>,           The JAMIT ID of the job
 *              class: <class>,         The class of job, used for coloring the icon
 *              title: <job_title>,     The title of the job
 *              summary: <job_summary>, A short summary/overview of the job
                <etc...>                Other fields relevant to displaying the job
 *          } ], <etc...> ],                (Other jobs)
 *      }, <etc...> }                       (Other zip codes)
 *   }
 *
 * Currently, the values of <class> can be 'search1', 'search2', 'both'. 
 * 
 * @param options Options to set:
 *  - div: The <div> in which to draw the map.
 *  - initialData: The initial data, in the above format, to draw on the map.
 *  - onload: A callback to invoke when the data has been loaded.
 *  - iconUrl: The url from which to fetch custom icons.
 *  - iconStyles: A mapping of class to icon image.  If a full URL is specified,
 *            it will be retrieved; otherwise the iconUrl will be prepended.
 *  - legend: If true, show a legend for the different symbols, including
 *            links to hide them.
 *  - initOnLoad: If true, the map will automatically be initialized when the 
 *            document is done loading.  If false, you have to call init() yourself.
 */
function iheGmap(options) {
    var map = null;
    var mapdiv;
    var manager;
    var markers;
    var bounds;
    var mapIds;
    var zoom;
    var initData;
    var url;
    var filterdiv;
    
    var defaults = {
        iconUrl: '/design/ihe/images/google-maps/',
        iconStyles: {
            'search1': {
                'icon': 'blue.png',
                'name': 'First search'
            },
            'search2': {
                'icon': 'red.png',
                'name': 'Second search'
            },
            'both': {
                'icon': 'both.png',
                'name': 'Both searches'
            }
        },
        legend: true,
        initOnLoad: true,
        initialZoom: 12
        
    };
    options = $.extend(defaults, options);
    
    if (options.iconUrl.lastIndexOf('/') != options.iconUrl.length - 1) {
        options.iconUrl += '/';
    }
    
    var markerCount = {};
    for (style in options.iconStyles) {
        markerCount[style] = 0;
    }
    
    /**
     * Initializes the map and loads initial data
     */
    function init() {
        if (typeof(GBrowserIsCompatible) == 'undefined' || !GBrowserIsCompatible()) {
            // TODO Show some error
            return;
        }
        mapdiv = $(options.div);
        if (mapdiv.length == 0) {
            // TODO Show some error
            return;
        }
        
        // Initialize the map
        map = new GMap2(mapdiv.get(0));
        map.setCenter(new GLatLng(38.8888, -77.0264), options.initialZoom);
        map.addControl(new GMapTypeControl());
        map.addControl(new GLargeMapControl());
        map.disableScrollWheelZoom();
        
        
        // Initialize the marker manager, if it has been included
        if (typeof(MarkerManager) != 'undefined') {
            manager = new MarkerManager(map);
        }
        
        // When the info window is closed, return the map to where it was
        GEvent.addListener(map, 'infowindowclose', oninfoclose);
        
        // Register the cleanup code to be run when the document is unloaded
        $("body").unload(GUnload);
        
        // Load any initial data        
        loadData(options.initialData);
        
        // Set up the filter links
        if (options.legend) {
            createLegend();
        }

        if (manager) {
            manager.refresh();
        }
        
        // Invoke the callback
        if (options.onload) {
            var obj = this;
            window.setTimeout(function() { obj.options.onload(obj) }, 100);
        }
    }
    
    /**
     * Loads data of the specified input format into the map, overwriting
     * any previous data.
     *
     * If you pass in null, the map will be cleared.
     */
    function loadData(data) {
        // Start by clearing the map
        if (manager) {
            manager.clearMarkers();
        }
        map.clearOverlays();
        
        markers = [];
        bounds = null;
        mapIds = {};
        
        // Maintain a global store for the data 
        initData = {
            'url': (data ? data.url : null),
            'zips': {}
        };
        
        if (!data) {
            return;
        }
        
        // Render the data
        url = data.url;
        addZips(data.zips);
        
        // Zoom to see all the data
        for (var i in markers) {
            var point = markers[i].getLatLng();
            
            // Compute the outer bounds of all the markers
            if (!bounds) {
                bounds = new GLatLngBounds(point, point);
            }
            else {
                bounds.extend(point);
            }        
        }
        if (bounds) {
            
/*            zoom = map.getBoundsZoomLevel(bounds);
            zoom = Math.min(zoom, 10);
            
            center = bounds.getCenter();
            if (options.initialZoom>0) 
            {
                map.setCenter(center, options.initialZoom);
                options.initialZoom=0;
            }
            else
                map.setCenter(center, zoom);
            */
        }
    }
    
    /**
     * Adds a list of zip codes and their associated data to the map.
     * The zip code can either be the key of each object, or it can be
     * in the 'zip' property.
     */
    function addZips(zips) {
        for (var zip in zips) {
            var info = zips[zip];
            addZip(zip, info);
            // Update the stored data object
            initData.zips[zip] = info;
        }
    }
    
    /**
     * Adds a zip code to the map, and its associated data object.
     */
    function addZip(zip, info) {
        var marker = createMarker(zip, info);
        addMarker(marker);
    }
    
    /**
     * Adds a marker to the map, and hashes it by all of its job ids.
     */
    function addMarker(marker) {
        markers.push(marker);
        
        // Add the marker to the map
        if (manager) {
            manager.addMarker(marker, 1);
        }
        else {
            map.addOverlay(marker);
        }

        // Add the marker to a hash by job id
        for (var i in marker.jobIds) {
            var id = marker.jobIds[i];
            mapIds[id] = marker;
        }
    }
    
    /**
     * Creates a marker from the given zip code, but does not add it to the map
     */
    function createMarker(zip, info) {
        var markerClass = info['group'];
               
        // Create the marker icon
        var image = createIconUrl(markerClass);
        var icon = new GIcon(G_DEFAULT_ICON, image);
        icon.iconSize = new GSize(32,32);
        
        var opts = { 'icon': icon };
        
        // Add a marker for this zip code
        var point = new GLatLng(info.lat, info.lon);
        var marker = new GMarker(point, opts);
        marker['class'] = markerClass;
        
        markerCount[markerClass]++;
        
        // Show the info window when the marker is clicked
        var html = createInfoHtml(zip, info);
        GEvent.addListener(marker, 'click', function() {
        	onclick(marker, html);
        });
        
        return marker;
    }
    
    function createIconUrl(cls) {
        var image = "icons/icon"+cls+".png";
               
        return image;
    }
    
    /**
     * When a placemark is opened, store the previous position of the map,
     * since the map will likely move to make space for the info window.
     */
    function onclick(marker, html) {
        // Store the current map location
        map.savePosition();
        marker.openInfoWindow(html);
    }
    
    /**
     * When the info window is closed, return the map to its previous position
     */
    function oninfoclose() {
        map.returnToSavedPosition();
    }
    
    /**
     * Returns the marker with the given mapId, if it already exists.
     */
    function getMarker(mapId) {
        return mapIds[mapId];
    }
    
    /**
     * Pans the map to center on the marker matching the given mapId,
     * and opens its information window.
     */
    function panToMarker(mapId) {
        if (mapIds[mapId]) {
            var marker = mapIds[mapId];
            
            // Center on the marker and zoom to a standard level
            map.setCenter(marker.getLatLng(), 8);
        }
        else {
            map.savePosition();
            map.openInfoWindow(map.getCenter(), 
                "<p>Could not locate the given job</p>"
            );
        }
    }
        
    function hideMarker(mapId) {
        if (mapIds[mapId]) {
            var marker = mapIds[mapId];
            marker.hide();
        }
    }
    
    function hideAllMarkers() {
        for (var i in markers) {
            markers[i].hide();
        }
    }
    
    /**
     * Creates checkboxes that show or hide each class of placemarks, also
     * serving as a key.
     * Information is drawn from the iconStyles array.
     */
    function createLegend() {
        // If there is nothing showing, don't do a legend
        var hasData = false;
        for (var cls in markerCount) {
            if (markerCount[cls] > 0) {
                hasData = true;
                break;
            }
        }
        if (!hasData) {
            return;
        }
    
        filterdiv = $("<div/>")
            .addClass('map-legend')
            .append("<div>Show on the map all zip codes with jobs that match: </div>")
            .insertAfter(mapdiv);
            
        for (var cls in markerCount) {
            var count = markerCount[cls];
            if (count > 0) {
                var style = options.iconStyles[cls];
                
                var span = $("<span/>")
                    .text(style.name)
                    .appendTo(filterdiv);
                
                var checkbox = $("<input type='checkbox'/>")
                    .attr('checked', 'checked')
                    .prependTo(span);
                    
                var imageUrl = createIconUrl(cls);
                var icon = $("<img/>")
                    .attr('src', imageUrl)
                    .prependTo(span);
                    
                checkbox.bind('click', function() {
                	filterClass(checkbox, cls);
                });
            }
        }
    }
    
    /**
     * Shows or hides all results of the given class
     */
    function filterClass(checkbox, cls) {
        var show = checkbox.is(":checked");
        
        for (var i in markers) {
            var marker = markers[i];
            
            if (marker['class'] == cls) {
                if (show && marker.isHidden()) {
                    marker.show();
                }
                else if (!show && !marker.isHidden()) {
                    marker.hide();
                }
            }
        }
    }
    
    /**
     * When the user clicks on a marker, displays information about that zip code.
     */
    function createInfoHtml(zip, info) {
    
        var html = $("<div class='placemark'>"+info.description+"</div>")

        return html.get(0);
    }

    if (options.initOnLoad) {
        $(document).ready(init);
    }
    
    
    return {
        'loadData' : loadData
    };
}