﻿//Author:   Lorraine.Kilby
//Created:  3/11/2009 1:35:19 PM

var states= "State,ACT,NSW,NT,SA,TAS,VIC,WA,QLD";
var regions = "Region,Auckland,Bay of Plenty,Canterbury,Gisborne,Hawkes Bay,Manawatu-Wanganui,Marlborough,Nelson,Northland,Otago,Southland,Taranaki,Tasman,Waikato,Wellington,West Coast";
var country;
var mkrAu;
var mkrNz;
var mkrs = new Array();
var POIs = new Array();
var savedState = "";
var listCaption;

var p = new Object();
p.NAME = 'Australia (Head Office)';
p.COMPANY_NAME = 'MapData Sciences Pty Ltd';
p.ABN = '(ABN 45 053 437 282)';
p.STREET_ADD = '110 Pacific Highway';
p.SUBURB = 'Greenwich';
p.STATE = 'NSW';
p.POSTCODE = '2065';
p.COUNTRY = 'Australia';
p.PHONE = '+61 (2) 8436-2800';
p.FAX = '+61 (2) 8436-2888';
p.EMAIL = 'info@mapds.com.au';
p.Position = new Object();
p.Position.Latitude = -33.825132308272714;
p.Position.Longitude = 151.19038492441177;
POIs[0] = p;
//p = new Object();
//p.NAME = 'New Zealand';
//p.COMPANY_NAME = 'MapData Sciences (NZ) Ltd';
//p.ABN = '(IRD: 73 888 514)';
//p.STREET_ADD = '49 Boulcott St';
//p.SUBURB = 'Wellington';
//p.COUNTRY = 'New Zealand';
//p.PHONE = '+64 (4) 472-8188';
//p.FAX = '+64 (4) 472-8048';
//p.EMAIL = 'info@mapds.co.nz';
//p.Position = new Object();
//p.Position.Latitude = -41.287417;
//p.Position.Longitude = 174.774070;
//POIs[1] = p;
p = null;

