// googleMaps.js
// Google Maps plugin for Wordpress by Nick Wallis built on the shoulders of
// Avi Alkaley and Mikhail Kornienko
// http://www.32sixteen.com

/*

This code is licensed under the LGPL License
Copyright ( 2 ) 2007 Nick Wallis
Copyright ( c ) 2006 Mikhail Kornienko
Copyright ( c ) 2006 Avi Alkalay


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/******************************************************************************
 * Would like to extend Element with Element.prototype but its not supported
 * on IE6 so have to define the next 2 methods in a non-OO way.
 */
function firstChildElement( elem ) {
    var walker=elem.firstChild;
    while ( walker && ( walker.nodeType != 1 )) {
        walker=walker.nextSibling;
    }
    return walker;
}


function nextSiblingElement( elem ) {
    var walker=elem.nextSibling;
    while ( walker && ( walker.nodeType != 1 )) {
        walker=walker.nextSibling;
    }
    return walker;
}


/******************************************************************************
 * Class to store all single map related parameters: markers, geo position,
 * map type, map options, overlays, HTML element to hook to, style etc.
 */
function MapData( elem, commandsAttributeName, defaultW, defaultH ) {
    // DOM related stuff
    this.elem=elem;
    this.inheritedStyle=null;
    this.className=null;
    this.inheritedID=null;
    this.defaultW=defaultW;
    this.defaultH=defaultH;

    this.commandsAttributeName=commandsAttributeName;

    // Map related stuff
    this.type=G_NORMAL_MAP;
    this.geoCoord=null;
    this.zoom=6;
    this.overviewMapControl=false;
    this.controls=true;
    this.showMarkers=true;

    // default to the Greenwich Meridian/Equator intersection
    this.defaultGeoCoord=new GLatLng( 0.0, 0.0 );

    // The markers including balloon text, position, etc
    this.markers=[];
    this.idLetters=[];

    // List of URLs to KMLs and GeoRSSs to overlay the map
    this.xmlOverlays=[];

}


/*------------------------------------------------------------------------
 * Debugging function.
 */
MapData.prototype.toString = function() {
    var text="";

    text += "Center: " + this.geoCoord.toString() + "<br/>";
    text += "Zoom: " + this.zoom + "<br/>";

    return text;
}


/*------------------------------------------------------------------------

 * This is the method that effectively creates the Google Map based
 * on the MapData object.
 */
MapData.prototype.createGoogleMap = function() {
    var realMap;
    var mapNode;

    mapNode = document.createElement( "div" );

    mapNode.className = "map " + (( this.className ) ?
        " " + this.className :
        "" );

    if ( this.inheritedStyle ) {
        mapNode.style.cssText = this.inheritedStyle;
    }

    mapNode.style.display = "block";

    if ( mapNode.style.visibility == "hidden" ) {
         mapNode.style.visibility="inherit";
    }

    this.elem.parentNode.replaceChild( mapNode, this.elem );

    if ( mapNode.style.width == 0 ) {
        ( this.defaultW.toString().indexOf( "%" ) != -1 ) ?
            mapNode.style.width = this.defaultW :
            mapNode.style.width = this.defaultW + "px";
    }

    if ( mapNode.style.height == 0 ) {
        ( this.defaultH.toString().indexOf( "%" ) != -1 ) ?
            mapNode.style.height = this.defaultH :
            mapNode.style.height = this.defaultH + "px";
    }

    mapNode.id = ( this.inheritedID ) ?
        this.inheritedID :
        "googlemap-" + Math.ceil( 10000 * Math.random());

    // Create the map
//    realMap = new GSmapSearchControl( mapNode );
    realMap = new GMap2( mapNode );
    realMap.setCenter( this.geoCoord, parseInt( this.zoom ),this.type );

    if ( this.controls ) {
        ( parseInt( mapNode.style.height ) >= 320 ) ?
            realMap.addControl( new GLargeMapControl()) :
            realMap.addControl( new GSmallMapControl());

        realMap.addControl( new GMapTypeControl());
        realMap.addControl( new GScaleControl());

        if ( this.overviewMapControl ) {
            realMap.addControl( new GOverviewMapControl());
        }
    }

    while ( this.xmlOverlays.length > 0 ) {
        var url=this.xmlOverlays.shift();
        var overlay=new GGeoXml( url );

        realMap.addOverlay( overlay );
    }

    if ( this.showMarkers && this.markers.length > 0 ) {
        var manager=new GMarkerManager( realMap );

        manager.addMarkers( this.markers, 1 );
        manager.refresh();
    }
}


