//Author:   lorraine.kilby
//Created:  7/7/2009 2:53:56 PM

        
var Locator = {

    InitialZoom: 3,
    InitialCentre: new MapDS.LatLng( -33.866774, 151.207108 ),
        
    Strings: {
        InvalidSuburbPostcode: 'Please enter a Town/Suburb or Postcode',
        InvalidSearch: 'Please enter a Street Address, Town/Suburb or Postcode',
        InvalidSuburb: 'Unable to find address, please try different information',
        InvalidPostcode: 'Invalid postcode.',
        MultipleAddressDialogTitle: 'Multiple Address Matches Found',
        MultipleAddressPrompt: 'Choose an address',
        Searching: 'Searching...',
        ItemsFound: '{0} match{1} found'
    },
    
    Images: {
        Loading: './images/loading-circle-black.gif'
    },
    
    ElementIDs: {
        POIResults: 'poi',
        MultipleAddressDialog: 'multipleAddressDialog',
        Sidebar: 'sidebar',
        Map: 'map',
        MultipleAddresses: 'ddlMultipleAddresses',
        ItemsFound: 'itemsfound',
        Search: 'search',
        SearchStreet: 'txtStreet',
        SearchSuburb: 'txtSuburb',
        SearchState: 'ddlState',
        SearchPostcode: 'txtPostcode',
        AdvancedSearchTrigger: 'chkAdvancedSearch',
        AdvancedSearch: 'search-advanced',
        SearchButton: 'btnFindAddress',
        ResetButton: 'btnReset',
        ExceptionDialog: 'exception-dialog',
        DrivingDirections: 'drivingDirections'
    },
    
    /// This method is used to validate the search fields. The following function has code
    /// to perform validation on the standard address fields. Use it as a guide for your
    /// custom search fields.
    validateSearchFields: function() {
        
        var haveValidSuburb = false;
        var haveValidPostcode = false;
        var haveValidStreet = false;
                
        var txtStreet = $( this.getElementID( this.ElementIDs.SearchStreet ) );
        var txtSuburb = $( this.getElementID( this.ElementIDs.SearchSuburb ) );
        var txtPostcode = $( this.getElementID( this.ElementIDs.SearchPostcode ) );
        
        if ( txtStreet.attr("value") != '' && txtStreet.attr("value").toLowerCase() != txtStreet.attr("title").toLowerCase() ) {
            //this.displayException( this.Strings.InvalidStreet );
            //return false;
            haveValidStreet = true;
        }
        
        if ( txtSuburb.attr("value") != '' && txtSuburb.attr("value").toLowerCase() != txtSuburb.attr("title").toLowerCase() ) {
            //this.displayException( this.Strings.InvalidSuburb );
            //return false;
            haveValidSuburb = true;
        } 
        
        //else if ( txtPostcode.attr("value") != '' && txtPostcode.attr("value").toLowerCase() != txtPostcode.attr("title").toLowerCase() ) {
            //this.displayException( this.Strings.InvalidPostcode );
            //return false;
        //    haveValidPostcode = true;
        //}
        
        if ( txtPostcode.attr("value") != '' && txtPostcode.attr("value").toLowerCase() != txtPostcode.attr("title").toLowerCase() ) 
        {
            var pValid = this.IsPostcodeValid(txtPostcode.attr("value"));
            if (pValid) haveValidPostcode = true;
            else 
            {
                this.displayException( this.Strings.InvalidPostcode );
                haveValidPostcode = false;
                return false;                
            }
                
        }
        //haveValidPostcode = true;
        
        
        if ( !haveValidSuburb && !haveValidPostcode && !haveValidStreet) {
            this.displayException( this.Strings.InvalidSearch );
            return false;
        } 
        
        if ( !haveValidSuburb && !haveValidPostcode && haveValidStreet) {
            this.displayException( this.Strings.InvalidSuburbPostcode );
            return false;
        } 
        
        /*else if ( !haveValidSuburb && haveValidPostcode ) {
            this.displayException( this.Strings.InvalidSuburb );
            return false;
        } else if ( haveValidSuburb && !haveValidPostcode ) {
            this.displayException( this.Strings.InvalidPostcode );
            return false;
        }*/
        
        return true;
    },
    
    // The function to check whether the details entered in a text field are numeric
    // is to to loop through the string and compare each character to a pre-defined 
    // list of acceptable characters. Also checking the value of Postcode is within 
    // the range [800, 899] [2000,7999]. 
    
    IsPostcodeValid: function (sText)
    {
        var ValidChars = "0123456789";
        var IsPostcode=true;
        var Char;
        for (i = 0; i < sText.length && IsPostcode == true; i++) 
        { 
            Char = sText.charAt(i); 
            if (ValidChars.indexOf(Char) == -1) 
            {
                IsPostcode = false;
            }
        }
        if (IsPostcode) 
        {
            var pint = parseInt(sText,10);
            if (((pint >= 800) && (pint <= 899) ) || ((pint >= 2000) && (pint <= 7999 ))) 
            {
            IsPostcode = true;
            }
            else IsPostcode = false;
        }             
        return IsPostcode;
   },

    
    /// Reset the search fields back to default values.
    /// Here we set the textfields to the value in the title attribute and then call the blur method to
    /// trigger jQuery hint code.
    resetSearchFields: function() {
       $( this.getElementID( this.ElementIDs.Search ) + ' input[title!=""]').attr("value","").blur();
       $( this.getElementID( this.ElementIDs.Search ) + ' input[type="checkbox"]').attr("checked","");
       $( this.getElementID( this.ElementIDs.SearchState ) ).attr( "selectedIndex", 0 );
    },
    
    
    /// Builds a JSON representation of the address to be geocoded. This method is 
    /// automatically called, it should return an object that contains the following
    /// properties:
    ///     sb = suburb
    ///     st = state
    ///     pc = postcode
    /// if the values are all required by the Geocode handler.
    buildAddressSearch: function() {
        
        // load street information
        var txtStreet = $( this.getElementID( this.ElementIDs.SearchStreet ) );
        var street = txtStreet.attr("value");
        if( txtStreet.attr("value").toLowerCase() == txtStreet.attr("title").toLowerCase() ) {
            street = '';
        }
        // load suburb information
        var txtSuburb = $( this.getElementID( this.ElementIDs.SearchSuburb ) );
        var suburb = txtSuburb.attr("value");
        if( txtSuburb.attr("value").toLowerCase() == txtSuburb.attr("title").toLowerCase() ) {
            suburb = '';
        }
        
        // load postcode information
        var txtPostcode = $( this.getElementID( this.ElementIDs.SearchPostcode ) );
        var postcode = txtPostcode.attr("value");
        if( txtPostcode.attr("value").toLowerCase() == txtPostcode.attr("title").toLowerCase() ) {
            postcode = '';
        }
        
        // load selected state value
        var state = '';
        var ddlState = $( this.getElementID( this.ElementIDs.SearchState ) );
        if( ddlState.attr("selectedIndex") != 0 ) {
            state = ddlState.attr("value");
        }
        
        return { 
            sb: suburb, 
            st: state, 
            pc: postcode,
            str: street
        };
    },
    
    /// Builds a JSON representation of the search filters available.
    /// MUST return either an object or null. Last line should always be "return null;"
    /// instead of removing, return your object before this line. A return is immediate
    /// and the rest of the code within the method will not execute.
    getSearchFiltersJSON: function() {
        return null;
    },
    
    /// This method is called from within findNearest. The purpose of this function is to allow
    /// for control of how the marker is created and how the extra POI information is displayed.
    processPOI: function(id,poi) {
        //Logger.debug(poi);
        var _SELF = this;
        var mkr;
        var withDDLink = true;
        mkr = _SELF.processPOIMarker( id, poi,withDDLink );
        this.map.addMarker( mkr );
        _SELF.processPOIResult( id, poi,mkr )
    },
    
    /// This method is called from within findNearest. The purpose of this function is to allow
    /// for control of how the marker is created and how the extra POI information is displayed.
    processPOIMarker: function(id,poi,withDDLink) {
        //Logger.debug(poi);
        var _SELF = this;
        var size = new MapDS.Size( 37, 29 );  
        var offset = new MapDS.Pixel( -14, -24 );  
        var icn = new MapDS.Icon( './images/markers/{0}.png'.format( id ), size, offset );  
        var mkr = new MapDS.Marker( new MapDS.LatLng( poi.Position.Latitude, poi.Position.Longitude ), icn );
        var spc = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"; 
        var terr = '<span style="text-align:right;" >' + poi.TERRITORY + '</span>';
        var stockist = ( poi.STOCKIST == 'D') ? '<p >Diabetes Stockist</p>' : '';
                
        if (withDDLink) rlink ='<p><a href="#" onclick="Locator.showDirections('+id+')">Get Directions...</a>' +spc + spc + terr + '</p>';   
        else rlink = '<p style="text-align:right;" >' + poi.TERRITORY + '</p>';           
       
        mkr.setPopupContent( '<div class="popup-content"><p style="width:150px;border-width:0px;background: url(./images/homypedlogosmall.jpg) no-repeat 0% 50%;vertical-align:bottom;height:19px;"></p><br/><p><b>{0}</b></p><p>{1}</p><p>{2}</p><p>{3}</p><p>{4}</p><p>{5}</p><p>{6}</p>{7}{8}</div>'.format( 
            poi.NAME, 
            ( ( poi.STREETPREFIX != '' && !_SELF.isUndefined( poi.STREETPREFIX  ) ) ? poi.STREETPREFIX  + ' ' : '' ), 
            ( ( poi.STREETADDRESS != '' && !_SELF.isUndefined( poi.STREETADDRESS ) ) ? poi.STREETADDRESS + ' ' : '' ), 
            ( ( poi.SUBURB != '' && !_SELF.isUndefined( poi.SUBURB ) ) ? poi.SUBURB + ' ' : '' ),
            poi.STATE + ( ( poi.POSTCODE != '' && !_SELF.isUndefined( poi.POSTCODE ) ) ?  ', ' + poi.POSTCODE + '' : '' ),
            '<br/> ',
            poi.PHONE,
            stockist, 
            rlink
            ));
        
//        mkr.setPopupContent( '<div class="popup-content"><h6 style="border-width:0px;background: url(./images/homypedlogosmall.jpg) no-repeat 0% 50%;vertical-align:bottom;font-size:12px;height:19px; font-weight:bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{0}</h6><br/><p><b>Address: </b>{1}{2}{3}{4}{5}</p><br/><p><b>&nbsp;&nbsp;&nbsp;&nbsp;Phone: </b>{6}</p>{7}{8}{9}</div>'.format( 
//            poi.NAME, 
//            ( ( poi.STREETPREFIX != '' && !_SELF.isUndefined( poi.STREETPREFIX  ) ) ? poi.STREETPREFIX  + '<br> ' : '' ), 
//            ( ( poi.STREETADDRESS != '' && !_SELF.isUndefined( poi.STREETADDRESS ) ) ? spc + poi.STREETADDRESS + ' ' : '' ), 
//            ( ( poi.SUBURB != '' && !_SELF.isUndefined( poi.SUBURB ) ) ? poi.SUBURB + '<br> ' : '' ),
//             spc + poi.STATE + ' ' ,
//            ( ( poi.POSTCODE != '' && !_SELF.isUndefined( poi.POSTCODE ) ) ?  poi.POSTCODE + '' : '' ),
//            poi.PHONE,
//            stockist, 
//            rlink,
//            terr ));
            
//        $('<img class="drivingLink" src="./images/CarIcon.gif" height="20px" width="20px" alt="Driving Directions" title="Driving Directions" />').click( 
//            this.createRouteCallback(poi,null,id)
//        ).appendTo(
//            $( '<li style="margin-top:0.4em;padding-bottom:0.4em;border-bottom:solid 1px #6f84c5;background: url(./images/markers/{0}.png) no-repeat 1px 3px;" >'.format( id ) ).html( '<h6>&nbsp;&nbsp;&nbsp;{0}</h6><p>&nbsp;&nbsp;&nbsp;{1}{2}{3}{4}</p></li>'.format( 
//                poi.NAME,
//                ( ( poi.STREET_ADD != '' && !_SELF.isUndefined( poi.STREET_ADD ) ) ? poi.STREET_ADD + ', ' : '' ), 
//                ( ( poi.SUBURB != '' && !_SELF.isUndefined( poi.SUBURB ) ) ? poi.SUBURB + ', ' : '' ),
//                ( ( poi.POSTCODE != '' && !_SELF.isUndefined( poi.POSTCODE ) ) ? poi.POSTCODE + ', ' : '' ),
//                poi.STATE 
//            ) ).click( function() { _SELF.map.setCentre( mkr.getPoint(), _SELF.map.getZoom() ); mkr.showPopup(); } ).appendTo( this.getElementID( this.ElementIDs.POIResults ) )
//        );
        return mkr;        
        //this.map.addMarker( mkr );
   },
    
    processPOIResult: function(id,poi,mkr) {
    var _SELF = this;
    $('<img class="drivingLink" src="./images/CarIcon.gif" height="20px" width="20px" alt="Driving Directions" title="Driving Directions" />').click( 
            this.createRouteCallback(poi,null,id)
        ).appendTo(
            $( '<li style="margin-top:0.4em;padding-bottom:0.4em;border-bottom:solid 1px #6f84c5;background: url(./images/markers/{0}.png) no-repeat 1px 3px;" >'.format( id ) ).html( '<h6>&nbsp;&nbsp;&nbsp;{0}</h6><p>&nbsp;&nbsp;&nbsp;{1}{2}{3}{4}</p></li>'.format( 
                poi.NAME,
                ( ( poi.STREET_ADD != '' && !_SELF.isUndefined( poi.STREET_ADD ) ) ? poi.STREET_ADD + ', ' : '' ), 
                ( ( poi.SUBURB != '' && !_SELF.isUndefined( poi.SUBURB ) ) ? poi.SUBURB + ', ' : '' ),
                ( ( poi.POSTCODE != '' && !_SELF.isUndefined( poi.POSTCODE ) ) ? poi.POSTCODE + ', ' : '' ),
                poi.STATE 
            ) ).click( function() { _SELF.map.setCentre( mkr.getPoint(), _SELF.map.getZoom() ); mkr.showPopup(); } ).appendTo( this.getElementID( this.ElementIDs.POIResults ) )
        );
    },    
    
    /// This method is called from within findNearest. The purpose of this function is to allow
    /// for control of how the searched location marker is created and how the extra information is displayed.
    processLocationPOI: function(poi) {
    
        var size = new MapDS.Size( 20, 20 );  
        var offset = new MapDS.Pixel( -10, -10 );  
        var icn = new MapDS.Icon( './images/tear-drop-location.png', size, offset );  
        var mkr = new MapDS.Marker( new MapDS.LatLng( poi.Position.Latitude, poi.Position.Longitude ), icn );
        mkr.setPopupContent( '<div class="popup-content"><h5>Your searched location</h5><p>{0}{1}{2}{3}</p></div>'.format( 
                ( poi.Street!='' ? poi.Street + ', ' : '' ), 
                ( poi.Suburb!='' ? poi.Suburb + ', ' : '' ),
                ( poi.Postcode!='' ? poi.Postcode + ', ' : '' ),
                poi.State 
            ) 
        );
        this.map.addMarker( mkr );
        
        // DO NOT REMOVE THE FOLLOWING LINE - It is needed for routing purposes.
        this.locationMarker = mkr;
    },
    
    /// This method is called from within createRouteCallback. It is the helper method for generating the caption that will appear on
    /// the driving directions listing for the point.
    buildRouteCaption: function(poi) {
        return poi.NAME;
    },
    
    /// This method is called from within createRouteCallback. It is the helper method for generating the content that will appear 
    /// in the popup for the route end point.
    buildRouteEndPopupCaption: function(poi) {
        return '<div class="popup-content"><h5>Destination</h5><p>{0}</p></div>'.format( poi.NAME );
    },    
    
    
/// CODE BELOW THIS LINE SHOULD NOT NEED TO BE CHANGED!!


    map: null, // object that contains the QuickMap instance
    RouteMode: 1, // eventually this will be a setting that can change therefore it will move to the top of the script file.
    locationMarker: null,
    
    _isSidebarVisible: false, // private
    _foundLocations: null, // private
    _lastSearchedLocation: null, // private
    _pois: Array(), // private
    _lastFieldFocussed: null, // private, the last search field to have focus
    
    /**
    * Returns true if position is a valid sidebar position
    */
    isValidSidebarPosition: function(position) {
        if ( position == this.SidebarPositions.Left || 
                position == this.SidebarPositions.Bottom || 
                position == this.SidebarPositions.Right || 
                position == this.SidebarPositions.Top ) {
            return true;
        }
        return false;
    },
    
    /**
    * Returns true if object is undefined
    */
    isUndefined: function(object) {
        return typeof object === "undefined";
    },
    
    /**
     * Private method
     * Returns true if object is an Image
     * IE doesn't support "instanceof Image" so instead we test that the object is in the DOM then test the tagName is 'IMG'
     */
    isImage: function(object) {
        return this.isHTMLElement( object, 'IMG' );
    },

    /**
     * Private method
     * Returns true if object is an HTML Element
     * {type} is a string that represents the type of HTML element that the object should be.
     *      for example. if [type] is "div" then the element should be a div.
     */
    isHTMLElement: function(object, type) {
        if ( !type || !( typeof type === "string" ) ) {
            return false;
        }
        if ( object && object.nodeType ) {
            return (object.tagName===type.toUpperCase() ? true : false);
        }
        return false;
    },
    
    /**
    * Returns the id string asked for, will prepend # unless the second parameter is false.
    */
    getElementID: function( id, withHash ) {
        if( this.isUndefined( withHash ) ) {
            withHash = true;
        }
        return ( withHash ? "#" : "" ) + id;
    },

    /**
    * Will fire a call to load up the exception dialog, the HTML will be set using the message parameter.
    */
    displayException: function( errorMessage ) {
        $( this.getElementID( this.ElementIDs.ExceptionDialog ) ).html( errorMessage ).dialog( 'open' );
    },
    
            /*    if ( this._lastFieldFocussed ) {
                this._lastFieldFocussed.focus();
            }*/
    
    /**
    * Will save a POI as the last location.
    */
    saveLocationPOI: function(poi) {
        this._lastSearchedLocation = poi;
    },
    
    /**
     * Private method
     * Assumption is made that at least the first parameter exists and that it is a number. 
     * The same number assumption is made for precision if it is given.
     * Default for precision is 1, therefore no change.
     */
    round: function(val, precision) {
        precision = (arguments.length>1) ? Math.pow(10, arguments[1]) : 1;
        return Math.round( val * precision ) / precision;
    },
    
    /**
    * Will return the full bearing from the shortened string.
    */
    expandBearing: function(bearing) {
        switch(bearing) {
            case 'N':
                return 'North';
                break;
            case 'S':
                return 'South';
                break;
            case 'E':
                return 'East';
                break;
            case 'W':
                return 'West';
                break;
            case 'NE':
                return 'North East';
                break;
            case 'NW':
                return 'North West';
                break;
            case 'SE':
                return 'South East';
                break;
            case 'SW':
                return 'South West';
                break;
            default:
                return '';
        }
    },
    
    /**
     * Will convert seconds to a formatted time string
     */
    convertToTime: function(seconds) {
        
        var hrs = Math.floor(seconds/3600);
        var mins = Math.floor(seconds / 60) - (hrs * 60);
        var secs = seconds - ( (hrs * 3600) + (mins * 60) );
        var msg = 'about ';
        
        if (hrs>0) {
            msg = hrs + 'hrs ';
        }
        
        if (mins>0) {
            msg += mins + 'mins ';
        }
        
        if(hrs==0 && mins==0) {
            msg = 'less than 1 minute';
        }
        
        return msg;
        
    },
    
    /**
    * Resets everything! 
    */
    reset: function() {
        this._foundLocations = null;
        this._lastSearchedLocation = null;
        this.locationMarker = null;
        this._pois = new Array();
        
        this.resetSearchFields();
        
        if ( $( this.getElementID( this.ElementIDs.AdvancedSearch ) ).is(":visible") ) {
            $( this.getElementID( this.ElementIDs.AdvancedSearch ) ).slideUp();
        }
        
        $( this.getElementID( this.ElementIDs.POIResults ) ).html('');
        $( this.getElementID( this.ElementIDs.DrivingDirections ) ).html('');
        
        if( this._isSidebarVisible ) {
            
            var newWidth = $( this.getElementID( this.ElementIDs.Map ) ).width() + ( $( this.getElementID( this.ElementIDs.Sidebar ) ).width() + 5 )
            
            $( this.getElementID( this.ElementIDs.Sidebar ) ).hide( "slide", { direction: "left" }, 1000 );

            var _SELF = this;
            setTimeout( 
                function() { 
                    $( _SELF.getElementID( _SELF.ElementIDs.Map ) ).animate( { width: newWidth }, { queue: true, duration: 1000 } ).width( newWidth ); 
                    // reset map window.
                    _SELF.map.clearMarkers();
                    _SELF.map.clearRoutes(false);
                    _SELF.map.setCentre( _SELF.InitialCentre, _SELF.InitialZoom );
                }, 500 );
            
            this._isSidebarVisible = false;
        }
    },
    
    /**
    * Performs a call to geocode in order to geocode an address. 
    */
    findAddress: function() {
        
        var areSearchFieldsValid = this.validateSearchFields();
        if(!areSearchFieldsValid) {
            //Boxy.alert( this.Strings.InvalidSearchArguments );
            //this.displayException(this.Strings.InvalidSearchArguments);
            return;
        }
        
        $( this.getElementID( this.ElementIDs.DrivingDirections ) ).hide( "slow" );
        $( this.getElementID( this.ElementIDs.POIResults ) ).show( "slow" );
        $( this.getElementID( this.ElementIDs.ItemsFound ) ).show( "slow" );
        this.map.clearRoutes(false);
        this.map.clearMarkers();
        
        $( this.getElementID( this.ElementIDs.Sidebar ) ).removeClass('scrollContent');
        $( this.getElementID( this.ElementIDs.ItemsFound ) ).html( this.Strings.Searching );
        $( this.getElementID( this.ElementIDs.POIResults ) ).html("");
        
        // lock down the find button
        $( this.getElementID( this.ElementIDs.SearchButton ) ).attr('disabled', 'true');
        
        var _SELF = this;
        this.geocode(this.buildAddressSearch(), function(address){_SELF.findNearest(address);}  );
    },
    
    /**
    * Calls FindNearest.ashx passing X/Y and search filters. Results are displayed.
    */
    findNearest: function(address) {
        //Logger.debug(address);
        
        var _SELF = this;
        
        var requestAdvancedSearch = $( this.getElementID( this.ElementIDs.AdvancedSearchTrigger ) ); 
        var AdvancedSearch = "";
                
        if (requestAdvancedSearch.attr("checked") == '') 
        
            {
            //if ($( this.getElementID( this.ElementIDs.StoreAll ) ).attr("checked"))
            //{
            //storetype = $( this.getElementID( this.ElementIDs.StoreAll ) )
            //}
            //else
            //{
            //storetype = $( this.getElementID( this.ElementIDs.StoreOW ) )
            //}
         //storetype = $( this.getElementID( this.ElementIDs.StoreType ) );
         }        
        else
        {AdvancedSearch = requestAdvancedSearch.attr("value");}
        
        
        
        
        $.getJSON( "FindNearest.ashx?x={0}&y={1}&adv={2}".format( address.Position.Longitude, address.Position.Latitude, AdvancedSearch ), 
            this.getSearchFiltersJSON(),
            function(data) {
                
                // unlock down the find button
                $( _SELF.getElementID( _SELF.ElementIDs.SearchButton ) ).attr('disabled', '');
                
                if ( data.Code > 0 ) {
                    _SELF.displayException.apply( _SELF, [ data.Message ] );
                    return;
                } else {
                    var searchedAddress = new String( 
                        ( address.Suburb!='' ? address.Suburb.toProperCase() + ', ' : '' ) + 
                        ( address.State!='' ? address.State + ', ' : '' ) + 
                        ( address.Postcode!='' ? address.Postcode + ', ' : '' ) 
                    );
                    searchedAddress = searchedAddress.substr( 0, searchedAddress.lastIndexOf( ', ' ) );
                    $( _SELF.getElementID( _SELF.ElementIDs.ItemsFound ) ).html( 
                        _SELF.Strings.ItemsFound.format( data.items.length, ( data.items.length>1 ? 'es' : '' ) ) + ' for ' + searchedAddress
                    );
                    
                    _SELF._pois = new Array();
                    _SELF.map.clearMarkers();
                    
                    $.each(data.items, function(i,poi){
                        var id = i+1;
                        _SELF._pois.push( poi );
                        _SELF.processPOI( id, poi )
                    });
                    
                    // code to add alt class to list items. This needs to be called when returning from a route listing
                    /*$( _SELF.getElementID( _SELF.ElementIDs.POIResults + ' li' ) ).each(
                        function(i,li) {
                            if(i % 2 != 0) {
                                $( li ).addClass('alt');
                            }
                        }
                    );*/
                    
                    // for now show results & resize map..
                    //$( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).attr("style","display:block");
                    //$( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).show( "slide", { direction: "left" }, 1000 );
                    
                    if(!_SELF._isSidebarVisible) {                        
                        /*$( _SELF.getElementID( _SELF.ElementIDs.Map ) ).width( 
                            (
                                $( _SELF.getElementID( _SELF.ElementIDs.Map ) ).width() - 
                                ( $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).width() + 5 )
                            )
                        );*/
                        
                        var newWidth = $( _SELF.getElementID( _SELF.ElementIDs.Map ) ).width() - 
                                ( $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).width() + 5 );
                        
                        $( _SELF.getElementID( _SELF.ElementIDs.Map ) ).animate( { width: newWidth }, { queue: true, duration: 1000 } ).width( newWidth );
                        
                        setTimeout( 
                            function() { 
                                $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).show( "slide", { direction: "left" }, 1000, function() {
                                    if ( $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).attr('offsetHeight') + $( _SELF.getElementID( _SELF.ElementIDs.ItemsFound ) ).attr('offsetHeight') > 
                                            $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).attr('offsetHeight') ) {
                                        // add scroll...
                                        $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).addClass('scrollContent');
                                    }
                                });
                            }, 1000 );
                        
                        _SELF._isSidebarVisible = true;
                    } else {
                        $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).show( "slide", { direction: "left" }, 1000, function() {
                            if ( $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).attr('offsetHeight') + $( _SELF.getElementID( _SELF.ElementIDs.ItemsFound ) ).attr('offsetHeight') > 
                                    $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).attr('offsetHeight') ) {
                                // add scroll...
                                $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).addClass('scrollContent');
                            }
                        });
                    }
                    
                    _SELF.saveLocationPOI.apply( _SELF, [address] );
                    _SELF.processLocationPOI( address );
                    
                    _SELF.map.loadBestView( new MapDS.LatLng( address.Position.Latitude, address.Position.Longitude ) );
                    
                }
            }
        );
    },
    
    /**
    * Performs a call to Geocode an address. If a single match is found then the method 
    * will automatically call findNearest. Multiple matches are displayed within a 
    * dialog for the user to select.
    */
    geocode: function(addressToGeocode, loadAddressCallBack) {
        
        // make jQuery Ajax call...
        var _SELF = this;
        $.getJSON("Geocode.ashx",
            addressToGeocode,
            function(data){
                //Logger.log(data.addresses.length);
                if ( data.Code != '1' && data.Code != '100' ) {
                    // unlock down the find button
                    $( _SELF.getElementID( _SELF.ElementIDs.SearchButton ) ).attr('disabled', '');
                    
                    _SELF.displayException.apply( _SELF, [ data.Message ] );
                    return;
                } else {
                    
                    _SELF._foundLocations = data.Locations;
                    
                    // need to display selection if multiple
                    if(_SELF._foundLocations.length>1) {
                        // display modal window?
                        
                        $( _SELF.getElementID( _SELF.ElementIDs.MultipleAddressDialog ) ).find( 
                                _SELF.getElementID( _SELF.ElementIDs.MultipleAddresses ) ).html('');
                        
                        $("<option/>").attr('value','0').html( _SELF.Strings.MultipleAddressPrompt ).appendTo(
                                $( _SELF.getElementID( _SELF.ElementIDs.MultipleAddressDialog ) ).find( 
                                _SELF.getElementID( _SELF.ElementIDs.MultipleAddresses ) ));
                        
                        $.each(_SELF._foundLocations, function(i,address){
                            $("<option/>").html( ( address.Street!='' ? address.Street + ', ' : '' ) + 
                                address.Suburb + ', ' + address.State + ', ' + address.Postcode ).appendTo(
                                $( _SELF.getElementID( _SELF.ElementIDs.MultipleAddressDialog ) ).find( 
                                _SELF.getElementID( _SELF.ElementIDs.MultipleAddresses ) ));
                        });
                        
                        // unlock down the find button
                        $( _SELF.getElementID( _SELF.ElementIDs.SearchButton ) ).attr('disabled', '');
                        
                        $( _SELF.getElementID( _SELF.ElementIDs.MultipleAddressDialog ) ).dialog('open');
                        
                    } else {
                        loadAddressCallBack(_SELF._foundLocations[0]);
                    }
                }
            }
        );
    },
    
    /**
    * Creates a callback function that each marker will have in order to fire a route build request.
    */
    createRouteCallback: function(poi, startMarker,id) {
        var _SELF = this;
        if ( this.RouteMode == 1 ) {
            return ( function() { 
                        try {
                            var listCaption = new String( 
                                ( _SELF._lastSearchedLocation.Suburb!='' ? _SELF._lastSearchedLocation.Suburb.toProperCase() + ', ' : '' ) + 
                                ( _SELF._lastSearchedLocation.State!='' ? _SELF._lastSearchedLocation.State + ', ' : '' ) + 
                                ( _SELF._lastSearchedLocation.Postcode!='' ? _SELF._lastSearchedLocation.Postcode + ', ' : '' ) 
                            );
                            listCaption = listCaption.substr( 0, listCaption.lastIndexOf( ', ' ) );
                            
                            //var endMarker = new MapDS.Marker( new MapDS.LatLng( poi.Position.Latitude, poi.Position.Longitude ), 
                            //    MapDS.MarkerOptions.IconType.RouteEnd );
                            //endMarker.setPopupContent( _SELF.buildRouteEndPopupCaption(poi) );
                            var withDDLink = false;
                            var endMarker =_SELF.processPOIMarker( id, poi,withDDLink )
                            
                            _SELF.buildRoute({
                                from: { caption: listCaption, position: _SELF._lastSearchedLocation.Position }, 
                                to: { caption: _SELF.buildRouteCaption(poi), position: poi.Position }
                            }, startMarker, endMarker, this ); 
                        } catch(ex) {
                            //Logger.debug(ex);
                        }
                        return false; 
                    });
        } else {
            // TODO: slot in code here for allowing custom starting address.
        }
        
    },
    
    
    /**
    * This method calls for a route to be built. The result is handled and a lot of processing occurs here in order to reformat the returned 
    * route information into a nice list.
    */
    buildRoute: function(routeRequest, startMarker, endMarker, srcImageLink) {
        var _SELF = this;
        
        if ( typeof routeRequest != "object" ) {
            throw "Invalid route request.";
        }
        if ( !( startMarker instanceof MapDS.Marker ) ) {
            if ( !( _SELF.locationMarker instanceof MapDS.Marker ) ) {
                throw "Invalid start marker.";
            }
            startMarker = _SELF.locationMarker;
        }
        if ( !( endMarker instanceof MapDS.Marker ) ) {
            throw "Invalid end marker.";
        }
        
        if( srcImageLink && _SELF.isImage( srcImageLink ) ) {
            // change icon image to loading image
            srcImageLink.src = _SELF.Images.Loading;
        }
        
        // possibly need to look at generic approach here - use wp instead of s/e
        $.getJSON("RouteBuilder.ashx?s={0},{1}&e={2},{3}".format(
                routeRequest.from.position.Latitude, 
                routeRequest.from.position.Longitude, 
                routeRequest.to.position.Latitude, 
                routeRequest.to.position.Longitude
            ), 
            function(data) {
                //Logger.debug(o);
                $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) ).html('');
                
                $('<a class="back"/>').html('Back to results').click( function(){
                    $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) ).hide( "slow" );
                    $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).show( "slow" );
                    $( _SELF.getElementID( _SELF.ElementIDs.ItemsFound ) ).show( "slow" );
                    $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).removeClass('scrollContent');
                    _SELF.map.removeRoute(myRoute, false);
                    _SELF.map.clearMarkers();
                    $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).html('');
                    $.each(_SELF._pois, function(i,poi){
                        var id = i+1;
                        _SELF.processPOI( id, poi )
                    });
                    _SELF.processLocationPOI( _SELF._lastSearchedLocation );
                    _SELF.map.loadBestView(null,true);
                }).appendTo( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) );
                
                
                
                $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).hide( "slow" );
                $( _SELF.getElementID( _SELF.ElementIDs.ItemsFound ) ).hide( "slow" );
                
                var dd = $('<div></div>');
                
                $('<h5/>').html( "Start: <strong>{0}</strong>".format( routeRequest.from.caption ) ).appendTo(dd);
                
                
                $.each(data.Segments, function(i,segment){
                    var segmentTable = $('<table></table>');
                    $.each(segment.Directions, function(i){
                        // scope of this method is the direction itself.
                        //Logger.debug(this);
                        var instruction;
                        var distance = _SELF.round(this.Distance, 2) + 'km';
                        if(i==0) { // start
                            instruction = 'Head <strong>{0}</strong> on <strong>{1}</strong>'.format( 
                                (_SELF.expandBearing(this.Bearing)).toLowerCase(), 
                                this.Address.Street 
                            );
                        } else if (this.Instruction.toLowerCase() == 'continues as') {
                            instruction = 'Continue along <strong>{0}</strong>'.format( this.Address.Street );
                        } else if (this.Instruction.toLowerCase() == 'finish on') {
                            instruction = this.Instruction+' <strong>{0}</strong>'.format( this.Address.Street );
                            distance = '';
                        } else {
                            instruction = '{0} at <strong>{1}</strong>'.format(
                                this.Instruction.replace(/left/i,'<strong>left</strong>').replace(/right/i,'<strong>right</strong>'),
                                this.Address.Street
                            );
                        }
                        // NOTE: something here to be done for distances under 100 metres...
                        $( '<tr><td>{0}.</td><td>{1}</td><td>{2}</td></tr>'.format( (i+1), instruction, distance ) ).appendTo(segmentTable);
                    });
                    
                    $( '<tr class="segmentTotal"><td colspan="3">{0}km - {1}</td></tr>'.format(
                        _SELF.round(segment.Distance, 2),
                        _SELF.convertToTime.apply(_SELF,[segment.DrivingTime])
                    )).appendTo(segmentTable);
                    segmentTable.appendTo(dd);
                });
                
                $('<h5/>').html( "End: <strong>{0}</strong>".format( routeRequest.to.caption ) ).appendTo(dd);
                
                $('<h3>Driving Directions</h3>').appendTo( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) );
                
                dd.appendTo( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) )
                
                // build the route object
                var myRoute = new MapDS.Route( null, data.MapFileName, new MapDS.Bounds( data.Bounds.Left, data.Bounds.Bottom, data.Bounds.Right, data.Bounds.Top ) );
                
                $('<a class="back"/>').html('Back to results').click( function(){
                    $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) ).hide( "slow" );
                    $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).show( "slow" );
                    $( _SELF.getElementID( _SELF.ElementIDs.ItemsFound ) ).show( "slow" );
                    $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).removeClass('scrollContent');
                    _SELF.map.removeRoute(myRoute, false);
                    _SELF.map.clearMarkers();
                    $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).html('');
                    $.each(_SELF._pois, function(i,poi){
                        var id = i+1;
                        _SELF.processPOI( id, poi )
                    });
                    _SELF.processLocationPOI( _SELF._lastSearchedLocation );
                    _SELF.map.loadBestView(null,true);
                }).appendTo( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) );
                
                $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) ).show( "slow", function() {
                    if ( $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) ).attr('offsetHeight') > $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).attr('offsetHeight') ) {
                        // add scroll...
                        $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).addClass('scrollContent');
                    }
                } );
                
                _SELF.map.clearMarkers();
                
                // TODO: add back the route points...
                _SELF.map.addMarker(startMarker);
                _SELF.map.addMarker(endMarker);
                
                // add route to map
                _SELF.map.addRoute( myRoute );
                _SELF.map.loadBestView(null,true);
            }
        );
    },
    
    /**
    * This method is hooked into the window.onload automatically.
    */ 
    initMap: function( lat, lng, zoom ) {
        this.map = new MapDS.Map( this.getElementID( this.ElementIDs.Map, false ) );
        this.map.setCentre( this.InitialCentre, this.InitialZoom );
    },
    
    /*function alert(msg) {
        $('#alert').jqmShow().find('div.jqmAlertContent').html(msg);
    }*/
    
    /**
    * ?.
    */
    showDirections: function(id)
    {
        var createRoute = this.createRouteCallback(this._pois[id-1],null,id);
        createRoute();
        //this.createRouteCallback(this._pois[id-1],null);
    },
    
    init: function(options) {
        var _SELF = this;
        $().ready(function() {
            //$('#alert').jqm({overlay: 0, modal: true, trigger: false});
            
            // Grab all anchor elements on the page that have an href and target is external. Set their target to blank.
            $( 'a[rel="external"]' ).filter( '[href!=""]' ).each( function() { this.target = "_blank"; } );
            
            $( _SELF.getElementID( _SELF.ElementIDs.SearchButton ) ).click( function() { Locator.findAddress(); } );
            
            $( _SELF.getElementID( _SELF.ElementIDs.ResetButton ) ).click( function() { Locator.reset(); } );
            
            // hook up the HTML elements...
            
            $( _SELF.getElementID( _SELF.ElementIDs.AdvancedSearchTrigger ) ).click(
                function() {
                    if ( $( _SELF.getElementID( _SELF.ElementIDs.AdvancedSearch ) ).is(":visible") ) {
                        $( _SELF.getElementID( _SELF.ElementIDs.AdvancedSearch ) ).slideUp();
                    } else {
                        $( _SELF.getElementID( _SELF.ElementIDs.AdvancedSearch ) ).slideDown();
                    }
                }
            );
            
            // need to have the current text blanked on focus, reset on blur...
            $( _SELF.getElementID( _SELF.ElementIDs.Search ) + ' input[title!=""]').keyup(
                function(evt) {
                    _SELF._lastFieldFocussed = this;
                    if (evt.keyCode == 13 ) {
                        $( _SELF.getElementID( _SELF.ElementIDs.SearchButton ) ).click();
                    }
                }
            ).hint();
            
            
            // attach the exception dialog
            $( _SELF.getElementID( _SELF.ElementIDs.ExceptionDialog ) ).dialog({
                bgiframe: true,
                modal: true,
                draggable: false,
                resizable: false,
                autoOpen: false,
                close: function( evt, ui ) {
                        if( _SELF._lastFieldFocussed ) {
                            _SELF._lastFieldFocussed.focus();
                        }
                    },
                buttons: {
                    Ok: function() {
                        $(this).dialog('close');
                    }
                }
            });
            
            // attach the multiple results dialog.
		    $( _SELF.getElementID( _SELF.ElementIDs.MultipleAddressDialog ) ).dialog({
			    bgiframe: true,
			    height: 300,
			    modal: true,
                draggable: false,
                resizable: false,
                autoOpen: false,
//                close: function( evt, ui ) {
//                        var idx = $(this).find( _SELF.getElementID( _SELF.ElementIDs.MultipleAddresses ) ).attr('selectedIndex');
//                        if (idx!=0) {
//                            // go back one to get correct array location.
//                            idx--;
//                            // Call FindNearest....
//                            _SELF.findNearest(_SELF._foundLocations[idx]);
//                        }
//                    },
			    buttons: {
				    'Use Address': function() {
				        var idx = $(this).find( _SELF.getElementID( _SELF.ElementIDs.MultipleAddresses ) ).attr('selectedIndex');
                        if (idx!=0) {
                            // go back one to get correct array location.
                            idx--;
                            // Call FindNearest....
                            _SELF.findNearest(_SELF._foundLocations[idx]);
                        }				    
					    $(this).dialog('close');
				    }
			    }
		    });
		    
		    _SELF.reset();
		    
		    // load up options
		    if ( options ) {
		        
		    }
		    
		    
            // need to wrap the call in a function so we can maintain scope
            window.onload = function() { _SELF.initMap.apply(_SELF); };
            
        });
    }
    
};

Locator.init(  );