var Locator = {

    InitialZoom: 3,
    InitialCentre: new MapDS.LatLng(-33.866774, 149 ),
    
    Strings: {
        InvalidSearch: 'The required search fields have not been entered',
        InvalidSuburb: 'The value of Suburb is not valid',
        InvalidPostcode: 'The value of Postcode is not valid',
        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',
        DrivingDirectionsDialog: 'drivingDirectionsDialog',
        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',
        StateOptions: 'ddlState',
        startStreet:'startStreet',
        startSuburb:'startSuburb',
        startState:'startState',
        startPostcode:'startPostcode'
        
    },
    
    /// 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 haveValidStreet = false;
        var haveValidSuburb = false;
        var haveValidPostcode = 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() ) {
            haveValidStreet = true;
            }
        
        if ( txtSuburb.attr("value") != '' && txtSuburb.attr("value").toLowerCase() != txtSuburb.attr("title").toLowerCase() ) {
            haveValidSuburb = true;
        } else if ( txtPostcode.attr("value") != '' && txtPostcode.attr("value").toLowerCase() != txtPostcode.attr("title").toLowerCase() ) {
            haveValidPostcode = true;
        }
        if ( !haveValidSuburb && !haveValidPostcode && !haveValidStreet ) {
            this.displayException( this.Strings.InvalidSearch );       
          return false;
        }
        
        
        else if ( !haveValidSuburb && !haveValidPostcode ) {
            this.displayException( this.Strings.InvalidSearch );
          
            return false;
        } 
        
        return true;
    },
    
    /// 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:
    ///     str = street
    ///     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").val(); 
           
        }
 
         
        if (state == null){
        
        state = savedState;
        
        }
        else {
        
        if (state != ''){
        
        savedState = state;
        }
        }  
        
   
  
        if (Locator.drivingLocationID == 0){
         country = 'au';
        }
        else{
          country = 'nz';
        }
        
        return { 
            sb: suburb, 
            st: state, 
            pc: postcode,
            str:street,
            co: country
        };
    },
    
    /// 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) {
        //console.debug(poi);
        var _SELF = this;
        var size = new MapDS.Size( 25, 35 );  
        var offset = new MapDS.Pixel( -12.5, -35 );  
        var icn = new MapDS.Icon( 'images/{0}.png'.format( id ), size, offset );  
       
       
        mkrs[id] = new MapDS.Marker( new MapDS.LatLng( poi.Position.Latitude, poi.Position.Longitude ), icn );
        mkrs[id].setPopupContent( ('<img src="images/logo.png"/></br><br /><div class="popup-content"><b>{0} <br>{1}<br />{2}</b><br />' +
            '<br />{3}<br />{4}{5}{6}<br />{7}<br /><br />' +
            '<table><tr><td><b>Phone:</b></td><td>{8}</td></tr><td><b>Fax:</b></td><td>{9}</td><tr><td><b>Email:</b></td><td><a href="mailto:{10}">{10}</a></td></tr></table><br />' +
            '<a href="javascript:Locator.getdrivingdirections('+id+');">Driving Directions</a> ' + 
            '&nbsp;&nbsp;&nbsp;<a href="javascript:Locator.setmapcentre({11},{12},{13});" >Zoom in</a></div>').format( 
                poi.NAME,
                poi.COMPANY_NAME,
                poi.ABN,
                poi.STREET_ADD, 
                poi.SUBURB,
                ( ( poi.STATE != '' && !_SELF.isUndefined( poi.STATE ) ) ? ', ' + poi.STATE + ' ' : '' ),
                ( ( poi.POSTCODE != '' && !_SELF.isUndefined( poi.POSTCODE ) ) ? poi.POSTCODE + ' ' : '' ),
                poi.COUNTRY,
                poi.PHONE,
                poi.FAX,
                poi.EMAIL,
                poi.Position.Latitude,
                poi.Position.Longitude,
                16
            ) );
     
        this.map.addMarker( mkrs[id]);
 
    },
    
    /// 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 mkr = new MapDS.Marker( new MapDS.LatLng( poi.Position.Latitude, poi.Position.Longitude ), MapDS.MarkerOptions.IconType.Location );
        mkr.setPopupContent( '<div class="popup-content"><h5>Your start 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) {
    var caption = 'buildRouteCaption ';
         
    if (poi.STREET_ADD != '' && !this.isUndefined(poi.STREET_ADD)){
        caption = 'street: ' &  poi.STREET_ADD + ' ';
    }
     if (poi.SUBURB!= '' && !this.isUndefined(poi.SUBURB)){
        caption += 'suburb: ' & poi.SUBURB + ' ';
    }
      if (poi.STATE!= '' && !this.isUndefined(poi.STATE)){
        caption += 'state: ' &  poi.STATE + ' ';
    }
        if (poi.POSTCODE!= '' && !this.isUndefined(poi.POSTCODE)){
        caption += poi.POSTCODE;
    }
        
        return caption;
         
    },
    
    /// 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) {
 
  var endCaption = "";
                if (country == 'au'){
                    endCaption = POIs[0].STREET_ADD +  ' ' + POIs[0].SUBURB  + ', ' + POIs[0].STATE + ', ' + POIs[0].POSTCODE;
                
                }else{
                    endCaption = POIs[1].STREET_ADD +  ' ' + POIs[1].SUBURB ;
                }
 
 
     return '<div class="popup-content"><h5>Destination</h5><p>{0}</p></div>'.format( endCaption );
    },
    
    