/*------------------------------------------------------------------------
 * Extract Latitude and Longitude, zoom factor, whether to show an
 * overview mini map, map type, and possibly detect a My Maps ID, all from
 * a Google Maps URL.
 *
 */
MapData.prototype.parseGoogleMapsURL = function( gmapsurl ) {
    if ( gmapsurl.indexOf( "http://maps.google." ) == -1 ) {
        return 0;
    }

    var i;
    var params = gmapsurl.split( "?" );
    params = params[1].split( "&" );

    for ( i = 0; i <= params.length - 1; i++ ) {
        var param = params[i].split( "=" );
        switch ( param[0] ) {
            case "ll":
                var _geocoord = param[1].split( "," );
                if (( _geocoord[0] != null) && (_geocoord[1] != null )) {
                    this.geoCoord=new GLatLng( _geocoord[0],_geocoord[1] );
                }
                break;

            case "z":
                this.zoom = param[1];
                break;

            case "om":
                this.overviewMapControl = ( param[1] == "0" );
                break;

            case "t":
                switch ( param[1] ) {
                    case 'k':
                        this.type=G_SATELLITE_MAP;
                        break;
                    case 'h':
                        this.type=G_HYBRID_MAP;
                        break;
                }
                break;

            case "msid":
                // This is a multimarker map built and saved on Google Maps UI
                this.xmlOverlays.push(
                    "http://maps.google.com/maps/ms?oe=UTF-8&msa=0&output=kml&msid=" + param[1] );
            break;
        }
    }
    return 1;
}


/*------------------------------------------------------------------------
 * Parse stuff from title="" or rel="" attributes on XHTML elements.
 * Mostly operational mode, map size, etc.
 */
MapData.prototype.parseCommands = function( commands ) {
    if ( commands == undefined ) {
        return 0;
    } else if ( commands.indexOf( "googlemap" ) == -1 ) {
        return 0;
    }

    var params = commands.split( ";" );
    var i;

    for ( i = 0; i < params.length; i++ ) {
        if ( params[i].indexOf( "nomarker" ) != -1 ) {
            this.showMarkers=false;

        } else if ( params[i].indexOf( "w:" ) != -1 ) {
            var val = params[i].split( ':' );
            this.defaultW=val[1];

        } else if ( params[i].indexOf( "h:" ) != -1 ) {
            var val = params[i].split( ':' );
            this.defaultH=val[1];

        } else if ( params[i].indexOf( "nocontrol" ) != -1 ) {
            this.controls=false;

        } else if ( params[i].indexOf( "letter:" ) != -1 ) {
            var val = params[i].split( ':' );
            var idLetter = val[1].toUpperCase();
            this.idLetters.push( idLetter );
        }
    }
    return 1;
}


/*------------------------------------------------------------------------
 * Creates a GMaps marker and rich HTML balloon.
 */
MapData.prototype.addMarker = function( point,markerHTML,idLetter ) {

    // Define a base icon: end users can modify the marker appearance by
    // modifying the plugin script or further development could be put into
    // developing it programmatically
    var baseIcon = new GIcon();
    baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
    baseIcon.iconSize = new GSize( 20, 34 );
    baseIcon.shadowSize = new GSize( 37, 34 );
    baseIcon.iconAnchor = new GPoint( 9, 34 );
    baseIcon.infoWindowAnchor = new GPoint( 9, 2 );
    baseIcon.infoShadowAnchor = new GPoint( 18, 25 );

    ( idLetter ) ?
        baseIcon.image = "http://www.google.com/mapfiles/marker"+ idLetter + ".png" :
        baseIcon.image = "http://www.google.com/mapfiles/marker.png";

    var marker = new GMarker( point, new GIcon( baseIcon ));

    if ( markerHTML ) {
        GEvent.addListener( marker, "click", function() {
            var opts = { maxWidth : 450 };
            marker.openInfoWindowHtml( "<div class=\"balloon\" style=\"width:auto;height:auto\">"+markerHTML+"</div>", opts );
        } );
    }

    this.markers.push( marker );
}


