var StoreMap = {};

$(document).ready(function()
{
    StoreMap = {
        // debug mode to help development
        debug : false,
        // map mode is in static or google version
        is_static_map: true,
        // result mode is in store or direction
        is_direct_mode: false,
        // indicator whether user not doing any search or not
        // true means not doing any search
        is_fresh: true,
        search_value: "",
        // particular substore that picked
        selected_substore: {},
        // user address for direction purposes
        user_address: "",
        // element footer height since the safari hates div height so much
        footer_height : 143,
		// set the map height for ie7 bug
		map_height : "500px",
        // element div to put the map
        map_element : "map",
        // localise all the search of the map to be specific in a country
        localise_country : "Australia",
        state_locations: "nsw,vic,qld,sa,wa,tas,nt,act",
        // start location bound for Australia
        start_north : parseFloat(54.5020155),
        start_south : parseFloat(-75.3505903),
        start_east : parseFloat(-95.0920485),
        start_west : parseFloat(2.6423205),
        // start adjustment of start location since we do not have the full panel
        start_point_adj_lat : parseFloat(-20),
        start_point_adj_lng : parseFloat(-10),
        start_adj_zoom : parseInt(2),
        // minimum zoom level for store markers
        min_zoom_store_marker : 4,
		zoom_adjustment: -1,
        // distance search of stores in km
        distance_store_search : 10000,
        // store result number in the panel
        store_result_count : 5,
        // clustering size
        store_cluster_grid_size : 30,
        // enable clustering until specified zoom
        store_cluster_max_zoom : 13,
        filter_path: "data/storefilter.aspx" + "?q=" + Math.floor(Math.random() * 1000),
        type_path: "data/storetype.aspx" + "?q=" + Math.floor(Math.random() * 1000),
        store_path: "data/store.aspx" + "?q=" + Math.floor(Math.random() * 1000),
		default_more_dealer_image: "images/btn-moreDealer.gif",
		alternative_more_dealer_image: "images/btn-moreDealer-black.gif",
        // icon definition for store
        icon_store_highlight_image : "images/locator-on.png",
        icon_store_unhighlight_image : "images/locator-off.png",
        icon_store_shadow_image : "images/locator-shadow.png",
        icon_store_image_size_width : 30,
        icon_store_image_size_height : 34,
        icon_store_shadow_size_width : 30,
        icon_store_shadow_size_height : 34,
        // location of the store pin anchor
        icon_store_anchor_point_top : 34,
        icon_store_anchor_point_left : 12,
        // location of the panel store information
        icon_store_panel_anchor_top : 20,
        icon_store_panel_anchor_left : 14,
        
        // icon definition for store group (rounded)
        icon_stores_image : "images/locator-round-on.png",
        icon_stores_shadow_image : "images/locator-shadow-box.png",
        icon_stores_image_size_width : 40,
        icon_stores_image_size_height : 50,
        icon_stores_text_top : 28,
        icon_stores_anchor_point_top : -50,
        icon_stores_anchor_point_left : -20,
        icon_stores_text_color : "white",
        
		// icon definition for store start
		icon_direction_start_image: "images/start_on.png",
		icon_direction_start_image_size_width : 30,
        icon_direction_start_image_size_height : 34,
        icon_direction_start_anchor_point_top : 34,
        icon_direction_start_anchor_point_left : 12,
        
		// direction panel
		direct_panel_width: "400px",
		direct_panel_height: "160px",
		
		// direction polygon style
		direct_polygon_color: "#000000",
		direct_polygon_weight: 4,
		direct_polygon_opacity: 1,
				
        // polygon for the debugging
        debug_area_polygon : {},
        // position that user in
        user_point : {},
        // list all filter detail
        filters : [],
        // list all type detail
        types: [],
        // list all store detail
        stores : {},
        // stores that is stored in such a way that B# tree conforms
        stores_filters : {},
        // list all substores detail
        substores: [],
        // substores that is stored in such a way that B# tree conforms
        substores_filters : {},
        // Google Maps object
        managers : {
            gmap : {},
            marker : {},
            gdir: {}
        },
        // list all icon definition for markers
        icons : {
            stores : {}
        },
        // list all markers that available in the Google Maps
        markers : {
            user : {},
            stores : [],
            direction_start : {},
            direction_end : {}
        },
        // list all overlay that available in the Google Maps
        overlays : {
            store_panel : {},
            direction: {}
        },
        // custom panel for store detail
        StoreOverlay : function (point, store, substore)
        {
            this.map = {};
            this.point = point;
            this.div = {};
            this.store = store;
            this.substore = substore;
            
            this.prototype = new GOverlay();
            
            // create div panel
            this.initialize = function(map)
            {
                this.map = map;
                
                // get the div container
                this.div = $("<div />");

				// set the value
				var store_result = {
					title : store.title,
					filters_detail : substore.filters_detail,
					street : substore.address,
					suburb : substore.suburb,
					state : substore.state,
					postcode: substore.postcode,
					phone : substore.phone,
					fax: substore.fax,
					operatinghours: substore.operatingHours,
					website: store.website
				};
			
                // prepare the template object
                var template_object = {
                    store : store_result
                };
                
                // put the result
                this.div.setTemplateElement("store_panel_template", null, {filter_data: false});
                this.div.processTemplate(template_object);
                
                // bind the click event to the close button
                $(".close", this.div).click(function()
                {
                	// remove previous overlay that created
                    StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.store_panel);
                });
                
                $(".btn-getdirection", this.div).colorbox(
                {
					width: StoreMap.direct_panel_width,
					height: StoreMap.direct_panel_height,
                	inline: true,
                	href: "#direct-content"
                });
                
                this.div.appendTo(map.getPane(G_MAP_FLOAT_PANE));

                this.redraw(true);
            };
            
            // remove div
            this.remove = function()
            {
                this.div.remove();
            };

            // copy our data to a new instance
            this.copy = function()
            {
                return new StoreMap.StoreOverlay(this.point, this.store);
            };

            // redraw based on the current projection and zoom level
            this.redraw = function(force)
            {
                if (!force) return;

                var point = this.map.fromLatLngToDivPixel(this.point);
                var map_info = $(".map-info", this.div);
                
                // Now position our DIV based on the DIV coordinates of our bounds
                map_info.css(
                {
                    left : (point.x + StoreMap.icon_store_panel_anchor_left) + "px",
                    top : (point.y - (map_info.height()) + StoreMap.icon_store_panel_anchor_top) + "px"
                });
            };
        },
        // init all the control like search
        init_control: function()
        {
            // register the search locator
            $("#locator-search .btn-search").click(function()
            {
            	// check whether the map still using static version or not
				if (StoreMap.is_static_map)
				{
					StoreMap.hide_map_static();
				}
				
                StoreMap.search_on_change($("#locator-search .input-search").attr("value"));
                
                return false;
            });
            
            // register auto enter behaviour
            $("#locator-search .input-search").keypress(function (e)
            {
                if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13))
                {
                    $('#locator-search .btn-search').click();
                    
                    return false;
                }
                else
                {
                    return true;
                }  
            });
            
            // register the search locator
            $("#direct-content .btn-go").click(function()
            {
                StoreMap.direct_on_change($("#direct-content .input-address").attr("value"));
                
                return false;
            });
            
            // register auto enter behaviour
            $("#direct-content .input-address").keypress(function (e)
            {
                if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13))
                {
                    $("#direct-content .btn-go").click();
                    
                    return false;
                }
                else
                {
                    return true;
                }  
            });
			
			// bind the click event to the states label
			$("#states .state").each(function()
			{
				$(this).click(function()
				{
				    // check whether the map still using static version or not
    				if (StoreMap.is_static_map)
    				{
    					StoreMap.hide_map_static();
    				}
    				
					var state = $(this).text();
					$("#locator-search .input-search").attr("value", state);
					StoreMap.search_on_change(state);
				});
			});
        },
        // load static map that shows only states
    	init_map_static: function()
    	{
    		var map_static = $("#map_static");
    		
    		// go through all area that is defined
    		$("map[name='map_static_area'] area").each(function()
    		{
    			var state = $(this).attr("name");
    			
    			$(this).hover(function()
    			{
    				// stored first the orginal src
    				map_static.data("ori_src", map_static.attr("src"));
    				map_static.attr("src", "images/map_" + state + ".gif");
    			},
    			function()
    			{
    				// get back the stored src
    				map_static.attr("src", map_static.data("ori_src"));
    			});
    			
    			$(this).click(function()
    			{
	    			StoreMap.hide_map_static();
    				$("#locator-search .input-search").attr("value", state);
					StoreMap.search_on_change(state);
					
    				return false;
    			});
    		});
    	},
    	hide_map_static: function()
    	{
    		StoreMap.is_static_map = false;
    		$("#map_static").hide();
    	},
        // initialise all manager object including set up start up location
        init_map : function()
        {
            // check if the website Google Maps compatible
            if (GBrowserIsCompatible())
            {
                // create gmap manager object
                StoreMap.managers.gmap = new GMap2(document.getElementById(StoreMap.map_element));

                // check whether we enable the debug mode
                if (StoreMap.debug)
                {
                    // set event handler for map view changes
                    GEvent.addListener(StoreMap.managers.gmap, "click", function(marker, point)
                    {
                        // avoid processing when click the marker
                        if (point != null)
                        {
                            // search surrounding store
                            StoreMap.search_store(point);
                            // remove previous polygon
                            StoreMap.managers.gmap.removeOverlay(StoreMap.debug_area_polygon);
                            // make a new one
                            StoreMap.debug_area_polygon = StoreMap.make_circle_polygon(point, StoreMap.distance_store_search);
                            // and put it into Google Maps
                            StoreMap.managers.gmap.addOverlay(StoreMap.debug_area_polygon);
                        }
                    });
                }
                
                // set event handler for map view changes
                GEvent.addListener(StoreMap.managers.gmap, "zoomend", function()
                {
                	// remove previous overlay that created
                    StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.store_panel);
                });
                
                // enable double click zooming in the map
                StoreMap.managers.gmap.enableDoubleClickZoom();
                // enable to scroll map using mouse wheel
                StoreMap.managers.gmap.enableScrollWheelZoom();
                // enable to have smooth scrolling
                StoreMap.managers.gmap.enableContinuousZoom();
                // set center of the start up
                var start_bound = new GLatLngBounds(new GLatLng(StoreMap.start_south, StoreMap.start_west), new GLatLng(StoreMap.start_north, StoreMap.start_east));
                var start_zoom = StoreMap.managers.gmap.getBoundsZoomLevel(start_bound);
                var start_point = start_bound.getCenter();
                // set center of the start up
                StoreMap.managers.gmap.setCenter(new GLatLng(start_point.lat() + StoreMap.start_point_adj_lat, start_point.lng() + StoreMap.start_point_adj_lng), start_zoom + StoreMap.start_adj_zoom);
                
                // create marker cluster object
                /*StoreMap.managers.marker_clusterer = new MarkerClusterer(StoreMap.managers.gmap, [], {
                    gridSize : StoreMap.store_cluster_grid_size,
                    maxZoom : StoreMap.store_cluster_max_zoom,
                    styles : [{
                        width : StoreMap.icon_stores_image_size_width,
                        height : StoreMap.icon_stores_image_size_height,
                        url : StoreMap.icon_stores_image,
                        opt_textColor : StoreMap.icon_stores_text_color,
                        icon_adjust_left : StoreMap.icon_stores_anchor_point_left,
                        icon_adjust_top : StoreMap.icon_stores_anchor_point_top,
                        addon_css : "line-height: " + StoreMap.icon_stores_text_top + "px"
                    }]
                });*/
				
				StoreMap.managers.marker_clusterer = new MarkerManager(StoreMap.managers.gmap);
				
                // create GClientGeocoder object
                StoreMap.managers.gcg = new GClientGeocoder();
                // make GDirections object
                StoreMap.managers.gdir = new GDirections();
                GEvent.addListener(StoreMap.managers.gdir, "load", StoreMap.gdir_on_load);
            }
        },
        // load all filters for the store
        init_filters : function(callback)
        {
            // load facilities
            $.getJSON(StoreMap.filter_path, null, function(data, status)
            {
                // check the status of the request
                if (status == "success")
                {
                    // if response is success then load the data
                    // create empty array
                    var filters = [];
                    // creat empty object for next reference
                    StoreMap.filters = {};
                    
                    // go through the data
                    for (var key in data)
                    {
                        // create new object for filter
                        var filter = data[key];
						// put into the array
                        filters.push(filter);
                        // put into the object
						StoreMap.filters[key] = filter;
                    }
                        
                    // determine the middle index
                    var half_count = Math.round(filters.length / 2);
                    // split into two, if the item is odd then put more into first array
                    var data_template = {
                        first:filters.slice(0, half_count),
                        second:filters.slice(half_count)
                    };
                    
                    $("#facilities .items").setTemplateElement("filter_template", null, {filter_data: false});
                    $("#facilities .items").processTemplate(data_template);
                    // every filter, we apply callback when it is clicked
                    $("#facilities .items input").each(function()
                    {
                        // determine the checkbox whether checked or not at start
                        $(this).attr("checked", false);
                        
                        $(this).click(function()
                        {
                            var filters_on = [];
                            var filters_off = [];
                            
                            // check whether the map still using static version or not
							if (StoreMap.is_static_map)
							{
								StoreMap.hide_map_static();
							}
							
                            // retreive all filter that is checked
                            $("#facilities .items input:checked").each(function()
                            {
                                filters_on.push($(this).attr("value"));
                            });
                            
                            // retreive all filter that is not checked
                            $("#facilities .items input:not(:checked)").each(function()
                            {
                                filters_off.push($(this).attr("value"));
                            });
                            
                            StoreMap.filter_on_change(filters_on, filters_off);
                        });
                    });
                    
                    // if the callback function is defined
                    if (callback)
                    {
                        // run it
                        callback();
                    }
                }
            });
        },
        // load all type for the store
        init_types : function(callback)
        {
            // load facilities
            $.getJSON(StoreMap.type_path, null, function(data, status)
            {
                // check the status of the request
                if (status == "success")
                {
                    // if response is success then load the data
                    // create empty object for next reference
                    StoreMap.types = {};
                    
                    // go through the data
                    for (var key in data)
                    {
                        // create new object for filter
                        var type = data[key];
                        // put into the object
						StoreMap.types[key] = type;
                    }
                    
                    // if the callback function is defined
                    if (callback)
                    {
                        // run it
                        callback();
                    }
                }
            });
        },
        // load all stores data
        init_stores : function(callback)
        {
            // load facilities
            $.getJSON(StoreMap.store_path, null, function(data, status)
            {
                // check the status of the request
                if (status == "success")
                {
                    // create new icon for stores
                    var stores_icon = new GIcon();
                    // set the properties
                    stores_icon.image = StoreMap.icon_store_highlight_image;
                    //StoreMap.icons.stores.shadow = StoreMap.icon_store_shadow_image;
                    stores_icon.iconSize = new GSize(StoreMap.icon_store_image_size_width, StoreMap.icon_store_image_size_height);
                    //StoreMap.icons.stores.shadowSize = new GSize(StoreMap.icon_store_shadow_size_width, StoreMap.icon_store_shadow_size_height);
                    stores_icon.iconAnchor = new GPoint(StoreMap.icon_store_anchor_point_left, StoreMap.icon_store_anchor_point_top);
                    //stores_icon.infoWindowAnchor = new GPoint(StoreMap.icon_store_panel_anchor_left, StoreMap.icon_store_panel_anchor_top);
                    // set it for later use
                    StoreMap.icons.stores = stores_icon;
					// create store data structure
                    StoreMap.stores_filters = new BSharpTree();
					// create new list
                    StoreMap.substores = [];
                    // create substore data structure
                    StoreMap.substores_filters = new BSharpTree();
                    
					var substore_index = 0;
					
                    // iterate all stores
                    for (var key in data)
                    {
                        var store = data[key];
						
						if (store.website != "")
						{
							store.website = "http://" + store.website;
						}
						
						// set the status that store is selected
						store.is_selected = true;
						// set the status that store is highlighted
						store.is_highlight = true;
						
						// highlight the store in the map
						store.highlight = function()
						{
							// check if the store is previously not highlighted
							if (!this.is_highlight)
							{
								// it is necessary to put this code into try catch since it is buggy and not stable when
								// combined with MarkerClusterer
								try
								{
									this.is_highlight = true;
									// change the image both template and current one
									this.substores[0].marker.getIcon().image = StoreMap.icon_store_highlight_image;
									this.substores[0].marker.setImage(StoreMap.icon_store_highlight_image);
								}
								catch(e)
								{
									
								}
							}
						};
						// unhighlight the store in the map
						store.unhighlight = function()
						{
							// check if the store is previously not highlighted
							if (this.is_highlight)
							{
								// it is necessary to put this code into try catch since it is buggy and not stable when
								// combined with MarkerClusterer
								try
								{
									this.is_highlight = false;
									// change the image both template and current one
									this.substores[0].marker.getIcon().image = StoreMap.icon_store_unhighlight_image;
									this.substores[0].marker.setImage(StoreMap.icon_store_unhighlight_image);
								}
								catch(e)
								{
									
								}
							}
						};
						
						store.filter_change = function(state)
						{
							this.is_selected = state;
							
							if (state)
							{
								this.highlight();
							}
							else
							{
								this.unhighlight();
							}
						};
						
						// go through all the types
						for (var i = 0; i < store.substores.length; i++)
						{
							var substore = store.substores[i];
							
							// function callback to check whether store is in the area from the point or not
							substore.contains_in_area = function(point)
							{
								// get the distance and convert to km
                                var distance = point.distanceFrom(this.point) / 1000;

                                // set the distance
                                this.distance_from_user = distance;
                                
                                // hide it as default
                                this.in_area = false;
                                
                                // add all stores that in distance of the point
                                if (distance <= StoreMap.distance_store_search)
                                {
                                    // set the status that the stores in the area
                                    this.in_area = true;
                                }
							};
							
							// filter for the store
							var filters = [];
							
							// check if the store filter is exist
							if (substore.filter != "")
							{
								// it is exist
								filters = substore.filter.split(",");
							}
							
							// set the filter
							substore.filters = filters;
							
							// set the filter detail
							substore.filters_detail = [];
							
							// browse all filter
							for (var j = 0; j < substore.filters.length; j++)
							{
								// get filter key
								var filter = substore.filters[j];
								
								// push the filter detail
								substore.filters_detail.push(StoreMap.filters[filter]);
							}
							
							// set the substore index in the collection for fast retreiving
							substore.nid = substore_index;
							
							var geocode = substore.geocode.split(",");
							
							if (geocode.length != 3)
							{
								geocode = [0, 0, 0];
							}

							// check if the geocode is valid
							if (geocode.length == 3)
							{
								// change the geocode to Google Maps format
								substore.point = new GLatLng(geocode[0], geocode[1]);
								
								// TODO: fix this to configuration
								
								// make marker only for sales
								if (i == 0)
								{
									// create store marker according to geocode from database
									var marker = new GMarker(substore.point,
									{
										icon : new GIcon(stores_icon)
									});
									
									// store the store nid
									marker.store_nid = new Number(store.nid);
									marker.substore_nid = new Number(substore_index);
									// store the type nid for later references like which marker is
									// being clicked like sales, service or parts
									marker.type_nid = new Number(substore.type);
		
									// bind marker to have onclick event and show the store title
									GEvent.addListener(marker, "click", function()
									{
										var store = StoreMap.stores[this.store_nid];
										var substore = store.substores[this.type_nid];
										StoreMap.selected_substore = StoreMap.substores[this.substore_nid];
										// remove previous overlay that created
										StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.store_panel);
										// create new overlay
										StoreMap.overlays.store_panel = new StoreMap.StoreOverlay(substore.point, store, substore);
										// move the map to the center of the store
										StoreMap.managers.gmap.panTo(substore.point);
										// add new overlay
										StoreMap.managers.gmap.addOverlay(StoreMap.overlays.store_panel);
										// show result in sidebar
										scroll_to_store(this.store_nid, substore.type);
									});
									
									// bind marker to have onmouseover event and show the store title
									GEvent.addListener(marker, "mouseover", function()
									{
										var store = StoreMap.stores[this.store_nid];
										var substore = store.substores[this.type_nid];
										StoreMap.selected_substore = StoreMap.substores[this.substore_nid];
										// remove previous overlay that created
										StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.store_panel);
										// create new overlay
										StoreMap.overlays.store_panel = new StoreMap.StoreOverlay(substore.point, store, substore);
										// add new overlay
										StoreMap.managers.gmap.addOverlay(StoreMap.overlays.store_panel);
									});
									
									// set the marker
									substore.marker = marker;
									// put into store markers
									StoreMap.markers.stores.push(marker);
								}
								
								// set the default distance
								substore.distance_from_user = 0;
								// set the status that store is in area of point so that when user first time load, the store will not show in the result
								substore.in_area = false;
								
								// put into list
								StoreMap.substores.push(substore);
								
								// add the substore based on the filter
								// notice, each substore in the store is registered into the B# tree
								// not the store itself
								StoreMap.substores_filters.add(substore.filters.toString(), substore_index);
								substore_index++;
							}
						}
						
						// put the store based on the key for fast retriving
						StoreMap.stores[store.nid] = store;
						
						// add the store based on the filter
                        StoreMap.stores_filters.add(store.filters.toString(), store.nid);
					}
                    
                    // add into marker manager
                    StoreMap.managers.marker_clusterer.addMarkers(StoreMap.markers.stores, 0);
                    StoreMap.managers.marker_clusterer.refresh();
                }
				
				// if the callback function is defined
				if (callback)
				{
					// run it
					callback();
				}
            });
        },
        // release all resources
        finalise : function()
        {
            GUnload();
        },
        // make a circle polygon for debugging purposes
        make_circle_polygon : function(point, radius)
        {
            var R = 6371; // km
            var kmToMile = 0.621371192;
            var mileToLatLng = 0.014483;
            var sides = 16;
            // find the radius in lat/lon
            var rlat = radius * kmToMile * mileToLatLng;
            var rlng = rlat / Math.cos(point.lat() * Math.PI / 180);

            var extp = new Array();
            for (var i = 0; i < sides + 1; i++) // one extra here makes sure we connect the
            {
                var theta = Math.PI * (i / (sides / 2));
                ex = point.lng() + (rlng * Math.cos(theta)); // center a + radius x * cos(theta)
                ey = point.lat() + (rlat * Math.sin(theta)); // center b + radius y * sin(theta)
                extp.push(new GPoint(ex, ey));
            }

            return new GPolyline(extp, "#000000", 2);
        },
        // event handler for direction on loaded
        gdir_on_load: function()
        {
        	// -- Drawing direction polyline --
        	// get polyline overlay from GDirections
        	var direction = StoreMap.managers.gdir.getPolyline();
			direction.setStrokeStyle({
				color: StoreMap.direct_polygon_color,
				weight: StoreMap.direct_polygon_weight,
				opacity: StoreMap.direct_polygon_opacity
			});
			
        	// remove previously drawn for direction
        	StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.direction);
        	// set current direction
        	StoreMap.overlays.direction = direction;
        	// draw new overlay for direction
        	StoreMap.managers.gmap.addOverlay(direction);
        	// -- End --
        	
        	// -- Drawing marker both start and end --
			var direction_point_count = direction.getVertexCount();
        	var start_point = direction.getVertex(0);
        	var end_point = direction.getVertex(direction_point_count - 1);
        	// remove previously drawn for direction
        	StoreMap.managers.gmap.removeOverlay(StoreMap.markers.direction_start);
        	
			// create new icon for direction
			var direction_start_icon = new GIcon();
			direction_start_icon.image = StoreMap.icon_direction_start_image;
			direction_start_icon.iconSize = new GSize(StoreMap.icon_direction_start_image_size_width, StoreMap.icon_direction_start_image_size_height);
			direction_start_icon.iconAnchor = new GPoint(StoreMap.icon_direction_start_anchor_point_left, StoreMap.icon_direction_start_anchor_point_top);
			
			// set start direction marker
        	StoreMap.markers.direction_start = new GMarker(start_point, {
				icon: direction_start_icon
			});
			
		   	// draw new overlay for direction
        	StoreMap.managers.gmap.addOverlay(StoreMap.markers.direction_start);
        	// remove previously drawn for direction
        	StoreMap.managers.gmap.removeOverlay(StoreMap.markers.direction_end);
        	// set current direction
        	//StoreMap.markers.direction_end = new GMarker(end_point);
        	// draw new overlay for direction
        	//StoreMap.managers.gmap.addOverlay(StoreMap.markers.direction_end);
        	// -- End --

        	// -- Move the map --
        	var direction_bound = direction.getBounds();
			var direction_zoom = StoreMap.managers.gmap.getBoundsZoomLevel(direction_bound);
			var direction_point = direction_bound.getCenter();
			// set center of the map
			StoreMap.managers.gmap.setCenter(direction_point, direction_zoom - 1);
			// -- End --
			
			// -- Drawing the direction summary --
			// get route of the direction
			var route = StoreMap.managers.gdir.getRoute(0);
        	// get number step
			var count = route.getNumSteps();
			// all step result
			var steps = [];
			
			for(var i = 0; i < count; i++)
			{
				var route_step = route.getStep(i);
				var step = {
					desc: route_step.getDescriptionHtml(),
					distance: route_step.getDistance().html
				};
				
				// put into list
				steps.push(step);
			}
			
			var substore = StoreMap.selected_substore;
			
			// prepare the template object
			var template_object = {
				start: StoreMap.user_address,
				// set the list of step
				steps : steps,
				end: substore.address + ", " + substore.suburb + ", " + substore.state
			};
			
			// get the div container
			var results = $("#direct-container");
			
			// put the result
			results.setTemplateElement("result_direction", null, {filter_data: false});
			results.processTemplate(template_object);
			
			// when user click clear button
			// we need to clear all polyline and the marker start and end
			// and we need to show the store result
			$(".btn-clear", results).click(function()
			{
				StoreMap.managers.gmap.removeOverlay(StoreMap.markers.direction_start);
				StoreMap.managers.gmap.removeOverlay(StoreMap.markers.direction_end);
				StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.direction);
				show_store_result();
			});
			
			show_direct_result();
			// -- End --
        },
        // callback function when filter checkboxs changed
        filter_on_change : function(filters_on, filters_off)
        {
            // activate the preloader
            $(".preloader").show();
            
            setTimeout(function()
            {
            	//console.debug("filters: " + filters_on + "   " + filters_off);
            	
				// list of stores that should be selected
				var stores_on = [];
				// list of stores that should be not selected
				var stores = StoreMap.stores;
	
				for (var key in stores)
				{
					stores[key].filter_change(false);
				}
				
				// check if there is filter selected
				if (filters_on.length != 0)
				{
					// there is filter then search it based on filters
					stores_on = StoreMap.stores_filters.search(filters_on.toString());
					
					for (var i = 0; i < stores_on.length; i++)
					{
						var nid = stores_on[i];
						
						stores[nid].filter_change(true);
					}
				}
				else
				{
					stores_on = StoreMap.stores;
					
					for (var key in stores_on)
					{
						var nid = stores_on[key].nid;
						
						stores[nid].filter_change(true);
					}                
				}
				
				// refine the result every time user change filter store
				StoreMap.refine_store_result();
	
				// deactivate the preloader
				$(".preloader").hide();
            }, 500);
        },
        // handy Google Geocoder with address input and it will return:
        // - empty list means no address identified
        // - one item in the list means address is identified
        // - more than one items in the list means address is ambiguous
        locate_address: function(address, callback)
        {
	        var result = [];
        	// add country name after the address request
            var search_address = address + " " + StoreMap.localise_country;
            
            // request geocode from address given
            StoreMap.managers.gcg.getLocations(search_address, function(data)
            {
                // list of the location that we interested
                var locations = [];
                
                // check if we get valid response
                if (data != null && data.Status.code == 200)
                {
                    // get list of response from GClientGeocoder
                    var placemarks = data.Placemark;
                    
                    // go through all the result and get all location that is in the country specified
                    for (var key in placemarks)
                    {
                        var placemark = placemarks[key];
                        var address_detail = placemark.AddressDetails;
                        
                        // check if the address detail is valid
                        if (address_detail != null)
                        {
                            // it is valid
                            
                            var country = address_detail.Country;
                            
                            // check if the country is valid
                            if (country != null)
                            {
                                // it is valid
                                
                                // check if there is country name specified
                                if (country.CountryName == StoreMap.localise_country)
                                {
                                    // it is specified then add into the list
                                    locations.push(placemark);
                                }
                            }
                        }
                    }
                }
				
                // if there is only 1 options
                if (locations.length == 1)
                {
                	result.push(locations[0]);
                }
                // there are locations that is valid
                else
                {
                	var key_located = -1;
                	
                	// go through all the location
                	// and check if the location is
                	// identified among the list
                	for (var key in locations)
                	{
                		// check if the address is match with
                		// what user wants
                		if (address == locations[key].address)
                		{
                			key_located = key;
                		}
                	}
                	
                	// check if the address is located
                	if (key_located != -1)
                	{
                		// just push the address that identified as the user queried
                		result.push(locations[key_located]);
                	}
                	else
                	{
                		// just copy all list which this means it is ambiguous
                		result = locations;
                	}
                }
                
                // call the callback function
                if (callback)
                {
                	callback(result);
                }
            });
        },
        // callback function when search is changed
        search_on_change : function(address)
        {
            // activate the preloader
            $(".preloader").show();
			// hide the no result box as default
			$(".no-result").hide();
			// hide the result predict box as default
			$(".result-predict").hide();
			// hide the result predict box as default
			$("#direct-container").hide();
			$("#direct-container *").remove();
			// hide the result predict box as default
			$("#result-container").hide();
			$("#result-container *").remove();
			
			// it is not fresh anymore since user already press the find button
			StoreMap.is_fresh = false;
			StoreMap.search_value = address;
			
			// get the addresses from Google
            StoreMap.locate_address(address, function(locations)
            {
                // deactivate the preloader
				$(".preloader").hide();
				
                // no locations found
                if (locations.length == 0)
                {
                    // show error message to indicate no result found
                    $(".no-result").show();
                }
                // if there is only 1 options
                else if (locations.length == 1)
                {
                	var points = locations[0].Point.coordinates;

                    //console.debug(locations[0]);
                    
					// set center of the start up
					var latlngBox = locations[0].ExtendedData.LatLonBox;
					var bound = new GLatLngBounds(new GLatLng(latlngBox.south, latlngBox.west), new GLatLng(latlngBox.north, latlngBox.east));
					var zoom = StoreMap.managers.gmap.getBoundsZoomLevel(bound) + StoreMap.zoom_adjustment;
					var point = bound.getCenter();
					
					// set center of the start up
					StoreMap.managers.gmap.setCenter(point, zoom);
					
                    // set the user point
                    StoreMap.user_point = point;
                    // search the surrounding store
                    StoreMap.search_store(point);
                }
                // the address is ambiguous
                else
                {
                	// all address result
                    var addresses = [];
                    
                    // go through all the result
                    for (var key in locations)
                    {
                        // put into the list
                        addresses.push(locations[key].address);
                    }
                    
                    // prepare the template object
                    var template_object = {
                        // set the list of address
                        addresses : addresses
                    };
                    
                    // get the div container
                    var results = $(".result-predict");
                    
                    // put the result
                    results.setTemplateElement("result_ambiguous", null, {filter_data: false});
                    results.processTemplate(template_object);
					// show the result
                    results.show();
                    
                    // resize the window
                    resizeWindow();
                    
                    // set all address to be clickable and will auto insert the text and do the searching
                    $(".address", results).click(function()
                    {
                        // set the text
                        $("#locator-search .input-search").attr("value", $(this).text());
                        // search the stores
                        $("#locator-search .btn-search").click();
                    });
                }
            });
        },
        // callback function when direction is changed
        direct_on_change: function(address)
        {
        	// activate the preloader
            $(".direct-preloader").show();
			// hide the no result box as default
			$(".direct-no-result").hide();
			// hide the result predict box as default
			$(".direct-result-predict").hide();
			
			// get the addresses from Google
            StoreMap.locate_address(address, function(locations)
            {
                // deactivate the preloader
				$(".direct-preloader").hide();
				
                // no locations found
                if (locations.length == 0)
                {
                    // show error message to indicate no result found
                    $(".direct-no-result").show();
                }
                // if there is only 1 options
                else if (locations.length == 1)
                {
                	var start = locations[0].address;
                	var end_point = StoreMap.selected_substore.point;
                	var end = end_point.lat() + "," + end_point.lng();
                	// set the direction from specified address to the point of the store
                	StoreMap.managers.gdir.load("from: " + start + " to: " + end,
                	{
                		getPolyline: true,
                		getSteps: true
                	});
                	
                	// set the user address for display
                	StoreMap.user_address = start;
                	
                	// close the color box
                	$.fn.colorbox.close();
                }
                // the address is ambiguous
                else
                {
                	// all address result
                    var addresses = [];
                    
                    // go through all the result
                    for (var key in locations)
                    {
                        // put into the list
                        addresses.push(locations[key].address);
                    }
                    
                    // prepare the template object
                    var template_object = {
                        // set the list of address
                        addresses : addresses
                    };
                    
                    // get the div container
                    var results = $(".direct-result-predict");
                    
                    // put the result
                    results.setTemplateElement("direct_ambiguous", null, {filter_data: false});
                    results.processTemplate(template_object);
					// show the result
                    results.show();
                    
                    // resize the window
                    resizeWindow();
                    
                    // set all address to be clickable and will auto insert the text and do the searching
                    $(".address", results).click(function()
                    {
                        // set the text
                        $("#direct-content .input-address").attr("value", $(this).text());
                        // search the stores
                        $("#direct-content .btn-go").click();
                    });
                }
            });
        },
        // function to show surrounding store and hide others
        search_store : function(point)
        {
            var substores = StoreMap.substores;
            
            // go throught all stores to check the distance
            for (var i = 0; i < substores.length; i++)
            {
            	// check the distance
                substores[i].contains_in_area(point);
            }

            // remove previous overlay that created
            StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.store_panel);

            // refine the result every time user search store
            StoreMap.refine_store_result();
        },
        // function to compose store list result from the query that user made
        refine_store_result : function()
        {
            // array of store result
            var store_results = [];
            var stores = StoreMap.stores;
            
            // go throught all stores to check the distance
            for (var key in stores)
            {
                var store_result = {};
                var store = stores[key];
                
                // set the value
				var store_result = {
					nid : store.nid,
					title : store.title,
					license: store.dealerLicense,
					website: store.website,
					substores : [],
					distance: ""
				};
				
				var substores = store.substores;
				var count = substores.length;
				
				for (var i = 0; i < count; i++)
				{
					var substore_result = {};
					var substore = substores[i];
					var type = StoreMap.types[substore.type];
					
					substore_result = {
						nid: substore.nid,
						title: type.title,
						type: substore.type,
						street: substore.address,
						suburb: substore.suburb,
						state: substore.state,
						postcode: substore.postcode,
						phone: substore.phone,
						fax: substore.fax,
						email: substore.email,
						operatinghours: substore.operatingHours,
						in_area: substore.in_area,
						distance_value: substore.distance_from_user
					};
					
					store_result.substores.push(substore_result);
				}
				
				// if the store is selected then add all the substores
				if (store.is_selected)
				{
					store_result.distance = (Math.round(substores[0].distance_from_user * 100) / 100) + "kms";
					
					// add into list
					store_results.push(store_result);
				}
            }
            
            // sort the stores to be ascending in terms of distance
			store_results.sort(function(a, b)
			{
				return a.substores[0].distance_value - b.substores[0].distance_value;
			});
			
			var store_filtered_results = [];
			
            // hide the no result box as default
			$(".no-result").hide();
			
			// remove previous overlay that created
            StoreMap.managers.gmap.removeOverlay(StoreMap.overlays.store_panel);
            
			// check if the store is empty and make sure the page
            if (store_results.length == 0 && !StoreMap.is_fresh)
            {
            	// show the no result box
				$(".no-result").show();
				
				// remove the old result
				$("#result-container *").remove();
            }
            else if (StoreMap.is_fresh)
            {
	            store_filtered_results = store_results;
            }
            else
            {				
				var searchTerm = StoreMap.search_value.toLowerCase();
				var state_locations = StoreMap.state_locations.split(",");
				var isStateTerm = false;
				
				for (var i = 0; i < state_locations.length; i++)
				{
					if (state_locations[i] == searchTerm)
					{
						isStateTerm = true;
					}
				}
				
				if (isStateTerm)
				{
					var count = store_results.length;
					var store_index = 0;
					
					for (var i = 0; i < count; i++)
					{
						var substore = store_results[i].substores[0];
						
						if (substore.state.toLowerCase() == searchTerm)
						{
							store_filtered_results.push(store_results[i]);
						}
					}
				}
				else
				{
					var count = store_results.length;
					var store_index = 0;
					
					// since the array already sorted based on the distance
					// we need to determine which index we should stop
					// by checking if the store is in area we include into the result
					for (var i = 0; i < count; i++)
					{
						var substore = store_results[i].substores[0];
						
						if (substore.in_area)
						{
							store_index = i;
						}
					}
					
					// set the store and remove all stores that more than the specified
					store_filtered_results = store_results.slice(0, store_index);
				}
            }

            if (store_filtered_results.length > 0)
            {
				var incremental_index = 0;
				// indicator to state that please open the panel for specific store
				var is_hidden = false;
				var regexPostcode = /^\d{4,}$/;
				var adjustment = 0;
				var is_first = true;
				
				var render_store_result = function()
				{
					if (regexPostcode.test(StoreMap.search_value) && is_first)
					{
						adjustment = 1 - StoreMap.store_result_count;
						$("#searchresults_more img").attr("src", StoreMap.alternative_more_dealer_image);
					}
					else
					{
						$("#searchresults_more img").attr("src", StoreMap.default_more_dealer_image);
					}
					
					var start_index = incremental_index * StoreMap.store_result_count + adjustment;
					var end_index = (incremental_index + 1) * StoreMap.store_result_count + adjustment;
					
					if (start_index < 0)
					{
						start_index = 0;
					}
					
					// prepare the template object
					var template_object =
					{
						start_index: start_index,
						stores : store_filtered_results.slice(start_index, end_index)
					};
					
					if (template_object.stores.length > 0)
					{
						var container = $("<div></div>");
						// put the result
						container.setTemplateElement("store_result_container_template", null, {filter_data: false});
						container.processTemplate(template_object);
						
						// do post-processing such as event binding
						$(".result", container).each(function()
						{
							// prepare panel visibility button
							make_up_store_result($(this), is_hidden);
							is_hidden = true;
						});
						
						$("#searchresults_more").before(container.children());
					}
					
					incremental_index++;
					
					// check if there is no more store then hide the more button
					if (store_filtered_results.length <= incremental_index * StoreMap.store_result_count)
					{
						$("#searchresults_more").hide();
					}
					
					// resize window
					resizeWindow();
					
					is_first = false;
				};
				
				// make container
				var results = $("#result-container");
				
				// put the result
				results.setTemplateElement("store_result_template", null, {filter_data: false});
				results.processTemplate();
				// display the result
				results.show();
				
				// at first, we want to show some result
				render_store_result();
				
				$("#searchresults_more").click(function()
				{
					render_store_result();
				});
            }
        }
    };
    
    // load controls
    StoreMap.init_control();
    // load static map that shows only states
    StoreMap.init_map_static();
    // load map
    StoreMap.init_map();
    
    // load filters
    StoreMap.init_filters(function()
    {
    	// load types
    	StoreMap.init_types(function()
    	{
    		// after loading filters then load stores
	        StoreMap.init_stores(function()
			{
				// check for query string start and end in the request for auto query
				if (window.location.search.length != 0)
				{
					// create an array
					var qs = window.location.search.substr(1).split("&");

					// iterate all the array item
					for (var i = 0; i < qs.length; i++)
					{
						var pair = qs[i].split("=");
						var name = pair[0];
						var value = pair[1];

						// check whether the site value is exist or not
						if (value != null)
						{
							// if the name in the query string is q
							if (name == "q")
							{
								// register auto enter behaviour
								$("#locator-search .input-search").val(value);
								$('#locator-search .btn-search').click();
							}
						}
					}
				}
			}); // end init_stores
    	}); // end init_types
    }); // end init_filters
    
    // register the unload callback to release all resources
    $(window).unload(function()
    {
        StoreMap.finalise();
    });
    
    // hide the pre loader
    $(".preloader").hide();
	// hide the no result box as default
	$(".no-result").hide();
	// hide the result predict box as default
	$(".result-predict").hide();
	// hide the result predict box as default
	$("#result-container").hide(); 
    // hide the pre loader
    $(".direct-preloader").hide();
	// hide the no result box as default
	$(".direct-no-result").hide();
	// hide the result predict box as default
	$(".direct-result-predict").hide();
	
	resizeWindow();
});