/// 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' );
    },
    
   
    
    /**
    * 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();
                    _SELF.map.setCentre( _SELF.InitialCentre, _SELF.InitialZoom );
                }, 500 );
            
            this._isSidebarVisible = false;
        }
    },
    
    centerOnIcon: function(lat,lng,i){
   var _SELF = this;
    
    var latlong = new MapDS.LatLng( lat, lng );
     this.map.setCentre( latlong ,_SELF.InitialZoom );
     mkrs[i].showPopup();
    
    },
    
    /**
    * Performs a call to geocode in order to geocode an address. 
    */
    findAddress: function() {
   
        var areSearchFieldsValid = this.validateSearchFields();
        if(!areSearchFieldsValid) {
            
          
            return;
        }
  
        this.map.clearRoutes();
      // this.map.clearMarkers();

        
        var _SELF = this;
        _SELF._pois = new Array();
                   
        $.each(POIs, function(i,poi){
           _SELF._pois.push( poi );
    
        });
        
        
        this.geocode(this.buildAddressSearch(), function(address) {
          
           if (address.Suburb == ''){
            address.Suburb = document.getElementById('txtSuburb').value;
           }
          
            Locator._lastSearchedLocation = address;
          
            
            _SELF.saveLocationPOI.apply( _SELF, [address] );
            _SELF.processLocationPOI( address );
            Locator.createRouteCallback(POIs[Locator.drivingLocationID])();
        }  );
   
 
   },
    
    /**
    * Calls FindNearest.ashx passing X/Y and search filters. Results are displayed.
    */
    findNearest: function(address) {
  
        var _SELF = this;
        $.getJSON( "FindNearest.ashx?x={0}&y={1}".format( address.Position.Longitude, address.Position.Latitude ), 
            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();
              
                    
                    $.each(data.items, function(i,poi){
                        var id = i+1;
                        _SELF._pois.push( poi );
                        _SELF.processPOI( id, poi )
                    });
                    
                    if(!_SELF._isSidebarVisible) {                        
                      
                        
                        var newWidth = $( _SELF.getElementID( _SELF.ElementIDs.Map ) ).width() - 
                                ( $( _SELF.getElementID( _SELF.ElementIDs.Sidebar ) ).width() + 5 );
                        
                        
                        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') ) {
                                
                                    }
                                });
                            }, 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') ) {
            
                            }
                        });
                    }
                    
                    _SELF.saveLocationPOI.apply( _SELF, [address] );
                    _SELF.processLocationPOI( address );
                    
               
                    
                }
            }
        );
    },
    
    /**
    * 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){

                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) {
        var _SELF = Locator;
        if ( this.RouteMode == 1 ) {
            return ( function() { 
                   
                             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) );
                            
                            _SELF.buildRoute({
                                from: { caption: listCaption, position: _SELF._lastSearchedLocation.Position }, 
                                to: { caption: _SELF.buildRouteCaption(poi), position: poi.Position }
                            }, startMarker, endMarker, this ); 
                  
                        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}&co={4}".format(
                routeRequest.from.position.Latitude, 
                routeRequest.from.position.Longitude, 
                routeRequest.to.position.Latitude, 
                routeRequest.to.position.Longitude,
                country 
            ), 
            function(data) {
    
                $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirections ) ).html('');
                
                document.getElementById('POIResults').style.visibility = 'hidden'; 

                $( _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.
                        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);
                });
                
                var endCaption = "";
                if (country == 'au'){
                    endCaption = POIs[0].STREET_ADD +  ' ' + POIs[0].SUBURB  + ', ' + POIs[0].STATE + ', ' + POIs[0].POSTCODE;
                
                }else{
                    endCaption = POIs[1].STREET_ADD +  ' ' + POIs[1].SUBURB ;
                }
                
               
                $('<h5/>').html( "End: <strong>{0}</strong>".format( endCaption ) ).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 href ="javascript:print();"> Print Directions </a><br /><br />').appendTo(dd);
                
                
                $('<a class="back"/>').html('Back').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');
                     document.getElementById('POIResults').style.visibility = 'visible'; 
                    
                    _SELF.map.removeRoute(myRoute);
                    _SELF.map.clearMarkers();
                    $( _SELF.getElementID( _SELF.ElementIDs.POIResults ) ).html('');
                    $.each(_SELF._pois, function(i,poi){
                        _SELF.processPOI( i, poi )
                    });
             
                    _SELF.map.setCentre( _SELF.InitialCentre, _SELF.InitialZoom );
                }).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') ) {
          
                        $( _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 );
       
        var _SELF = this;
        var size = new MapDS.Size( 25, 35 );  
        var offset = new MapDS.Pixel( -12.5, -35 );
        
        for (var i = 0; i < POIs.length; i++) {
            var poi = POIs[i];
            this.processPOI(i,poi);
        } 
      
    },
    
       setmapcentre: function( lat, lng,zoomLevel)
   {
            this.map.setCentre(new MapDS.LatLng(lat, lng), zoomLevel );
   },
     getdrivingdirections: function(id)
   {
   Locator.reset();

   var _SELF = this;
    this.drivingLocationID = id;
    
    //append the state/region options here
     var i;
     var List;
 
    //if Australia poi selected append state options
    if (id == 0){
     List = states.split( ",");
    
    //if New Zealand poi selected append region options
    } else {
       List = regions.split( ",");
    }
    
   
    //clear all existing options from dropdown
       $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirectionsDialog) ).find( 
                                _SELF.getElementID( _SELF.ElementIDs.StateOptions) ).html('');
    
   // loop through all array positions
   for (i=0; i<List.length; i++) 
   {
      $("<option/>").html( List[i]).appendTo(
                                $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirectionsDialog ) ).find( 
                                _SELF.getElementID( _SELF.ElementIDs.StateOptions ) ));
    }

        $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirectionsDialog ) ).dialog('open');
        $( this.getElementID( this.ElementIDs.Search ) + ' input[title!=""]').attr("value","").blur();
    },
    

    init: function(options) {
        var _SELF = this;
        $().ready(function() {
        
        $('#sidebar').show();
        
  
            // 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 ) {
                          Locator.findAddress();
                          $(_SELF.getElementID( _SELF.ElementIDs.DrivingDirectionsDialog ) ).dialog('close');
                    }
                }
            ).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 ) {
                
                try{
                
                        if( _SELF._lastFieldFocussed ) {
                            _SELF._lastFieldFocussed.focus();
                        }
                        
                        }
                     catch (e){
                     } 
                        
               
                       
                        
                          //display dd dialog again
                       if (  Locator.drivingLocationID == 0){
                        Locator.getdrivingdirections(0);
                       }
                       else{
                        Locator.getdrivingdirections(1);
                       }
                        
                     
                       
                    },
                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 ) {
                       
                    },
			    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--;
                           var selectedStreet = _SELF._foundLocations[idx].Street;
                       
                           var selectedSuburb = _SELF._foundLocations[idx].Suburb;
                  
                           var selectedState = _SELF._foundLocations[idx].State;
                           var selectedPostcode = _SELF._foundLocations[idx].Postcode;
      
                           document.getElementById('txtStreet').value = selectedStreet;
                           document.getElementById('txtSuburb').value = selectedSuburb;
                           document.getElementById('ddlState').value = selectedState;
                           document.getElementById('txtPostcode').value = selectedPostcode;
                      }
                   
                         Locator.findAddress();
				    
					    $(this).dialog('close');
				    }
			    }
		    });
		    
		    
		     // attach the driving directions dialog.
		    $( _SELF.getElementID( _SELF.ElementIDs.DrivingDirectionsDialog ) ).dialog({
			    bgiframe: true,
			    height: 150,
			    width:500,
			    modal: true,
                draggable: false,
                resizable: false,
                autoOpen: false,
              
                close: function( evt, ui ) {
                   
                },
               
			    buttons: {
			     
				    'Directions': function() {
				      
				    
				        Locator.findAddress();
					    $(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(  );