/*------------------------------------------------------------------------
 * Parse an entire <a title="googlemap"> element, including map URL,
 * operating mode, CSS style, etc and save it on the MapData object.
 */
MapData.prototype.parseAnchorTag = function() {
    if ( !this.parseGoogleMapsURL( this.elem.href )) {
        return 0;
    }

    if ( this.geoCoord == null ) {
        this.geoCoord=this.defaultGeoCoord;
    }

    if ( this.elem.style.cssText ) {
        this.inheritedStyle=this.elem.style.cssText;
    }

    if ( this.elem.className ) {
        this.className=this.elem.className;
    }

    if ( this.elem.id ) {
        this.inheritedID=this.elem.id;
    }

    this.addMarker( this.geoCoord, this.elem.innerHTML );

    return 1;
}


/*------------------------------------------------------------------------
 * Parse an entire <dl title="googlemap"> element, including sub elements,
 * operating mode, CSS style, etc and save it on the MapData object.
 */
MapData.prototype.parseDefinitionBlock = function() {
    var walker;

    if ( this.elem.style.cssText ) {
        this.inheritedStyle=this.elem.style.cssText;
    }

    if ( this.elem.className ) {
        this.className=this.elem.className;
    }

    if ( this.elem.id ) {
        this.inheritedID=this.elem.id;
    }

    // Get rid of "googlemap" and friends commands
    this.elem.title=null;

    // Point to first <dt>
    walker=firstChildElement( this.elem );

    // Process each <dt> and <dd> pair
    while ( walker && walker.nodeName.toLowerCase() == "dt" ) {
        // Get an <a> with the marker's position
        var elem=firstChildElement( walker );

        if ( !elem ) {
            // Empty <dt></dt>, move to next <dt>
            do {
                walker=nextSiblingElement( walker );
            } while ( walker && walker.nodeName.toLowerCase() != "dt" );

            // Restart loop
            continue;
        }

        if ( elem.nodeName.toLowerCase() == "a" ) {
            if ( this.geoCoord == null ) {
                // Still don't have a center point, so the first <a> is the
                // center of map
                this.parseGoogleMapsURL( elem.href );

                // move to next <dt>
                do {
                    walker=nextSiblingElement( walker );
                } while ( walker && walker.nodeName.toLowerCase() != "dt" );

                continue;
            } else if ( elem.title == "kml" || elem.title == "georss" ) {
                // This is a KML or GeoRSS resource on the web.
                this.xmlOverlays.push( elem.href );

                // <dd> is useless in this case, move to next <dt>
                do {
                    walker=nextSiblingElement( walker );
                } while ( walker && walker.nodeName.toLowerCase() != "dt" );

                continue;

            } else {
                // Already have a center point, so this is a plain marker
                var tempMapData = new MapData();

                if ( !tempMapData.parseGoogleMapsURL( elem.href )) {
                    // move to next <dt>
                    do {
                        walker=nextSiblingElement( walker );
                    } while ( walker && walker.nodeName.toLowerCase() != "dt" );

                    continue;
                }

                // Already have the marker position, now get the HTML for the
                // balloon

                // Ensure walker points to <dd>
                do {
                    walker=nextSiblingElement( walker );
                } while ( walker && ( walker.nodeName.toLowerCase() != "dd" && walker.nodeName.toLowerCase() != "dt" ));

                if (( walker && walker.nodeName.toLowerCase() == "dd" ) &&
                    ( this.parseCommands( elem.getAttribute( this.commandsAttributeName )))) {
                    // Marker has a specified identifier letter
                    this.addMarker(
                        tempMapData.geoCoord,walker.innerHTML,
                        this.idLetters[this.idLetters.length-1] );
                } else if ( walker && walker.nodeName.toLowerCase() == "dd" ) {
                    this.addMarker(
                        tempMapData.geoCoord,walker.innerHTML );
                } else {
                    // Marker with no balloon
                    this.addMarker( tempMapData.geoCoord );
                }
            }
        }

        // Parsed a center <dt> or a <dt><dd> pair, seek next <dt>.
        while ( walker && walker.nodeName.toLowerCase() != "dt" ) {
            walker=nextSiblingElement( walker );
        }
    }

    return ( this.geoCoord != null );
}


/*------------------------------------------------------------------------
 * A wrapper for parseAnchorTag() and parseDefinitionBlock() defined above
 */
MapData.prototype.parseNode = function () {

    switch ( this.elem.nodeName.toLowerCase()) {
        case "dl":
            if ( !this.parseCommands( this.elem.getAttribute( "title" ))) {
                return false;
            } else if ( !this.parseDefinitionBlock( this.commandsAttributeName )) {
                return false;
            }
            break;

        case "a":
            if ( !this.parseCommands( this.elem.getAttribute( this.commandsAttributeName ))) {
                return false;
            } else if ( !this.parseAnchorTag( this.commandsAttributeName )) {
                return false;
            }
            break;

        default:
            return false;
    }
    return true;
}


/******************************************************************************
 * Walks through all DOM nodes on the document looking for supported
 * elements and rel=/title="googlemap".
 */
MapPlugin.prototype.consumeMapContainers = function() {
    var mapContainers = ["dl", "a"];
    var attributeForCommands = "title";

    if ( this.useRelAttribute ) {
        attributeForCommands = "rel";
    }

    for ( var i = 0; i < mapContainers.length; i++ ) {
        var elems = document.getElementsByTagName( mapContainers[i] );
        var cur = null;
        var j = 0;

        while (( cur = elems.item( j ))) {
            var cmd = null;
            var map = new MapData(
                cur, attributeForCommands, this.defaultW, this.defaultH );

            map.parseNode() ?
                this.maps.unshift( map ):
                delete map;
            j++;
        }
    }
}


/*------------------------------------------------------------------------
 * After parsing and creating all MapData objects, efficiently create
 * the found maps.
 */
MapPlugin.prototype.createMaps = function() {
    var map;

    while (( map=this.maps.pop())) {
        map.createGoogleMap();
        delete map;
    }
}


/*------------------------------------------------------------------------
 * Object created by window.onload() event, contains global maps options as
 * sizes etc, starts and finishes all maps fetching and creation process.
 *
 * This is the plugin object per se.
 */
function MapPlugin( _defaultWidth, _defaultHeight, _useRelAttribute ) {
    this.defaultW = _defaultWidth;
    this.defaultH = _defaultHeight;
    this.userRelAttribute = _useRelAttribute;

    this.maps=new Array;

    this.consumeMapContainers();
    this.createMaps();
}


/******************************************************************************
 * Creates triggers on page loading/unloading events for plugin initialization.
 */
function MapPluginInit( _defaultWidth, _defaultHeight, _useRelAttribute ) {
    if ( ! GBrowserIsCompatible()) {
        return;
    }

    var oldOnLoad = window.onload;
    var oldOnUnload = window.onunload;

    var instantiate = function() {
        new MapPlugin( _defaultWidth, _defaultHeight, _useRelAttribute );
    }

    if ( typeof window.onload != 'function' ) {
        window.onload = instantiate;
    } else {
        window.onload = function() {
            oldOnLoad();
            instantiate();
        }
    }

    if ( typeof window.onunload != 'function' ) {
        window.onunload = GUnload;
    } else {
        window.onunload = function() {
            oldOnUnload();
            GUnload();
        }
    }
}

