function addScript(src, objectToCheck) {
	if (objectToCheck != null && typeof(window[objectToCheck]) == 'object') {
		return;
	}
    document.write('<script type="text/javascript" ');
    document.write('src="' + src + '">');
    document.write('</script>');
}

function addScriptDynamic(src) {
    var headNode = document.getElementsByTagName("head")[0];
    var scriptNode = document.createElement("script");
    scriptNode.setAttribute('type', 'text/javascript');
    scriptNode.setAttribute('src', src);
    headNode.appendChild(scriptNode);
}

function addCss(src, markerOptions) {
	var headNode  = document.getElementsByTagName("head")[0];
	if (markerOptions) {
		// check to see if the css has been loaded already
		var testNode = document.createElement(markerOptions['nodeType']);
		testNode.style.display = 'none';
		testNode.className = markerOptions['className'];
		headNode.appendChild(testNode);
		var styleVal = getRuntimeStyle(testNode, markerOptions['property']);
		if (styleVal == markerOptions['value']) {
			headNode.removeChild(testNode);
			return;
		}
		headNode.removeChild(testNode);
	}
    var cssNode   = document.createElement('link');
    cssNode.type  = 'text/css';
    cssNode.rel   = 'stylesheet';
    cssNode.href  = src;
    cssNode.media = 'screen';
    headNode.appendChild(cssNode);
}

/**
 * Get the runtime style attribute of an element (includes both external and inline styles)
 */
function getRuntimeStyle(oElm, strCssRule) {
	var strValue = "";
	if(window.getComputedStyle){
		strValue = window.getComputedStyle(oElm, "")[strCssRule];
	}
	else if(oElm.currentStyle){
		strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
			return p1.toUpperCase();
		});
		strValue = oElm.currentStyle[strCssRule];
	}
	return strValue;
}


(function(){
    var cache = {};
   
    this.tmpl = function tmpl(str, data){
      // Figure out if we're getting a template, or if we need to
      // load the template - and be sure to cache the result.
        
        str = document.getElementById(str).value;
      //var fn = !/\W/.test(str) ?
        //cache[str] = cache[str] ||
       //   tmpl(document.getElementById(str).value) :
       
        // Generate a reusable function that will serve as a template
        // generator (and which will be cached).
        var functionBody = "var p=[],print=function(){p.push.apply(p,arguments);};" +
        
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
       
        // Convert the template into pure JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');";
        //console.log(functionBody);
        var fn = new Function("obj",functionBody);
  // );
     
      // Provide some basic currying to the user
      return data ? fn( data ) : fn;
    };
})();
        
function waitForBody(callback) {
    waitForCond(function() { return document.body != undefined && document.body != null && $('_map_container') != null; }, WAIT_FOR_BODY_INTERVAL, callback);
}
        
function waitFor(elementId, checkInterval, callback) {
    waitForCond(function() { return document.getElementById(elementId); }, checkInterval, callback);
}

function waitForAll(elementIds, checkInterval, callback) {
    waitForCond(function() {
        var conditionMet = true;
        var elements     = [];
        for (var i = 0; i < elementIds.length; i++) {
            var element  = document.getElementById(elementIds[i]);
            conditionMet = conditionMet && (element != null);
            elements.push(element);
        }
        return conditionMet ? elements : false;
    }, checkInterval, callback);
}
        
function waitForCond(cond, checkInterval, callback) {
    var x = cond();
    if (x) {
        callback(x);
    } else {
        window.setTimeout(function() { waitForCond(cond, checkInterval, callback); }, checkInterval);
    }
}


/** if required, create a faux console for IE */
(function() {
    var _CONSOLE_DEBUG = 0;
    if (!window.console) {
        if (_CONSOLE_DEBUG) {
            var consoleDiv = document.createElement("textarea");
            var button     = document.createElement("button");
            button.innerHTML = "Clear Console";
            consoleDiv.className = "fauxConsole";
            consoleDiv.readOnly  = true;
            button.onclick = function(e) {
                consoleDiv.value = "";
            };
            waitForBody(function() {
                document.body.appendChild(consoleDiv);
                document.body.appendChild(button);
            });
            window.console = {log: function(t) { consoleDiv.value += (t + '\n'); } };
        } else {
            window.console = {log: function(t) { }}; /* at least avoid a javascript error */
        }
    }
})();

function StopWatch() {
    this.LOG_TIMES = false;
    this.go();
}

StopWatch.prototype.go = function() {
    this.start = new Date();
    this.lastCheckPoint = this.start;
};

StopWatch.prototype.checkpoint = function(msg) {
    var now = new Date();
    var elapsedSinceCheck = (now - this.lastCheckPoint) / 1000;
    var elapsedSinceStart = (now - this.start) / 1000;
    this.lastCheckPoint   = now;
    
    if (this.LOG_TIMES) {
        console.log(msg + " " + elapsedSinceCheck + "s since last checkpoint, " + elapsedSinceStart + "s since start");
    }
};

function EventThrottle(checkpoints) {
    this.checkpoints = checkpoints;
    this.count = checkpoints;
};

EventThrottle.prototype.start = function() {
    this.count = 0;
};

EventThrottle.prototype.checkpoint = function() {
    this.count++;
};

EventThrottle.prototype.isFinished = function() {
    return this.count >= this.checkpoints;
};

function _array_split_items_per_group(arr, numGroups) {
    var numItems = arr.length;
    if (numItems % numGroups == 0) {
        return numItems / numGroups;
    }
    return Math.ceil(numItems / numGroups);
}

function array_partition(arr, itemsPerGroup) {
    var currItem = 0;
    var groups = [];
    var currGroup = [];
    for (var i = 0; i < arr.length; i++) {
        currGroup.push(arr[i]);
        currItem++;
        if (currItem == itemsPerGroup) {
            groups.push(currGroup);
            currGroup = [];
            currItem = 0;
        }
    }
    if (currGroup.length > 0) {
        groups.push(currGroup);
    }
    return groups;
}

function array_split(arr, numGroups) {
    var itemsPerGroup = _array_split_items_per_group(arr, numGroups);
    return array_partition(arr, itemsPerGroup);
}
_data_response_queue = {};

_data_response_queue_process = function(serviceName, data) {
    var callback = _data_response_queue[serviceName];
    if (callback) {
        callback(data);
    }
};

_data_response_queue_add = function(serviceName, callback) {
    _data_response_queue[serviceName] = callback;
};

function ajax(options) {
    _data_response_queue_add(options.serviceName, options.callback);
    addScriptDynamic(options.url);
}

function ajax_xhr(options) {
	new Ajax.Request(options.url, {
		  method: 'POST',
		  parameters: options.params,
		  onSuccess: function(transport) {
			options.callback(transport.responseText);
		  }
    });
}

function fillDestination(targetSelectId, options) {
    ajax({
        serviceName: options.serviceName,
        url: 'http://www.asiawebdirect.com/chang/maps.php/json/' + options.serviceName + '?serviceName=' + options.serviceName + '&id=' + options.id,
        callback: function(data) {
            var locations = data.evalJSON();
            var dList = $(targetSelectId);
            dList.options.length = 0;
            if (options['all']) {
                dList.options[0] = new Option(options.allLabel, '');
            }
            for (var i = 0; i < locations.length; i++) {
                var option = new Option(options.nameFunc(locations[i]), options.valueFunc(locations[i]));
                option.setAttribute('path', locations[i].path);
                option.setAttribute('idPath', locations[i].id_path);
                option.setAttribute('id', locations[i].id);
                dList.options[dList.options.length] = option;
            }
            dList.onclick = function() { options.clickFunc(dList); };
            if (options['afterCreate']) {
                options.afterCreate(dList);
            }
        }
    });
}

function setDestinationListValues(values) {
	values = eval('(' + values + ')');
	setupDestinationLists(values.id_path, values.path);	
}

function setupDestinationLists(selectedDestIdPath, selectedDestPath, selectIds) {
    if (!selectIds) {
        selectIds = {
            'countrySelect' : 'countrySelect',
            'regionSelect'  : 'regionSelect',
            'areaSelect'    : 'areaSelect'
        };
    }
    if (selectedDestIdPath && selectedDestPath) {
        var idPath    = selectedDestIdPath.substr(1, selectedDestIdPath.length - 2).split('/');
        var path      = selectedDestPath.substr(1, selectedDestPath.length - 2).split('/');
        var destLevel = idPath.length;
        var autoSelectedCountry = false;
        var autoSelectedRegion  = false;
        var autoSelectedArea    = false;
    } else {
        var autoSelectedCountry = true;
        var autoSelectedRegion  = true;
        var autoSelectedArea    = true;
        var destLevel = 4;
    }

    fillDestination(selectIds.countrySelect, {
        'serviceName' : 'loadCountries',
        'id'          : '',
        'afterCreate' : function(countrySelect) {
            if (destLevel >= 2 && !autoSelectedCountry) {
                setSelectValue(countrySelect, {'name' : path[1]});
                autoSelectedCountry = true;
            }
        },
        'nameFunc'    : dstName,
        'valueFunc'   : dstId,
        'clickFunc'   : function(countrySelect) {
            clearList(selectIds.regionSelect);
            clearList(selectIds.areaSelect);
            fillDestination(selectIds.regionSelect, {
                'serviceName' : 'loadRegions',
                'id'          : countrySelect.options[countrySelect.selectedIndex].getAttribute('id'),
                'all'         : true,
                'allLabel'    : 'All Regions',
                'afterCreate' : function(regionSelect) {
                    if (destLevel >= 3 && !autoSelectedRegion) {
                        setSelectValue(regionSelect, {'name' : path[2]});
                        autoSelectedRegion = true;
                    }
                },
                'nameFunc'    : dstName,
                'valueFunc'   : dstId,
                'clickFunc'   : function(regionSelect) {
                    clearList(selectIds.areaSelect);
                    if (!regionSelect.value) return;
                    fillDestination(selectIds.areaSelect, {
                        'serviceName' : 'loadAreas',
                        'id'          : regionSelect.options[regionSelect.selectedIndex].getAttribute('id'),
                        'all'         : true,
                        'allLabel'    : 'All Areas',
                        'afterCreate' : function(areaSelect) {
                            if (destLevel >= 4 && !autoSelectedArea) {
                                setSelectValue(areaSelect, {'name' : path[3]});
                                autoSelectedArea = true;
                            }
                        },
                        'nameFunc'    : dstName,
                        'valueFunc'   : dstId,
                        'clickFunc'   : function(areaSelect) {
                            // do nothing
                        }
                    });
                }
            });
        }
    });
}

function dstName(dst) {
    return dst.name;
}

function dstId(dst) {
    return dst.id;
}

function clearList(id) {
    $(id).options.length = 0;
}

function dstPath(dst) {
    return dst.path;
}

/*
 * Target is an Object:
 * target.name -- the target name (used first if specified)
 * target.value -- the target value
 */
function setSelectValue(select, target) {
    for (var i = 0; i < select.options.length; i++) {
        var option = select.options[i];
        if (target['name'] && target['name'] == option.text) {
            select.selectedIndex = i;
            select.onclick();
            return;
        } else if (target['value'] && target['value'] == option.value) {
            select.selectedIndex = i;
            select.onclick();
            return;
        }
    }
}

function getSelectedDestinationId() {
    var selectedIndex = $('areaSelect').selectedIndex;
    if (selectedIndex != -1 && $('areaSelect').options[selectedIndex].value != "") return $('areaSelect').options[selectedIndex].value;
    
    selectedIndex = $('regionSelect').selectedIndex;
    if (selectedIndex != -1 && $('regionSelect').options[selectedIndex].value != "") return $('regionSelect').options[selectedIndex].value;

    return $('countrySelect').options[$('countrySelect').selectedIndex].value;
}

function fromPath(path) {
    return path.replace(/\//g, ',');
}

function processListAsync(list, batchSize, processor, callback, index) {
	if (!index) {
		index = 0;
	}
	var count = 0;
	while (index < list.length && count < batchSize) {
		processor(list[index], list, index);
		count++;
		index++;
	}
//	console.log('remaining: ' + (list.length - index));
	if (index == list.length) { // finished
		callback();
	} else {
		window.setTimeout(function() {
			processListAsync(list, batchSize, processor, callback, index);
		}, 0.05);
	}
}
  
addScript('http://www.asiawebdirect.com/chang/js/prototype.js',     'Prototype');
addScript('http://www.asiawebdirect.com/chang/js/scriptaculous.js', 'Scriptaculous');
addScript('http://www.asiawebdirect.com/chang/js/effects.js',       'Effect');
addScript('http://www.asiawebdirect.com/chang/js/dragdrop.js',      'Droppables');
addCss('http://www.asiawebdirect.com/chang/css/maps.css', {nodeType: 'div', className: '_marker_class', property: 'zIndex', value: '999'});

// Define timeout intervals; there should be no need to ever modify these.
DEFAULT_INTERVAL               = 500;
WAIT_FOR_BODY_INTERVAL         = DEFAULT_INTERVAL;
WAIT_FOR_TEMPLATE_INTERVAL     = DEFAULT_INTERVAL;
WAIT_FOR_CLUSTER_LIST          = DEFAULT_INTERVAL;
WAIT_FOR_FACILITY_LIST         = DEFAULT_INTERVAL;
WAIT_FOR_THUMBNAIL_INTERVAL    = DEFAULT_INTERVAL;
WAIT_FOR_IMAGE_BUTTON_INTERVAL = DEFAULT_INTERVAL;
WAIT_FOR_REVIEW_INTERVAL       = DEFAULT_INTERVAL;
WAIT_FOR_REVIEW_PAGE_INTERVAL  = DEFAULT_INTERVAL;

// Timing class that can be useful for debugging and performance tuning
STOP_WATCH               = new StopWatch();
STOP_WATCH.LOG_TIMES     = false;
ZOOM                     = 12;
CLUSTER_RADIUS           = 40;

MARKER_TYPE_SINGLE       = 1;
MARKER_TYPE_CLUSTER      = 2;
MARKER_TYPE_LANDMARK     = 3;
MARKER_TYPE_LANDMARK_NEW = 4;
MARKER_TYPE_LABEL        = 5;
MARKER_TYPE_DESTINATION  = 6;

LOCATION_TYPE_HOTEL      = 1;
LOCATION_TYPE_LANDMARK   = 2;
LOCATION_TYPE_LABEL      = 3;

DESTINATION_ZOOM_THRESHOLD = 10; // this or lower will show destinations instead of locations
DESTINATION_COUNTRIES_ONLY = 4;
LANDMARK_ZOOM_THRESHOLD  = 15;

// Constants that determine how much information to add to the map
VERBOSITY_MAXIMUM = 100;
VERBOSITY_PORTAL_SHOW_LANDMARK_ON_MAP = 75;
VERBOSITY_MEDIUM  = 50;
VERBOSITY_MINIMUM = 1;

// this is used to change the colour of the destination icons and prevent new behaviour being applied to awd.com
IS_AWD = window.location.hostname.match('.*?\.asiawebdirect.com') != null;

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

function SizeObj() {
}

SizeObj.prototype.size = function() {
    var size = 0;
    for (var key in this) {
        size++;
    }
    return size -1;
};

function HotelMap(container, options) {
    this.container = document.createElement("div");
    this.container.style.width  = container.style.width;
    this.container.style.height = container.style.height;
    this.container.style.position = 'relative';
    this.container.style.overflow = 'hidden';
    container.appendChild(this.container);
    this.options    = options;
    this.zoomTarget = null;
    this.highlightedLocations = null; // don't change this to empty array as it will cause locations to use faded icons
    this.selectedDestination = null;
    this.origLocations = null;
    this.locations = [];
    this.locationsRegister = [];
    this.pendingNewLocations = [];
    this.destinations = new SizeObj;
    this.overlays = [];
    this.labels     = [];
    this.allMarkers = new SizeObj;
    this.visibleMarkers = new SizeObj;
//    this.visibleMarkers = [];
    this.landmarkMarkers = [];
    this.showLandmarkControl = null;
    this.clusterRadius = CLUSTER_RADIUS;
    this._refreshing = false;
    this._refreshQueued = false;
    this._refreshCbQueue = [];
    // need to fetch templates
    ajax({
        serviceName: 'mapTemplates',
        url: 'http://www.asiawebdirect.com/chang/maps.php/json/templates?serviceName=mapTemplates',
        callback: function(data) {
            var json = data.evalJSON();
            var div  = document.createElement("div");
            div.innerHTML = json.html;
            document.body.appendChild(div);
            waitFor("_map_template_container", WAIT_FOR_TEMPLATE_INTERVAL, function() { this.initialize(); }.bind(this));            
        }.bind(this)
    });
    
    if (!this.options['verbosity']) {
        this.options.verbosity = VERBOSITY_MAXIMUM;
    }
    if (this.options.verbosity <= VERBOSITY_MINIMUM) {
        this.container.style.overflow = "hidden";
    }
    if (this.options['clusterRadius']) {
    	this.clusterRadius = this.options['clusterRadius'];
    }
    this.highlightLocationId = null;
    if (this.options['highlightLocationId']) {
    	this.highlightLocationId = this.options.highlightLocationId;
    	this.highlightedLocations = [];
    	this.highlightedLocations[this.highlightLocationId] = true;
    }
}

HotelMap.prototype.initialize = function() {
    this.hideLoadingBackground();
    this.map = new GMap2(this.container);
    if (this.options.verbosity >= VERBOSITY_PORTAL_SHOW_LANDMARK_ON_MAP) {
      this.map.addControl(new GLargeMapControl3D(), new GControlPosition(new GSize(5, 5), new GSize(3, 3)));
      this.map.addControl(new GMapTypeControl());
    }
    this.createMapTypes();
    
    GEvent.addListener(this.map, "zoomend", function(oldLevel, newLevel) {
//    	console.log('zoomend fired');
        if (this.showLandmarkControl != null) {
            this.showLandmarkControl.updateCheckbox(newLevel);
        }
        
    	this.refreshAsync();
    	
		if (!IS_AWD && (oldLevel != undefined) && (newLevel < oldLevel)) { // zooming out
			this.resolveWhatToLoad(); // to add any new locations/destinations
		} else if (!IS_AWD && (oldLevel < LANDMARK_ZOOM_THRESHOLD) && (newLevel >= LANDMARK_ZOOM_THRESHOLD)) { // zooming in and need to now show landmarks
			this.resolveWhatToLoad(); // to add any new locations/destinations
		}
    }.bind(this));
    
    GEvent.addListener(this.map, "moveend", function() {
//    	console.log('moveend fired');
        if (this.locationList.isInitialized()) {
            this.refreshVisibleLocations();
        }
    }.bind(this));
    
    GEvent.addListener(this.map, "click", function(overlay) {
        if (overlay != null && this.options.verbosity >= VERBOSITY_MEDIUM) { // clicked on marker
            this.showMarkerInfo(overlay);
        }
    }.bind(this));
    
    this.hotel_icon = new GIcon(G_DEFAULT_ICON);
    this.hotel_icon.image = "http://static.asiawebdirect.com/awd/icons/maps/hotel-icon.gif";
    this.hotel_icon.iconSize = new GSize(25, 25);
    this.hotel_icon.shadowSize = new GSize(35, 26);

    this.hotel_icon_faded = new GIcon(G_DEFAULT_ICON);
    this.hotel_icon_faded.iconSize = new GSize(25, 25);
    this.hotel_icon_faded.shadowSize = new GSize(0, 0);
    this.hotel_icon_faded.image = "http://static.asiawebdirect.com/awd/icons/maps/hotel-icon-fade.png";
    
    var lm_classifications = [{name: 'Activity',      width: 23, height: 23}, 
                              {name: 'Airport',       width: 23, height: 23},
                              {name: 'Attraction',    width: 20, height: 26},
                              {name: 'Night Spot',    width: 23, height: 23},
                              {name: 'Restaurant',    width: 23, height: 23},
                              {name: 'Shop',          width: 23, height: 23},
                              {name: 'Hospital/Clinic',          width: 23, height: 23},
                              {name: 'Train Station', width: 23, height: 23}];
    this.landmark_icons = {};
    for (var cIdx = 0; cIdx < lm_classifications.length; cIdx++) {
        var c = lm_classifications[cIdx];
        var imgName = c.name.replace(' ', '_').toLowerCase() + '.gif';
        imgName = imgName.replace('/', '_').toLowerCase();
        this.landmark_icons[c.name] = this.createIcon('http://static.asiawebdirect.com/awd/icons/maps/' + imgName, parseInt(c.width * .8), parseInt(c.height * .8));
    }

    this.landmark_icons_faded = {};
    for (var cIdx = 0; cIdx < lm_classifications.length; cIdx++) {
        var c = lm_classifications[cIdx];
        var imgName = c.name.replace(' ', '_').toLowerCase() + '-fade.gif';
		imgName = imgName.replace('/', '_').toLowerCase();
        this.landmark_icons_faded[c.name] = this.createIcon('http://static.asiawebdirect.com/awd/icons/maps/' + imgName, parseInt(c.width * .8), parseInt(c.height * .8));
    }
    defineOverlayWidgets();
    this.MapLabel = defineMapLabel();
    
    var LocationListControl = defineLocationListControl();
   
    this.locationList = new LocationListControl(this);
    if (this.options.verbosity >= VERBOSITY_MAXIMUM) {
        this.map.addControl(this.locationList);
    }
    
    var ShowLandmarkControl  = defineShowLandmarkControl();
    this.showLandmarkControl = new ShowLandmarkControl(this);
    if (this.options.verbosity >= VERBOSITY_MAXIMUM) {
        this.map.addControl(this.showLandmarkControl);
    }
    
    this.tooltipDiv = document.createElement("div");
    this.tooltipDiv.className = "map_tooltip";
    this.map.getContainer().appendChild(this.tooltipDiv);
    
    if (this.options.location) {
        this.map.setCenter(new GLatLng(this.options.location.lat, this.options.location.lng), this.options.location.zoom);
    }
    if (this.options.mapType) {
        this.map.setMapType(this.mapTypes[this.options.mapType]);
    }
    
    if (this.options.init) {
        this.options.init(this);
    }

    this.constrainLogoBounds();
};

HotelMap.prototype.constrainLogoBounds = function() {
	var containerNodes = this.map.getContainer().childNodes;
	for (var i = 0; i < containerNodes.length; i++) {
		var child = containerNodes[i];
		var grandChildNodes = child.childNodes;
		for (var j = 0; j < grandChildNodes.length; j++) {
			var grandChild = grandChildNodes[j];
			if (grandChild.nodeType != 1) continue;
			if (Element.hasClassName(grandChild, 'terms-of-use-link')) {
				child.style.color     = 'gray';
				child.style.left      = '70px';
				child.style.right     = '0';
				child.style.fontSize  = '9px';
				child.style.overflow  = 'hidden';
				child.style.textAlign = 'left';
				return;
			}
		}
	}
	// coulnd't find - map not totally initialized yet
	window.setTimeout(this.constrainLogoBounds.bind(this), 1500);
};

HotelMap.prototype.addLabel = function(point) {
    var label = {
        lat: point.lat(),
        lng: point.lng(),
        name: 'New Label',
        anchor: 'left',
        zoom: this.map.getZoom()
    };
    this.labels.push(label);
    this.refreshAsync();
};

HotelMap.prototype.createIcon = function(imgUrl, width, height) {
    var icon = new GIcon(G_DEFAULT_ICON);
    icon.image = imgUrl;
    icon.iconSize = new GSize(width, height);
    icon.shadowSize = new GSize(0, 0);
    return icon;
};

HotelMap.prototype.showMarkerInfo = function(overlay) {
    if (overlay._markerType == MARKER_TYPE_CLUSTER) {
        overlay.openInfoWindowHtml(new HotelMapClusterList(this, overlay).html);
    } else if (overlay._markerType == MARKER_TYPE_SINGLE) {
        this.requestHotelInfo(overlay, this.locations[overlay._dataIdx].source_id);
    } else if (overlay._markerType == MARKER_TYPE_LANDMARK) {
        var loc = this.locations[overlay._dataIdx];
        if (loc.id == null) {
            overlay.openInfoWindowHtml(new EditLandMark(this, overlay, null, loc.idPath, loc.path).html);
        } else {
            overlay.openInfoWindowHtml(new LandmarkInfo(this, overlay, loc).html);
            if (this.options.edit) {
	            $('current_name').update(loc.name);
	            $('current_id').update(loc.id);
            }
        }
    } else if (overlay._markerType == MARKER_TYPE_LABEL) {
        var label = this.labels[overlay._dataIdx];
        this.map.openInfoWindowHtml(overlay.point, new EditLabel(this, label).html);
    } else if (overlay._markerType == MARKER_TYPE_DESTINATION) {
    	var d  = this.destinations['id_' + overlay._destination_id];
        this.selectDestination(d.id);
        document.fire("map:destinationSelected", d.id);
    }
};

HotelMap.prototype.selectDestination = function(destId) {
	var el = $('dest-' + destId);
	if (this.selectedDestination != null) {
		this.selectedDestination.className = "mapDest";
	}
	this.selectedDestination = el;
	el.className = "mapDest mapDestSelected";
};

HotelMap.prototype.createMapTypes = function() {
	this.map.addMapType(G_PHYSICAL_MAP);
    this.mapTypes = {};
    var types = this.map.getMapTypes();
    for (var i = 0; i < types.length; i++) {
        this.mapTypes[types[i].getName()] = types[i];
    }
};

HotelMap.prototype.showLoadingBackground = function() {
    this.container.className = "mapLoading";
};

HotelMap.prototype.hideLoadingBackground = function() {
    this.container.className = "";
};

HotelMap.prototype.resize = function(widthPx, heightPx) {
    var c = this.map.getContainer();
    c.style.width  = widthPx  + 'px';
    c.style.height = heightPx + 'px';
    this.map.checkResize();
    this.locationList.setHeightBasedOnMap(this.map);
};

HotelMap.prototype.requestHotelInfo = function(overlay, id, callbackFunc) {
	if (!callbackFunc) {
		callbackFunc = function(data) {
            var hotel = data.evalJSON().hotel;
            this.createHotelInfoTabs(overlay, hotel, this.locations[overlay._dataIdx]);			
		}.bind(this);
	}
    ajax({
        serviceName: 'hotelInfo',
        url: 'http://www.asiawebdirect.com/chang/maps.php/json/loadHotelInfo?serviceName=hotelInfo&hotelId=' + id,
        callback: callbackFunc
    });
};

HotelMap.prototype.createHotelInfoTabs = function(overlay, hotel, location) {   
    var overview    = new HotelMapOverview(location, hotel);
    var facilities  = new HotelMapFacilities(location, hotel);
    var hotelPhotos = new HotelMapPhotos(location, hotel);
    var comments    = new HotelMapGuestComments(location, hotel);

    overlay.openInfoWindowTabs([overview.tab, facilities.tab, hotelPhotos.tab, comments.tab]);
    return {'overview' : overview.tab, 'facilities' : facilities.tab, 'photos' : hotelPhotos.tab, 'comments' : comments.tab};
};

HotelMap.prototype.locTypeToMarkerType = function(locType) {
    if (locType == LOCATION_TYPE_HOTEL) {
        return MARKER_TYPE_SINGLE;
    } else if (locType == LOCATION_TYPE_LANDMARK) {
        return MARKER_TYPE_LANDMARK;
    } else if (locType == LOCATION_TYPE_LABEL) {
        return MARKER_TYPE_LABEL;
    }
};

HotelMap.prototype.refreshVisibleLocations = function() {
    this.visibleMarkers = new SizeObj();
    var mapBounds = this.map.getBounds();

    for (var key in this.allMarkers) {
    	if (key != 'size') {
    		var marker = this.allMarkers[key];
    		if (mapBounds.containsLatLng(marker.getLatLng())) {
    			this.visibleMarkers['id_' + marker._locationId] = this.allMarkers[key];
    		}
    	}
    }
};

HotelMap.prototype.getVisibleHotels = function() {
    return this.getMarkersForLocationType(this.visibleMarkers, LOCATION_TYPE_HOTEL);
};

HotelMap.prototype.getVisibleLandmarks = function() {
    return this.getMarkersForLocationType(this.visibleMarkers, LOCATION_TYPE_LANDMARK);
};

HotelMap.prototype.getAllHotels = function() {
    return this.getMarkersForLocationType(this.allMarkers, LOCATION_TYPE_HOTEL);
};

HotelMap.prototype.getAllLandmarks = function() {
    return this.getMarkersForLocationType(this.allMarkers, LOCATION_TYPE_LANDMARK);
};

HotelMap.prototype.getMarkersForLocationType = function(markerList, locType) {
	// convert markerList to array if necessary
	if (markerList instanceof SizeObj) {
		var new_markerList = [];
		for (var key in markerList) {
			if (key != 'size') {
				new_markerList.push(markerList[key]);
			}
		}
		markerList = new_markerList;
	}

    var markers = [];
    for (var i = 0; i < markerList.length; i++) {
        var m = markerList[i];
        if (this.locations[m._dataIdx].location_type == locType) {
            markers.push(m);
        }
    }
    return markers;
};

HotelMap.prototype.getIconForLocation = function(loc) {
	var icon = null;
	if (loc.location_type == LOCATION_TYPE_HOTEL) {
		icon = this.hotel_icon;
		if ((this.highlightedLocations != null) && (this.highlightedLocations[loc.source_id] != true)) {
			// fade out all hotels except the highlighted location
			icon = this.hotel_icon_faded;
		}
	} else {
		icon = this.landmark_icons[loc.classification];
		if (this.highlightedLocations != null && this.highlightedLocations[loc.id] != true)  {
			// fade out all landmarks except the highlighted one
			icon = this.landmark_icons_faded[loc.classification];
		}
		if (this.highlightedLocations != null && this.highlightedLocations[loc.id] == true) {
			// add label above icon
			this.map.addOverlay(new LocationLabelOverlay(loc));
		}
	}
	return icon;
};

HotelMap.prototype.getClusterImage = function(cluster) {
	if (this.highlightedLocations != null) {
		// check if the highlighted location is in this cluster
		for (var lIdx = 0; lIdx < cluster.length; lIdx++) {
			var loc = this.locations[cluster[lIdx]._dataIdx].source_id;
			if (this.highlightedLocations[loc]) {
				return "http://static.asiawebdirect.com/awd/icons/maps/cluster-icon.gif";
			}
		}
		return "http://static.asiawebdirect.com/awd/icons/maps/cluster-icon-fade.png";
	}
	return "http://static.asiawebdirect.com/awd/icons/maps/cluster-icon.gif";
};

HotelMap.prototype.refreshAsync = function(finishCallback, options) {
	if (!options) {
		var options = {};
	}
	var zoom = this.map.getZoom();
	if (this._refreshing) {
		this._refreshQueued = true;
		if (options.locationsToAdd) {
			this.pendingNewLocations = this.pendingNewLocations.concat(options.locationsToAdd);
		}
		if (finishCallback) this._refreshCbQueue.push(finishCallback);
		return;
	}
    this._refreshing = true;
    finishCallbacks = this._refreshCbQueue.clone();
    this._refreshCbQueue = [];
    if (finishCallback) {
    	finishCallbacks.push(finishCallback);
    }

    if (!options.locationsToAdd && !options.destinationsToAdd) {
    	this.map.clearOverlays();
    }

    var allMarkers     = [];
    var lmMarkers      = [];
    var visibleMarkers = [];

    var mapBounds = this.map.getBounds();
    
    var locations = this.locations;
    
    if (options.locationsToAdd) {
    	locations = options.locationsToAdd;
    }
    
    if (zoom < 11 && IS_AWD == false) {
    	locations = [];
    }
    
    var destinations = this.destinations;
    if (options.destinationsToAdd) {
    	destinations = options.destinationsToAdd;
    }
    
    processListAsync(locations, 100,
    	function (loc, llist, i) {
	        var markerType = this.locTypeToMarkerType(loc.location_type);
	        var m = new GMarker(new GLatLng(loc.lat, loc.lng), { icon: this.getIconForLocation(loc), draggable: true });
	        m._markerType = markerType;
	
	        if (loc.id != null)
	            m.disableDragging();
	 
	        if (this.zoomTarget != null && this.zoomTarget._dataIdx == i)
	            this.zoomTarget = m;
	
	        if (!options.locationsToAdd){
	        	m._dataIdx = i;
	        } else {
	        	m._dataIdx = loc._dataIdx;
	        }
	        allMarkers.push(m);
	
	        if (mapBounds.containsLatLng(m.getLatLng()))
	            visibleMarkers.push(m);
	        
	        if (m._markerType == MARKER_TYPE_LANDMARK)
	            lmMarkers.push(m);
	        	
		        m._locationId = loc.id;
    	}.bind(this),
    	function () {
    		for (var i = 0; i < visibleMarkers.length; i++) {
    			this.visibleMarkers['id_' + visibleMarkers[i]._locationId] = visibleMarkers[i];
    		}
    		for (var i = 0; i < lmMarkers.length; i++) {
    			this.landmarkMarkers.push(lmMarkers[i]);
    		}
    		for (var i = 0; i < allMarkers.length; i++) {
    			this.allMarkers['id_' + allMarkers[i]._locationId] = allMarkers[i];
    		}
    	    if ((zoom <= DESTINATION_ZOOM_THRESHOLD) && (IS_AWD == false)) {
    	    	var clusters = [];
    	    } else {
    	    	var clusters = this.clusterMarkers(allMarkers, this.clusterRadius, this.map.getZoom()); // clusters CONTAINS landmarks
    	    }
    	    processListAsync(clusters, 30, function(clist, _, cIdx) {
	    	    	var c = clist[0];
	    	    	var clusterSize = null;
    	    		if (!options.locationsToAdd) {
    	    			c._clusterIdx = cIdx;
    	    		} else {
    	    			c._clusterIdx = this.clusters.length + cIdx;
    	    			clusterSize = clist.length;
    	    		}
	    	        this.addMarkerToolTipEvent(c, clusterSize);
	    	        
	    	        if (this.shouldAddOverlay(c)) {
	    	            this.map.addOverlay(c);
	    	        }
	    	        if (clist.length > 1) {
	    	            c._markerType = MARKER_TYPE_CLUSTER;
	    	            var img = this.getClusterImage(clist);
	    	            c.setImage(img);
	    	        }
    	    	}.bind(this),
    	    	function() {
    	    		
    	    		if ((zoom <= DESTINATION_ZOOM_THRESHOLD)) {
	    	    		// convert destinations to array if necessary
	    	    		if (destinations instanceof SizeObj) {
	    	    			var new_dests = [];
	    	    			for (var key in destinations) {
	    	    				if (key != 'size') {
	    	    					new_dests.push(destinations[key]);
	    	    				}
	    	    			}
	    	    			destinations = new_dests;
	    	    		}
	    	    		
	    	    		if (zoom <= DESTINATION_COUNTRIES_ONLY) {
	    	    			var countries_only = [];
	    	    			for (var i = 0; i < destinations.length; i++) {
	    	    				if (destinations[i].level == 1) {
	    	    					countries_only.push(destinations[i]);
	    	    				}
	    	    			}
	    	    			destinations = countries_only;
	    	    		}
	    	    		destinations = this.removeOverlappingDestinations(destinations.clone(), 20, this.map.getZoom());
    	    		} else {
    	    			destinations = [];
    	    		}
    	    		
    	    		processListAsync(destinations, 30, function(dest, dlist, dIdx) {
		                    var destNode = document.createElement("div");
		                    destNode.className = "mapDest";
		                    if (IS_AWD) {
		                    	destNode.innerHTML = '<div style="position: relative; _width: 100%;"><div class="destContainer">&nbsp;</div><a href="javascript:void(0);">' + dest.name + '</a></div>';
		                    } else {
		                    	destNode.innerHTML = '<div style="position: relative; _width: 100%;" class="destination-level' + dest.level + '"><div class="destContainer">&nbsp;</div><a href="javascript:void(0);">' + dest.name + '</a></div>';
		                    }
		                    destNode.id = "dest-" + dest.id;
		                    var overlay = new DraggableOverlay(destNode, '', new GLatLng(dest.lat, dest.lng));
		                    overlay.draggable = false;
		                    overlay._markerType = MARKER_TYPE_DESTINATION;
		                    overlay._destination_id = destinations[dIdx].id;
	                    	this.map.addOverlay(overlay);
		                    
    	    		    }.bind(this),
        	        	function() {
    	    		    	for (var i = 0; i < this.overlays.length; i++) {
    	    		    		this.map.addOverlay(this.overlays[i]);
    	    		    	}
    	    		    	if (!options.locationsToAdd && !options.destinationsToAdd) {
    	    		    		this.clusters = clusters;
    	    		    	} else {
    	    		    		for (var i = 0; i < clusters.length; i++) {
    	    		    			this.clusters.push(clusters[i]);
    	    		    		}
    	    		    	}

        	        	    this.locationList.refresh();
        	        	    finishCallbacks.each(function (cb) { cb(); }.bind(this));
        	        	    this._refreshing = false;
        	    	    
    	    	            if (this.zoomTarget != null) {
        	    	            this.showMarkerInfo(this.zoomTarget);
        	    	            this.zoomTarget = null;	    	        	
    	    	            }
        	    	    
        	    	        if (this._refreshQueued) {
        	    	        	// process pending refreshes
        	    	        	this._refreshQueued = false;
        	    	        	var options_new = {};
        	    	        	options_new.locationsToAdd = this.pendingNewLocations;
        	    	        	if (this.pendingNewLocations) {
        	    	        		this.refreshAsync(null, options_new);
        	    	        	} else {
        	    	        		this.refreshAsync();
        	    	        	}
        	    	        } else {
        	    	        	// all pending refreshes complete, fire global event
        	    	        	document.fire("map:refreshComplete");
        	    	        }
        	        	}.bind(this));
    	    	}.bind(this));
    	}.bind(this)
    );
};

HotelMap.prototype.refresh = function() {
    STOP_WATCH.checkpoint("Map refresh started");
    this.map.clearOverlays();
    STOP_WATCH.checkpoint("Overlays cleared");
    var mapBounds = this.map.getBounds();
    
    var allMarkers     = [];
    var lmMarkers      = [];
    var visibleMarkers = [];

    for (var i = 0; i < this.locations.length; i++) {
        var loc = this.locations[i];
        var markerType = this.locTypeToMarkerType(loc.location_type);
        var m = new GMarker(new GLatLng(loc.lat, loc.lng), {
            icon: this.getIconForLocation(loc),
            draggable: true
        });
        m._markerType = markerType;

        if (loc.id != null)
            m.disableDragging();
 
        if (this.zoomTarget != null && this.zoomTarget._dataIdx == i)
            this.zoomTarget = m;

        m._dataIdx = i;
        
        allMarkers.push(m);

        if (mapBounds.containsLatLng(m.getLatLng()))
            visibleMarkers.push(m);
        
        if (m._markerType == MARKER_TYPE_LANDMARK)
            lmMarkers.push(m);
    }
    STOP_WATCH.checkpoint("Created GMarkers for each location");
    
//    this.visibleMarkers  = visibleMarkers;
    for (var i = 0; i < visibleMarkers.length; i++) {
    	this.visibleMarkers['id_' + visibleMarkers[i]._location_id] = visibleMarkers[i];
    }
    this.landmarkMarkers = lmMarkers;

//    this.allMarkers      = allMarkers.clone(); // we clone the array as the clusterMarkers function removes elements from the original
    for (var i = 0; i < allMarkers.length; i++) {
		this.allMarkers['id_' + allMarkers[i]._locationId] = allMarkers[i];
	}
    
    var clusters = this.clusterMarkers(allMarkers, this.clusterRadius, this.map.getZoom());
    STOP_WATCH.checkpoint("Clustered markers");
    
    for (var cIdx = 0; cIdx < clusters.length; cIdx++) {
        var c = clusters[cIdx][0];
        c._clusterIdx = cIdx;
        this.addMarkerToolTipEvent(c);
        if (this.shouldAddOverlay(c)) {
            this.map.addOverlay(c);
        }
        if (clusters[cIdx].length > 1) {
            c._markerType = MARKER_TYPE_CLUSTER;
            var img = this.getClusterImage(clusters[cIdx]);
            c.setImage(img);
        }
    }
    STOP_WATCH.checkpoint("Attached clusters to the map");
    
    for (var lIdx = 0; lIdx < this.labels.length; lIdx++) {
        (function(){
            var l = this.labels[lIdx];
            var node       = document.createElement("div");
            node.className = 'mapLabel';
            node.innerHTML = '&#8226;' + l.name;
            var zoomDiff   = l.zoom - this.map.getZoom();
            var fontSize   = 14 - (zoomDiff * 2);
            node.style.fontSize = fontSize + 'px';
            if (fontSize >= 8) {
                var overlay = new DraggableOverlay(new GLatLng(l.lat, l.lng), node, l.anchor);
                overlay._dataIdx    = lIdx;
                overlay._markerType = MARKER_TYPE_LABEL;
                
                GEvent.addListener(overlay, "move", function(o, p) {
                    l.lat = p.lat();
                    l.lng = p.lng();
                }.bind(this));
                this.map.addOverlay(overlay);
            }
        }).bind(this)();
    }
    
    this.clusters = clusters;
    if (this.zoomTarget != null) {
        this.showMarkerInfo(this.zoomTarget);
        this.zoomTarget = null;
    }
    this.locationList.refresh();
    STOP_WATCH.checkpoint("Map refresh finished");
};

HotelMap.prototype.destinationOverlaps = function(overlay, visible_destinations) {
	for (var i = 0; i < visible_destinations.length; i++) {
		if (visible_destinations[i].overlaps(overlay)) {
			return true;
		}
	}
	return false;
};

HotelMap.prototype.shouldAddOverlay = function(o) {
    if (o._markerType == MARKER_TYPE_LANDMARK && this.showLandmarkControl != null) {
        return this.showLandmarkControl.isShowingLandmarks();
    }
    return true;
};

HotelMap.prototype.addMarkerToolTipEvent = function(marker, clusterSize) {
	var options = {
			'showClusterText': true
	};
	if (clusterSize > 1) {
		options.clusterSize = clusterSize;
	}
    GEvent.addListener(marker, "mouseover", function() { this.showToolTip(marker, options); }.bind(this));
};

HotelMap.prototype.showToolTip = function(marker, options) {
    GEvent.clearListeners(marker, "mouseout");
    var timeout = window.setTimeout(function() {
        this.tooltipDiv.style.display = 'none';
    }.bind(this), 3000);
    GEvent.addListener(marker, "mouseout", function() {
        window.clearTimeout(timeout);
        this.tooltipDiv.style.display = 'none';
    }.bind(this));
    
    var tooltip = options['tooltip'];
    if (!tooltip) {
        tooltip = this.locations[marker._dataIdx].name;
    }
    if (marker._markerType == MARKER_TYPE_CLUSTER && options['showClusterText']) {
    	if (options['clusterSize'] && options['clusterSize'] != 1) {
    		tooltip = options['clusterSize'] + " hotels at this point (click to choose and zoom)";
    	} else {
    		tooltip = this.clusters[marker._cIdx].length + " hotels at this point (click to choose and zoom)";
    	}
    }
        
    var markerPoint = this.map.fromLatLngToContainerPixel(marker.getLatLng());
    this.tooltipDiv.style.left = markerPoint.x + 'px';
    this.tooltipDiv.style.top = markerPoint.y + 'px';
    this.tooltipDiv.innerHTML = tooltip;
    this.tooltipDiv.style.display = 'block';
};

HotelMap.prototype.initializeZoomAndCenter = function(forceRefresh) {
	var currentZoom = this.map.getZoom();
    var bounds = new GLatLngBounds();
    var landmarkHighlighted = false;
    for (var i = 0; i < this.locations.length; i++) {
        var loc = this.locations[i];
        if (loc.location_type == LOCATION_TYPE_HOTEL &&
            (this.highlightedLocations == null || this.highlightedLocations[loc.source_id])) {
            bounds.extend(new GLatLng(loc.lat, loc.lng));
        } else if (loc.location_type == LOCATION_TYPE_LANDMARK &&
            (this.highlightedLocations == null || this.highlightedLocations[loc.id])) {
            bounds.extend(new GLatLng(loc.lat, loc.lng));
            landmarkHighlighted = true;
        }
    }
    var zoom = this.map.getBoundsZoomLevel(bounds);
    if (landmarkHighlighted) {
    	zoom = 17;
    }
    if (zoom == currentZoom && forceRefresh) {
    	this.refreshAsync();
    }
};

HotelMap.prototype.initializeZoomAndCenterOnLatLng = function(lat, lng, zoom) {
    if (zoom == undefined) {
    	zoom = 16;
    }
	this.map.setCenter(new GLatLng(lat, lng), zoom);
	this.refreshAsync(null, {});
};

HotelMap.prototype.initializeZoomAndCenterOnLocation = function(forceRefresh, location_id, zoom) {
    for (var i = 0; i < this.locations.length; i++) {
    	if (this.locations[i].id == location_id) {
    		// should be first one as query sorted by distance but iterate in case of dups
    		var base_location = this.locations[i];
    		break;
    	}
    }

    if (zoom == undefined) {
    	zoom = 16;
    }
    
    if (base_location) { // should always exist
    	this.map.setCenter(new GLatLng(base_location.lat, base_location.lng), zoom);
    }

    var currentZoom = this.map.getZoom();
    if (zoom == currentZoom && forceRefresh) {
    	this.refreshAsync();
    }
};

HotelMap.prototype.initializeZoomAndCenterOnLandmark = function(forceRefresh, landmark) {
	var currentZoom = this.map.getZoom();
    var bounds = new GLatLngBounds();
    for (var i = 0; i < this.locations.length; i++) {
        var loc = this.locations[i];
        if (loc.location_type == LOCATION_TYPE_LANDMARK &&
                (this.highlightedLocations == null || this.highlightedLocations[loc.id])) {
                bounds.extend(new GLatLng(loc.lat, loc.lng));
        }
    }
    var zoom = this.map.getBoundsZoomLevel(bounds);
    this.map.setCenter(bounds.getCenter(), zoom);
    if (zoom == currentZoom && forceRefresh) {
    	this.refreshAsync();
    }
};

HotelMap.prototype.initializePositionFromDest = function(dest, forceRefresh, zoomOffset) {
	var bounds = new GLatLngBounds();
    bounds.extend(new GLatLng(dest.minLat, dest.minLng));
    bounds.extend(new GLatLng(dest.minLat, dest.maxLng));
    bounds.extend(new GLatLng(dest.maxLat, dest.minLng));
    bounds.extend(new GLatLng(dest.maxLat, dest.maxLng));
    
    var currentZoom = this.map.getZoom();
    var newZoom     = this.map.getBoundsZoomLevel(bounds);
    newZoom += zoomOffset;
    
    this.map.setCenter(new GLatLng(dest.lat, dest.lng), newZoom);
    if (newZoom == currentZoom && forceRefresh) {
    	this.refreshAsync();
    }
};

HotelMap.prototype.initializePositionFromChildDests = function(forceRefresh) {
	var bounds = new GLatLngBounds();
	for (var key in this.destinations) {
		var dest = this.destinations[key];
		bounds.extend(new GLatLng(dest.lat, dest.lng));
	}
	
	var currentZoom = this.map.getZoom();
	var newZoom     = this.map.getBoundsZoomLevel(bounds);
	
	this.map.setCenter(bounds.getCenter(), newZoom);
	
	if (newZoom == currentZoom && forceRefresh) {
		this.refreshAsync();
	}
};

HotelMap.prototype.showHighlightedLocationPopup = function() {
	if (this._refreshing) {
		setTimeout(this.showHighlightedLocationPopup.bind(this), 500); // try again in half a second
		return;
	}
	for (var key in this.visibleMarkers) {
		if (key != 'size') {
			var marker = this.allMarkers[key];
			var loc    = this.locations[marker._dataIdx];
			if (loc.source_id == this.highlightLocationId) {
				var clusterMarker = this.clusters[marker._cIdx][0];
				this.requestHotelInfo(clusterMarker, loc.source_id, function(data) {
					clusterMarker.openInfoWindow(new MiniHotelMapOverview(loc, data.evalJSON().hotel).html);
				}.bind(this));
				return;
			}
		}
	}
};

HotelMap.prototype.clearStructures = function() {
	this.locations      = [];
	this.destinations   = new SizeObj;
	this.overlays       = [];
	this.allMarkers     = new SizeObj;
	this.visibleMarkers = new SizeObj;
};

HotelMap.prototype.loadDestination = function(name, options) {
	var keepMapPos; 
	if (typeof(options.keepMapPos) == 'undefined') {
		keepMapPos = false;
	} else {
		keepMapPos = options.keepMapPos;
	}
	
	if (this.highlightLocationId) {
		if (this.options.location && this.options.location.zoom) {
			var zoom = this.options.location.zoom;
		} else {
			var zoom = 17;
		}
		this.loadLocationsCloseToHotel(this.highlightLocationId, 4, 500, zoom, keepMapPos);
		return;
	}
	
	var url = 'http://www.asiawebdirect.com/chang/maps.php/json/loadByDestName?serviceName=loadDestination&destMatch=' + name;
    ajax({
        serviceName: 'loadDestination',
        url: url,
        callback: function(data) {
    	    this.clearStructures();
            var json = data.evalJSON();
            this.locations = json.hotels.concat(json.landmarks);
            if (!options['keepMapPos']) {
            	if (json.dest) {
                    for (var i = 0; i < this.locations.length; i++) {
        				this.locationsRegister['id_' + this.locations[i].id] = this.locations[i];
        			}
            		this.initializePositionFromDest(json.dest, 0, 0);
            	} else {
            		this.initializeZoomAndCenter();
            	}
            }
            this.refreshAsync(function () {
                if (options['openLocList']) {
                    this.locationList.openList();
                }
                if (options['onload']) {
                	options.onload(this, json);
                }            	
            }.bind(this));

            GEvent.addListener(this.map, 'dragend', function(){
            	this.resolveWhatToLoad();
            }.bind(this));

        }.bind(this)
    });
};


HotelMap.prototype.loadLocationsCloseToLatLng = function(lat, lng, max_dist, zoom, type, keepMapPos) {
	if (typeof(keepMapPos) == 'undefined') {
		keepMapPos = false;
	}

	if (!max_dist) { // guesstimates
		if (zoom == 20) max_dist = 0.375;
		if (zoom == 19) max_dist = 0.75;
		if (zoom == 18) max_dist = 1.375;
		if (zoom == 17) max_dist = 2.375;
		if (zoom == 16) max_dist = 4;
		if (zoom == 15) max_dist = 7;
		if (zoom == 14) max_dist = 12.25;
		if (zoom == 13) max_dist = 21.5;
		if (zoom == 12) max_dist = 37.5;
		if (zoom == 11) max_dist = 65.5;
		if (zoom == 10) max_dist = 115;
		if (zoom == 9) max_dist = 200;
		if (zoom == 8) max_dist = 350;
		if (zoom == 7) max_dist = 615;
		if (zoom == 6) max_dist = 1080;
		if (zoom == 5) max_dist = 1880;
		if (zoom == 4) max_dist = 3300;
		if (zoom == 3) max_dist = 5800;
		if (zoom == 2) max_dist = 10100;
		if (zoom == 1) max_dist = 20000;
	}
	max_dist *= 1000; // change to m
	
	if (type == undefined) var type = 'all';
	if (zoom < LANDMARK_ZOOM_THRESHOLD) type = 'hotels';
	
	var url = 'http://www.asiawebdirect.com/chang/maps.php/json/loadLocationsCloseToLatLong?serviceName=loadLocationsCloseToLatLng&lat=' + lat + '&long=' + lng + '&max_dist=' + max_dist + '&type=' + type;

    ajax({
        serviceName: 'loadLocationsCloseToLatLng',
        url: url,
        callback: function(data) {
            var json = data.evalJSON();
            this.locations = json.locations;
            for (var i = 0; i < this.locations.length; i++) {
            	this.locationsRegister['id_' + this.locations[i].id] = this.locations[i];
            }
            
            if (keepMapPos) {
            	this.refreshAsync(null, {});
            } else {
            	this.initializeZoomAndCenterOnLatLng(lat, lng, zoom);
            }

            if (!IS_AWD) {
	            GEvent.addListener(this.map, 'dragend', function(){
	            	this.resolveWhatToLoad();
	            }.bind(this));
            }

        }.bind(this)
    });
};

HotelMap.prototype.loadLocationsCloseToLocation = function(location_id, max_distance, limit, zoom) {
	
	var url = 'http://www.asiawebdirect.com/chang/maps.php/json/loadLocationsCloseToLocation?serviceName=loadLocationsCloseToLocation&location_id=' + location_id + '&max_distance=' + max_distance + '&limit=' + limit;
    ajax({
        serviceName: 'loadLocationsCloseToLocation',
        url: url,
        callback: function(data) {
    	    this.clearStructures();
            var json = data.evalJSON();
            this.locations = json.locations;
            this.highlightedLocations = [];
            this.highlightedLocations[location_id] = true;
            this.initializeZoomAndCenterOnLocation(true, location_id, zoom);

            for (var i = 0; i < this.locations.length; i++) {
				this.locationsRegister['id_' + this.locations[i].id] = this.locations[i];
			}
            
            GEvent.addListener(this.map, 'dragend', function(){
            	this.resolveWhatToLoad();
            }.bind(this));

        }.bind(this)
    });
};

HotelMap.prototype.loadLocationsCloseToHotel = function(hotel_id, max_distance, limit, zoom, keepMapPos) {
	if (typeof(keepMapPos) == 'undefined') {
		var keepMapPos = false;
	}
	var url = 'http://www.asiawebdirect.com/chang/maps.php/json/loadLocationsCloseToHotel?serviceName=loadLocationsCloseToHotel&hotel_id=' + hotel_id + '&max_distance=' + max_distance + '&limit=' + limit;
	if (zoom < LANDMARK_ZOOM_THRESHOLD) {
		url += '&type=hotels';
	}
	
    ajax({
        serviceName: 'loadLocationsCloseToHotel',
        url: url,
        callback: function(data) {
    	    this.clearStructures();
            var json = data.evalJSON();
            this.locations = json.locations;
            this.highlightedLocations = [];
            this.highlightedLocations[hotel_id] = true;
            this.highlightLocationId = hotel_id;

            for (var i = 0; i < this.locations.length; i++) {
				this.locationsRegister['id_' + this.locations[i].id] = this.locations[i];
			}
            if (keepMapPos) {
            	this.refreshAsync(null, {});
            } else {
            	this.initializeZoomAndCenterOnLocation(true, json.location_id, zoom);
            }
            this.showHighlightedLocationPopup();
            
            if (!IS_AWD) {
	            GEvent.addListener(this.map, 'dragend', function(){
	            	this.resolveWhatToLoad();
	            }.bind(this));
            }
        }.bind(this)
    });
};

HotelMap.prototype.resolveWhatToLoad = function() {
	var zoom = this.map.getZoom();
	if (zoom > DESTINATION_ZOOM_THRESHOLD) {
		this.loadLocationsCloseToCenter();
	} else {
		this.loadDestinationsCloseToCenter();
	}
};

HotelMap.prototype.loadLocationsCloseToCenter = function() {
	var type = 'all';
	if (this.map.getZoom() < LANDMARK_ZOOM_THRESHOLD && this.showLandmarkControl.showLandmarks == false) {
		type = 'hotels';
	}
	var center = this.map.getCenter();
	var bounds = this.map.getBounds();
	var diagonal = bounds.getSouthWest().distanceFrom(bounds.getNorthEast());
	
	var url = 'http://www.asiawebdirect.com/chang/maps.php/json/loadLocationsCloseToLatLong?serviceName=loadLocationsCloseToCenter&lat=' + center.lat() + '&long=' + center.lng() + '&max_dist=' + diagonal + '&type=' + type;
	ajax({
		serviceName: 'loadLocationsCloseToCenter',
		url: url,
		callback: function(data) {
			var json = data.evalJSON();
			var newLocations = json.locations;
			var options = {};
			options.locationsToAdd = [];

			for (var i = 0; i < newLocations.length; i++) {
				if (!this.locationsRegister['id_' + newLocations[i].id]) {
					options.locationsToAdd.push(newLocations[i]);
					newLocations[i]._dataIdx = this.locations.length; // length -1 = current idx -> +1 = next idx
					this.locations.push(newLocations[i]);
					this.locationsRegister['id_' + newLocations[i].id] = newLocations[i]; // need id_ to force associative array
				}
			}
			this.refreshAsync(null, options);
		}.bind(this)
	});
};

HotelMap.prototype.loadDestinationsCloseToCenter = function() {
	var center = this.map.getCenter();
	var bounds = this.map.getBounds();
	var max_dist = bounds.getSouthWest().distanceFrom(bounds.getNorthEast()) * 1.2;
	var url = 'http://www.asiawebdirect.com/chang/maps.php/json/loadDestinationsCloseToLatLong?serviceName=loadDestinationsCloseToCenter&lat=' + center.lat() + '&long=' + center.lng() + '&max_dist=' + max_dist;
	ajax({
		serviceName: 'loadDestinationsCloseToCenter',
		url: url,
		callback: function(data) {
			var json = data.evalJSON();
			var newDestinations = json.destinations;
			var options = {};
			options.destinationsToAdd = [];
			
			for (var i = 0; i < newDestinations.length; i++) {
				if (!this.destinations['id_' + newDestinations[i].id]) {
					options.destinationsToAdd.push(newDestinations[i]);
					this.destinations['id_' + newDestinations[i].id] = newDestinations[i];
				}
			}
			this.refreshAsync(null, options);
		}.bind(this)
	});
};

HotelMap.prototype.loadDestinationByLandmarkId = function(locationId) {
	var url = 'http://www.asiawebdirect.com/chang/maps.php/json/loadDestinationByLandmarkId?serviceName=loadDestinationByLandmarkId&landmarkId=' + locationId;
    ajax({
        serviceName: 'loadDestinationByLandmarkId',
        url: 'http://www.asiawebdirect.com/chang/maps.php/json/loadDestinationByLandmarkId?serviceName=loadDestinationByLandmarkId&landmarkId=' + locationId,
        callback: function(data) {
    	    this.clearStructures();
            var json = data.evalJSON();

            this.locations = json.hotels.concat(json.landmarks);
            for (var i = 0; i < this.locations.size(); i++) {
            	if (this.locations[i].id == locationId) {
            		var landmark = this.locations[i];
            		landmark.dataIdx = i;
            		break;
            	}
            }
            this.highlightedLocations = [];
			this.highlightedLocations[locationId] = true;
            this.initializeZoomAndCenter();
        }.bind(this)
    });
};

HotelMap.prototype.loadChildDestPositions = function(id) {
	ajax({
	    serviceName: 'loadChildDestPositions',
	    url: 'http://www.asiawebdirect.com/chang/maps.php/json/loadChildDestPositions?serviceName=loadChildDestPositions&destId=' + id,
	    callback: function(data) {
            this.clearStructures();
		    var json = data.evalJSON();
		    var calledDestinations = json.destinations;
		    for (var i = 0; i < calledDestinations.length; i++) {
		    	this.destinations['id_' + calledDestinations[i].id] = calledDestinations[i];
		    }
		    this.initializePositionFromDest(json.parent, true, 0);
		    //this.initializePositionFromChildDests(true);
		    
	    }.bind(this)
	});
};

HotelMap.prototype.loadCountry = function(id) {
	ajax({
	    serviceName: 'loadCountry',
	    url: 'http://www.asiawebdirect.com/chang/maps.php/json/loadChildDestPositions?serviceName=loadCountry&destId=' + id,
	    callback: function(data) {
            this.clearStructures();
		    var json = data.evalJSON();

		    var url = "http://static.asiawebdirect.com/kml/" + json.parent.id + ".kml";
	        var kml = new GGeoXml(url, function() {
	            if (kml.loadedCorrectly()) {
	                this.overlays.push(kml);
	            }
	            this.initializePositionFromDest(json.parent, true, -1);
	        }.bind(this));
	    }.bind(this)
	});
};

/**
 * Cross-domain compatible way of loading locations by IDs.  The number of IDs that an be loaded at once is limited as this method uses HTTP Get.
 */
HotelMap.prototype.loadLocations = function(ids) {
    ajax({
        serviceName: 'loadByIDs',
        url: 'http://www.asiawebdirect.com/chang/maps.php/json/loadByIDs?serviceName=loadByIDs&locationIds=' + ids,
        callback: function(data) {
            this.clearStructures();
            this.locations = data.evalJSON().hotels;
            this.initializeZoomAndCenter(true);
        }.bind(this)
    });
};

/**
 * Preferrable way to load locations by IDs: This allows the use of HTTP Post and as such allows sending more IDs in the request.
 * NOTE: This method uses XMLHttpRequest and as such is not cross-domain compatible.
 */
HotelMap.prototype.loadLocationsXHR = function(ids) {
	ajax_xhr({
		url: 'http://www.asiawebdirect.com/chang/maps.php/json/loadByIDsXHR',
		params: {'locationIds' : ids},
		callback: function(data) {
            this.locations = data.evalJSON().hotels;
            this.initializeZoomAndCenter();
            this.refresh();
		}.bind(this)
	});
};

/**
 * Filter the current location set to show only the given IDs (for the given location type).
 * If locType is ommitted, then hotel is assumed.
 * 
 * Note: IDs should be an object mapping IDs to true 
 */
HotelMap.prototype.filterLocations = function(ids, locType) {
	if (this.origLocations == null) {
		this.origLocations = this.locations;
	}
	if (!locType) {
		locType = LOCATION_TYPE_HOTEL;
	}
	var idsInc = {};
	for (var i = 0; i < ids.length; i++) {
		idsInc[ids[i]] = true;
	}
	this.highlightedLocations = idsInc;
	this.initializeZoomAndCenter(true);
};

OFFSET = 268435456;
RADIUS = OFFSET / Math.PI;

function lonToX(lon) {
    return Math.round(OFFSET + RADIUS * lon * Math.PI / 180);        
};

function latToY(lat) {
    return Math.round(OFFSET - RADIUS * 
                Math.log((1 + Math.sin(lat * Math.PI / 180)) / 
                (1 - Math.sin(lat * Math.PI / 180))) / 2);
};

function pixelDistance(lat1, lon1, lat2, lon2, zoom) {
    var x1 = lonToX(lon1);
    var y1 = latToY(lat1);

    var x2 = lonToX(lon2);
    var y2 = latToY(lat2);

    return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)) >> (21 - zoom);
};

HotelMap.prototype.calculateOptimalZoom = function(marker, allMarkers, startZoom) {
    var zoom;
    if (!startZoom) {
        zoom = this.map.getZoom();
    } else {
        zoom = startZoom;
    }
    while (zoom < 17) {
        if (!this.markerOverlaps(marker, allMarkers, this.clusterRadius, zoom)) {
            break;
        }
        zoom++;
    }
    return zoom;
};

HotelMap.prototype.markerOverlaps = function(marker, markers, distance, zoom) {
    for (var markerIndex = 0; markerIndex < markers.length; markerIndex++) {
        var target = markers[markerIndex];
        if (target == undefined) {
            continue;
        }
        if (marker == target) {
            continue;
        }
        var pixels = pixelDistance(
                marker.getLatLng().lat(), marker.getLatLng().lng(), target.getLatLng().lat(), target.getLatLng().lng(), zoom);
        if (distance > pixels) {
            return true;
        }
    }
    return false;
};

HotelMap.prototype.clusterMarkers = function(markers, distance, zoom) {
	// convert markers to array if necessary
	if (markers instanceof SizeObj) {
		var new_array = [];
		for (var key in markers) {
			if (key != 'size') {
				new_array.push(markers[key]);
			}
		}
		markers = new_array;
	}
	
    var clustered = [];
    var bounds = this.map.getBounds();
	var diagonal = bounds.getSouthWest().distanceFrom(bounds.getNorthEast());
	var center = this.map.getCenter();

    /* Loop until all markers have been compared. */
    
    while (markers.length) {
        var marker  = markers.pop();
        if (marker == undefined) {
            continue;
        }
        if (marker.getLatLng().distanceFrom(center) > (2 * diagonal)) {
        	// marker is not closer to the center than 2 * diagonal so ignore
        	continue;
        }
        var cluster = [marker];
        marker._cIdx = clustered.length;
        /* Compare against all markers which are left. */
        if (marker._markerType == MARKER_TYPE_SINGLE) {
            for (var markerIndex = 0; markerIndex < markers.length; markerIndex++) {
                var target = markers[markerIndex];
                if (target == undefined) continue;
                if (zoom >= 17) continue;
                
                if (marker._markerType == MARKER_TYPE_LANDMARK) continue; // don't check for overlapping landmarks
                if (target._markerType == MARKER_TYPE_LANDMARK) continue; // don't check for overlapping landmarks

                var pixels = pixelDistance(
                    marker.getLatLng().lat(), marker.getLatLng().lng(), target.getLatLng().lat(), target.getLatLng().lng(), zoom);
                
                /* If two markers are closer than given distance remove */
                /* target marker from array and add it to cluster.      */
                if ((distance) > pixels && (zoom < 17)) {
                    //console.log("CLUSTER: Marker and target differ by: " + pixels + "px");
                    delete markers[markerIndex];
                    target._cIdx = clustered.length;
                    cluster.push(target);
                } else {
                    //console.log("NO CLUSTER: Marker and target differ by: " + pixels + "px");
                }
            }
        }
        clustered.push(cluster);
    }
    return clustered;
};

HotelMap.prototype.removeLandmark = function(index) {
    var loc = this.locations[index];
    new Ajax.Request('/chang/maps_admin.php/landmarks/delete', {
        method: 'POST',
        parameters: {
            locId: loc.id
        },
        onSuccess: function(transport) {
            this.locations.splice(index, 1);
            this.refreshAsync();
        }.bind(this),
        onFailure: function() { },
        onException: function(e, f) { }
    });
};

HotelMap.prototype.removeOverlappingDestinations = function(destinations, distance, zoom) {
	for (var outerIndex = 0; outerIndex < destinations.length; outerIndex++) {
		var destination = destinations[outerIndex];
		
		if (destination == undefined) continue;
		
		// compare destination to all other destinations 
	    for (var innerIndex = 0; innerIndex < destinations.length; innerIndex++) {
	    	if (innerIndex == outerIndex) {
	    		continue; // don't compare to itself
	    	}
	    	var target = destinations[innerIndex];

	    	if (target == undefined) continue;
	    	
	    	var pixels = pixelDistance(destination.lat, destination.lng, target.lat, target.lng, zoom);	    	
	    	if (pixels < distance) { // too close, need to remove one
//	    		console.log(destination.name + ' and ' + target.name + ' are too close');
	    		
	    		if (parseInt(destination.hotel_count) > parseInt(target.hotel_count)) {
	    			delete destinations[innerIndex];
	    		} else {
	    			delete destinations[outerIndex];
	    		}
	    	}
	    }
	}
	
	output = [];
	for (var i = 0; i < destinations.length; i++) {
		if (destinations[i] != undefined) {
			output.push(destinations[i]);
		}
	}
	
    return output;
};

/* -------------------------------------------------------------------------------------------- */
  
/**
 * HotelMapClusterList class
 */

function HotelMapClusterList(map, clusterMarker) {
    this.clusterMarker = clusterMarker;
    this.map = map;

    var allLocations = map.clusters[clusterMarker._clusterIdx].clone();
    allLocations.sort(function (a, b) {
        var nameA = map.locations[a._dataIdx].name;
        var nameB = map.locations[b._dataIdx].name;
        if (nameA > nameB) {
            return 1;
        } else if (nameA == nameB) {
            return 0;
        }
        return -1;
    }.bind(this));

    this.html = tmpl(HotelMapClusterList.prototype.TEMPLATE, {
        'allMarkers' : allLocations,
        'locations'  : map.locations
    });
    
    waitFor(HotelMapClusterList.prototype.ID_TABLE, WAIT_FOR_CLUSTER_LIST, function(e) {
    	var markers = map.allMarkers;
    	if (markers instanceof SizeObj) {
    		var new_array = [];
    		for (var key in markers) {
    			if (key != 'size') {
    				new_array.push(markers[key]);
    			}
    		}
    		markers = new_array;
    	}

        document.getElementById("map_hotelClusterList_zoom").onclick = function(ev) {
            var locData = map.locations[allLocations[0]._dataIdx];
            var zoom    = map.calculateOptimalZoom(allLocations[0], markers);
            map.map.setCenter(new GLatLng(locData.lat, locData.lng), zoom);            
        }.bind(this);
        for (var i = 0; i < allLocations.length; i++) {
            (function () {
                var markerIndex = i; /* close over index */
                var marker      = allLocations[markerIndex];
                var locData     = map.locations[marker._dataIdx];
                var link = document.getElementById(HotelMapClusterList.prototype.LOC_LINK_PREF + marker._dataIdx);
                link.onclick = function(ev) {
                    map.zoomTarget = marker;

                    var zoom = map.calculateOptimalZoom(marker, markers);
                    map.map.setCenter(new GLatLng(locData.lat, locData.lng), zoom);
                }.bind(this);
            }).bind(this)();
        }
    }.bind(this));
}

HotelMapClusterList.prototype.TEMPLATE      = "map_hotelClusterList";
HotelMapClusterList.prototype.ID_TABLE      = "map_hotelClusterList_table";
HotelMapClusterList.prototype.LOC_LINK_PREF = "map_hotelClusterList_loc_";

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

/**
 * HotelMapOverview class
 */

function HotelMapOverview(location, hotel) {
    this.location = location;
    this.hotel    = hotel;
    if (window.location.hostname.match('.*?\.latestays.co')) {
    	hotel.url = 'http://' + window.location.hostname + '/' + hotel.latestays_url + '/';
    }
    this.tab      = new GInfoWindowTab("Overview", tmpl(HotelMapOverview.prototype.TEMPLATE, {
        'hotelName'   : location.name,
        'hotelImage'  : (hotel.image != "" && hotel.image != null) ? hotel.image.url : "http://www.asiawebdirect.com/chang/images/no_photo.png",
        'starRating'  : hotel.starRating,
        'description' : hotel.description,
        'numRooms'    : hotel.numRooms,
        'address'     : hotel.address,
        'hotelUrl'    : hotel.url,
        'showExtraContent' : true
    }));
}

HotelMapOverview.prototype.TEMPLATE = "hotelInfoTab_overview";

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

/**
 * MiniHotelMapOverview class
 */

function MiniHotelMapOverview(location, hotel) {
    this.location = location;
    this.hotel    = hotel;
    if (window.location.hostname.match('.*?\.latestays.co')) {
    	hotel.url = 'http://' + window.location.hostname + '/' + hotel.latestays_url + '/';
    }
    this.html     = tmpl(MiniHotelMapOverview.prototype.TEMPLATE, {
        'hotelName'   : location.name,
        'hotelImage'  : (hotel.image != "" && hotel.image != null) ? hotel.image.url : "http://www.asiawebdirect.com/chang/images/no_photo.png",
        'starRating'  : hotel.starRating,
        'description' : hotel.description,
        'numRooms'    : hotel.numRooms,
        'address'     : hotel.address,
        'hotelUrl'    : hotel.url,
        'showExtraContent' : false
    });
}

MiniHotelMapOverview.prototype.TEMPLATE = "hotelInfoTab_overview";

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

/**
 * HotelMapFacilities class
 */
_FAC_ID_COUNTER = 0;
function HotelMapFacilities(location, hotel) {
    _FAC_ID_COUNTER++;
    this.hotelFacilities  = hotel.hotelFacilities;
    this.roomFacilities   = hotel.roomFacilities;
    this.hotelFacIdPrefix = HotelMapFacilities.prototype.ID_HOTEL_FACILITIES + _FAC_ID_COUNTER;
    this.roomFacIdPrefix  = HotelMapFacilities.prototype.ID_ROOM_FACILITIES  + _FAC_ID_COUNTER;
    this.tab              = new GInfoWindowTab("Facilities", tmpl(HotelMapFacilities.prototype.TEMPLATE, {
        'hotelFacilities' : array_split(this.hotelFacilities, 3),
        'roomFacilities'  : array_split(this.roomFacilities, 3),
        'hotelIdPrefix'   : this.hotelFacIdPrefix,
        'roomIdPrefix'    : this.roomFacIdPrefix,
        'hotelName'       : location.name,
        'hotelUrl'        : hotel.url
    }));

    this.throttle = new EventThrottle(2);

    var clickCreator = function(thisWindow, otherWindow) {
        return function(e) {
            if (this.throttle.isFinished()) {
                this.throttle.start();
                Effect.Shrink(thisWindow, {
                    'queue' : 'front',
                    afterFinish: function() { this.throttle.checkpoint(); }.bind(this)
                });
                Effect.Grow(otherWindow, {
                    'queue' : 'end',
                    afterFinish: function() { this.throttle.checkpoint(); }.bind(this)
                });
            }
        }.bind(this);
    }.bind(this);

    waitFor(this.hotelFacIdPrefix, WAIT_FOR_FACILITY_LIST, function(e) {
        e.onclick = clickCreator(this.hotelFacIdPrefix, this.roomFacIdPrefix);
    }.bind(this));
    
    waitFor(this.roomFacIdPrefix, WAIT_FOR_FACILITY_LIST, function(e) {
        e.onclick = clickCreator(this.roomFacIdPrefix, this.hotelFacIdPrefix);
    }.bind(this));
}

HotelMapFacilities.prototype.TEMPLATE            = "hotelInfoTab_facilities";
HotelMapFacilities.prototype.ID_HOTEL_FACILITIES = "hotelFacilities";
HotelMapFacilities.prototype.ID_ROOM_FACILITIES  = "roomFacilities";

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

/**
 * HotelMapPhotos class
 */
_THUMB_ID_COUNTER = 0;
function HotelMapPhotos(location, hotel) {
    _THUMB_ID_COUNTER++;
    this.photos          = hotel.photos;
    this.thumbnailPrefix = 'thumbnail' + _THUMB_ID_COUNTER + "_";
    this.moveLeftId      = HotelMapPhotos.prototype.ID_MOVE_LEFT  + _THUMB_ID_COUNTER;
    this.moveRightId     = HotelMapPhotos.prototype.ID_MOVE_RIGHT + _THUMB_ID_COUNTER;
    if (this.photos.length > 0) {
        this.tab = new GInfoWindowTab("Photos", tmpl(HotelMapPhotos.prototype.TEMPLATE_PHOTOS, {
            'photos'      : array_partition(this.photos, 5),
            'idPrefix'    : this.thumbnailPrefix,
            'moveLeftId'  : this.moveLeftId,
            'moveRightId' : this.moveRightId,
            'hotelName'   : location.name,
            'hotelUrl'    : hotel.url
        }));
    } else {
        this.tab = new GInfoWindowTab("Photos", tmpl(HotelMapPhotos.prototype.TEMPLATE_NO_PHOTOS, {}));
        return;
    }
    this.borderHighlight = "dashed 1px #000";
    this.borderNormal    = "solid 1px #000";
    this.selectedImg     = 0;
    
    /**
     * Schedule a callback when each thumbnail img tag is present to add an appropriate onclick handler as well
     * as selecting the first image by default.
     */
    for (var i = 0; i < this.photos.length; i++) {
        (function() {
            var j = i /* close over i */;
            waitFor(this.thumbnailPrefix + j, WAIT_FOR_THUMBNAIL_INTERVAL, function(e) {
                e.onclick = function(ev) { this.selectImage(j); }.bind(this);
                if (j == 0) {
                    this.selectImage(j);
                }
            }.bind(this));
        }).bind(this)();
    }

    waitFor(this.moveLeftId, WAIT_FOR_IMAGE_BUTTON_INTERVAL, function(e) {
        e.onclick = function(ev) { this.selectImage(this.selectedImg - 1); }.bind(this);
    }.bind(this));

    waitFor(this.moveRightId, WAIT_FOR_IMAGE_BUTTON_INTERVAL, function(e) {
        e.onclick = function(ev) { this.selectImage(this.selectedImg + 1); }.bind(this);
    }.bind(this));
}

HotelMapPhotos.prototype.selectImage = function(imageIndex) {
    if (imageIndex < 0 || imageIndex >= this.photos.length) return; // invalid index
    var photo     = this.photos[imageIndex];
    var img       = $(this.thumbnailPrefix + imageIndex);
    var largeImg  = $("hotelInfoTab_photos_preview");
    var imgTxt    = $("hotelInfoTab_photos_imageText");
    var moveLeft  = $(this.moveLeftId);
    var moveRight = $(this.moveRightId);

    this.selectedImg = imageIndex; /* update reference to selected image index */
    largeImg.src     = img.src;    /* replace large image */
    imgTxt.innerHTML = photo.text; /* replace image description */
    
    // reset the border for all images
    for (var j = 0; j < this.photos.length; j++) {
        document.getElementById(this.thumbnailPrefix + j).style.border = this.borderNormal;
    }
    img.style.border = this.borderHighlight; /* set selected image border */

    // update the navigation images
    moveLeft.src  = (imageIndex > 0) ? HotelMapPhotos.prototype.IMG_MOVE_LEFT : HotelMapPhotos.prototype.IMG_MOVE_LEFT_DIS;
    moveRight.src = (imageIndex < (this.photos.length - 1)) ? HotelMapPhotos.prototype.IMG_MOVE_RIGHT : HotelMapPhotos.prototype.IMG_MOVE_RIGHT_DIS;
}

HotelMapPhotos.prototype.TEMPLATE_PHOTOS    = "hotelInfoTab_photos";
HotelMapPhotos.prototype.TEMPLATE_NO_PHOTOS = "hotelInfoTab_noPhotos";
HotelMapPhotos.prototype.ID_MOVE_LEFT       = "hotelInfoTab_photos_moveLeft";
HotelMapPhotos.prototype.ID_MOVE_RIGHT      = "hotelInfoTab_photos_moveRight";
HotelMapPhotos.prototype.IMG_MOVE_LEFT      = "http://www.asiawebdirect.com/chang/images/left_arrow.png";
HotelMapPhotos.prototype.IMG_MOVE_RIGHT     = "http://www.asiawebdirect.com/chang/images/right_arrow.png";
HotelMapPhotos.prototype.IMG_MOVE_LEFT_DIS  = "http://www.asiawebdirect.com/chang/images/left_arrow_disabled.png";
HotelMapPhotos.prototype.IMG_MOVE_RIGHT_DIS = "http://www.asiawebdirect.com/chang/images/right_arrow_disabled.png";


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

/**
 * HotelMapGuestComments Class
 */
_REV_ID_COUNTER = 0;
function HotelMapGuestComments(location, hotel) {
    _REV_ID_COUNTER++;
    this.reviews             = hotel.reviews;
    this.reviewGroups        = array_partition(this.reviews, 2);
    this.currentPageIdx      = 0;
    this.throttle            = new EventThrottle(2);
    this.reviewPageIdPrefix  = HotelMapGuestComments.prototype.REV_PAGE_PREF  + _REV_ID_COUNTER + "_";
    this.reviewGroupIdPrefix = HotelMapGuestComments.prototype.REV_GROUP_PREF + _REV_ID_COUNTER + "_";
    
    if (this.reviews.length == 0) {
        this.tab = new GInfoWindowTab("Comments", tmpl(HotelMapGuestComments.prototype.NO_COMMENTS_TEMPLATE, {}));
        return;
    }
    this.tab = new GInfoWindowTab("Comments", tmpl(HotelMapGuestComments.prototype.TEMPLATE, {
        'reviews'       : this.reviewGroups,
        'pageIdPrefix'  : this.reviewPageIdPrefix,
        'groupIdPrefix' : this.reviewGroupIdPrefix,
        'hotelName'     : location.name,
        'hotelUrl'      : hotel.url
    }));
    
    waitFor(this.reviewGroupIdPrefix + '0', WAIT_FOR_REVIEW_INTERVAL, function(e) {
        e.style.display = ""; 
    });
    
    for (var groupIdx = 0; groupIdx < this.reviewGroups.length; groupIdx++) {
        (function() {
            var page = groupIdx; /* close over outer variable */
            waitFor(this.reviewPageIdPrefix + page, WAIT_FOR_REVIEW_PAGE_INTERVAL, function(e) {
                e.onclick = function(e) {
                    if (page != this.currentPageIdx && this.throttle.isFinished()) {
                        this.throttle.start();
                        this.showPage(page);
                    }
                }.bind(this);
                if (page == 0) {
                    e.className = "hotelInfoTab_guestComments_link_active";
                }
            }.bind(this));
        }).bind(this)();
    }
    
}

HotelMapGuestComments.prototype.showPage = function(pageIdx) {
    Effect.Puff(this.reviewGroupIdPrefix + this.currentPageIdx, {
        'queue' : 'front',
        afterFinish: function() {
            this.throttle.checkpoint();
        }.bind(this)
    });
    Effect.Appear(this.reviewGroupIdPrefix + pageIdx, {
        'queue' : 'end',
        afterFinish: function() {
            this.throttle.checkpoint();
        }.bind(this)
    });
    document.getElementById(this.reviewPageIdPrefix + this.currentPageIdx).className = "hotelInfoTab_guestComments_page_link";
    document.getElementById(this.reviewPageIdPrefix + pageIdx).className = "hotelInfoTab_guestComments_link_active";
    this.currentPageIdx = pageIdx;
}

HotelMapGuestComments.prototype.TEMPLATE             = 'hotelInfoTab_guestComments';
HotelMapGuestComments.prototype.NO_COMMENTS_TEMPLATE = 'hotelInfoTab_noGuestComments';
HotelMapGuestComments.prototype.REV_GROUP_PREF       = 'hotelInfoTab_guestComments_reviewGroup_';
HotelMapGuestComments.prototype.REV_PAGE_PREF        = 'hotelInfoTab_guestComments_page_link_';

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

/**
 * Show Landmark Control;
 * 
 * We defer the definition of this class as it requires the google maps javascript to be loaded to instantiate the parent prototype.
 */

function defineShowLandmarkControl() {
    
    function ShowLandmarkControl(map) {
        this.showLandmarks = false;
        this.userOverride  = false; // user is controlling landmarks manually
        this.hotelMap      = map;
    }
    
    ShowLandmarkControl.prototype = new GControl();
    
    ShowLandmarkControl.prototype.initialize = function(map) {
        var container = document.createElement("div");
        container.innerHTML = tmpl("map_showLandmarkControl", {});

        map.getContainer().appendChild(container);
        $('map_showLandmarkControl_checkbox').onclick = function() {
            this.toggle();
        }.bind(this);
        this.updateCheckbox();
        return container;
    }
    
    ShowLandmarkControl.prototype.getDefaultPosition = function() {
        return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(10, 26));
    }
    
    ShowLandmarkControl.prototype.updateCheckbox = function(zoomLevel) {
        if (!this.userOverride) {
            this.showLandmarks = zoomLevel >= LANDMARK_ZOOM_THRESHOLD;
            this._setCheckBoxValue(this.showLandmarks ? "checked" : "");
        }
    }
    
    ShowLandmarkControl.prototype.isShowingLandmarks = function() {
        return this.showLandmarks;
    }
    
    ShowLandmarkControl.prototype.toggle = function() {
        this.userOverride  = true;
        this.showLandmarks = !this.showLandmarks;
        this._setCheckBoxValue(this.showLandmarks ? "checked" : "");
        this.hotelMap.refreshAsync();
    }
    
    ShowLandmarkControl.prototype._setCheckBoxValue = function(checked) {
        var cb = $('map_showLandmarkControl_checkbox');
        if (cb) {
            cb.checked = checked;
        }
    }
    
    return ShowLandmarkControl;
}

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

MOVE_STATUS_FIXED  = 1
MOVE_STATUS_MOVING = 2;
function LandmarkInfo(map, overlay, landmark) {
    this.hotelMap = map;
    this.overlay  = overlay;
    this.landmark = landmark;
    if (!overlay['_moveStatus']) {
        overlay._moveStatus = MOVE_STATUS_FIXED;
    }
    this.html     = tmpl("map_landmark_info", {
        'landmarkName' : landmark.name,
        'landmarkUrl' : landmark.website_url,
        'landmarkPhotoUrl' : landmark.photo_url,
        'landmarkDescription' : landmark.description,
        'landmarkClassification' : landmark.classification,
        'moveLabel' : (overlay['_moveStatus'] == MOVE_STATUS_FIXED) ? "Move" : "Set Position",
        'moveId'    : (overlay['_moveStatus'] == MOVE_STATUS_FIXED) ? "move" : "setpos",
        'canEdit'   : this.hotelMap.options['edit']
    });

    waitFor("map_landmark_info_edit_link", DEFAULT_INTERVAL, function(e) {
        e.onclick = function() {
            this.hotelMap.map.updateInfoWindow([new GInfoWindowTab("_",
                    new EditLandMark(this.hotelMap, this.overlay, this.landmark).html)]);
        }.bind(this);
    }.bind(this));

    waitFor("map_landmark_info_move_link", DEFAULT_INTERVAL, function(e) {
        e.onclick = function() {
            this.overlay._moveStatus = MOVE_STATUS_MOVING;
            this.overlay.enableDragging();
            this.hotelMap.map.closeInfoWindow();
        }.bind(this);
    }.bind(this));
    
    waitFor("map_landmark_info_setpos_link", DEFAULT_INTERVAL, function(e) {
        e.onclick = function() {
            this.overlay._moveStatus = MOVE_STATUS_FIXED;
            this.overlay.disableDragging();
            this.updateLandmarkPosition();
        }.bind(this);
    }.bind(this));
    
    waitFor("map_landmark_info_remove_link", DEFAULT_INTERVAL, function(e) {
        e.onclick = function() {
            this.hotelMap.removeLandmark(this.overlay._dataIdx);
        }.bind(this);
    }.bind(this));
    
    waitForAll(["map_landmark_info_img", "map_landmark_info_heading", "map_landmark_info_body"], DEFAULT_INTERVAL, function(elements) {
        var img  = elements[0];
        var head = elements[1];
        var body = elements[2];
        
        body.style.height = (img.offsetHeight - head.offsetHeight) + 'px';
    }.bind(this));
}

LandmarkInfo.prototype.updateLandmarkPosition = function() {
    new Ajax.Request('/chang/maps_admin.php/landmarks/updatePosition', {
        method: 'POST',
        parameters: {
            id:        this.landmark.id,
            longitude: this.overlay.getLatLng().lng(),
            latitude:  this.overlay.getLatLng().lat()
        },
        onSuccess: function(transport) {
            this.landmark.lat = this.overlay.getLatLng().lat();
            this.landmark.lng = this.overlay.getLatLng().lng();
            this.hotelMap.map.closeInfoWindow();
        }.bind(this),
        onFailure: function() { },
        onException: function(e, f) { }
    });
}

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

_editLabelIdCounter = 0;
function EditLabel(map, label) {
    this.map = map;
    this.label = label;
    this.idSuffix = _editLabelIdCounter++;
    this.html = tmpl("template_edit_label", {'idSuffix' : this.idSuffix});
    waitFor("cancelEditLabel" + this.idSuffix, DEFAULT_INTERVAL, function(b) {
        
    }.bind(this));
    waitFor("saveEditLabel" + this.idSuffix, DEFAULT_INTERVAL, function(b) {
        b.onclick = function() {
            this.label.name = this.getLabelText();
            this.label.anchor = this.getLabelAnchor();
            this.map.refreshAsync();
        }.bind(this);
    }.bind(this));
}

EditLabel.prototype.getLabelText = function() {
    return $('editLabelText' + this.idSuffix).value;
}

EditLabel.prototype.getLabelAnchor = function() {
    return $('editLabelAnchor' + this.idSuffix).value;
}

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

_idCounter = 0;
function EditLandMark(map, marker, landmark, idPath, path) {
	
    this.idSuffix = _idCounter++;
    this.map      = map;
    this.marker   = marker;
    this.landmark = landmark;

    if (this.landmark != null) {
    	this.html = tmpl("template_new_landmark", this.getLandmarkValues(landmark));

    } else {
    	this.html = tmpl("template_new_landmark", this.getDefaultLandmarkValues());

    }

    waitFor("landmark_classification" + this.idSuffix, DEFAULT_INTERVAL, function(select) {
       ajax({
           serviceName : 'landMarkClassifications',
           url         : '/chang/maps.php/json/loadLandmarkClassifications?serviceName=landMarkClassifications',
           callback    : function(data) {
               var values = data.evalJSON();
               var selectedIndex = 0;
               for (var i = 0; i < values.length; i++) {
                   var option = new Option(values[i].name, values[i].id);
                   if (this.landmark != null && this.landmark.classification == values[i].name) {
                       selectedIndex = i;
                   }
                   select.options[select.options.length] = option;
               }
               select.selectedIndex = selectedIndex;
           }.bind(this)
        });
    }.bind(this));
    
    waitFor("landmark_tags" + this.idSuffix, DEFAULT_INTERVAL, function(select) {
    	var url = '/chang/maps.php/json/loadLandmarkTags?serviceName=landMarkTags';
    	if(this.landmark != null) {
    		url += '&landmarkId=' + this.landmark.id.toString();
    	}
        ajax({
            serviceName : 'landMarkTags',
            url         : url,
            callback    : function(data) {

        		var values = data.evalJSON();
                var selectedIndex = 0;
                for (var i = 0; i < values.tags.length; i++) {
                    var option = new Option(values.tags[i].name, values.tags[i].id);
                    select.options[select.options.length] = option;
                }
                $("landmark_tags" + this.idSuffix).setValue(values.selectedTags);
                $j("select#landmark_tags" + this.idSuffix).multiselect({sortable: false, dividerLocation: 0.50});
            }.bind(this)
         });
     }.bind(this));
     
    waitFor("button_cancel_landmark" + this.idSuffix, DEFAULT_INTERVAL, function(b) {
        b.onclick = function() { this.map.map.closeInfoWindow(); }.bind(this);
    }.bind(this));
    
    waitFor("button_save_landmark" + this.idSuffix, DEFAULT_INTERVAL, function(b) {
        b.onclick = function() {
            var pos = this.marker.getLatLng();
            new Ajax.Request('/chang/maps_admin.php/landmarks/save', {
                method: 'POST',
                parameters: {
                    id:             this.landmark != null ? this.landmark.id : null,
                    longitude:      pos.lng(),
                    latitude:       pos.lat(),
                    name:           this.getParam('landmark_name'),
                    description:    this.getParam('landmark_description'),
                    classification: this.getParam('landmark_classification'),
                    tags:			[$F('landmark_tags' + this.idSuffix)],
                    photoUrl:       this.getParam('landmark_photo_url'),
                    websiteUrl:     this.getParam('landmark_website_url')
//                    destinationId:  getSelectedDestinationId() //////////////// when this is removed a landmark can't be saved
                },
                onSuccess: function(transport) {
                    var lm = transport.responseText.evalJSON().landmark;
                    this.map.locations[this.marker._dataIdx] = lm; // replace existing location
                    this.map.refreshAsync();
                }.bind(this),
                onFailure: function() { },
                onException: function(e, f) { }
            });
        }.bind(this);
    }.bind(this));
}

EditLandMark.prototype.getDefaultLandmarkValues = function() {
    return {
        'idSuffix'     : this.idSuffix,
        'name'         : '&lt;Enter Name&gt;',
        'url'          : 'Enter url',
        'photoUrl'     : 'Enter photo url',
        'description'  : 'Enter description'
    };
}

EditLandMark.prototype.getLandmarkValues = function(lm) {
    return {
        'idSuffix'    : this.idSuffix,
        'name'        : lm.name,
        'url'         : lm.website_url,
        'photoUrl'    : lm.photo_url,
        'description' : lm.description
    }
}

EditLandMark.prototype.getParam = function(name) {
	elem = $(name + this.idSuffix);
	if (elem.tagName == 'SELECT') {
		return elem.value;
	} else {
		return elem.readAttribute('value');
	}
}

function defineMapLabel() {
    
  function MapLabel(point) {
    this.point = point;
  }
  
  MapLabel.prototype = new GOverlay();
  
  MapLabel.prototype.initialize = function(map) {
      this.map = map;
      var container = document.createElement("div");
      container.className = 'mapLabel';
      container.innerHTML = "Hello World!!!";

      map.getPane(G_MAP_FLOAT_SHADOW_PANE).appendChild(container);
      this.div = container;
      return container;
  }
  
  MapLabel.prototype.redraw = function(force) {
      var p = this.map.fromLatLngToDivPixel(this.point);
      var h = parseInt(this.div.clientHeight);
      this.div.style.left = p.x + "px";
      this.div.style.top  = (p.y - h) + "px";
  }
  
  return MapLabel;
}

function defineOverlayWidgets() {
	
    function DraggableOverlay(node, anchor, point) {
        this.point     = point;
        this.node      = node;
        this.anchor    = anchor;
        this.draggable = true;
        this.dragging  = false;
    }
    
    DraggableOverlay.prototype = new GOverlay();
    
    DraggableOverlay.prototype.setPoint = function(point) {
        this.point = point;
    };
    
    DraggableOverlay.prototype.setAnchor = function(anchor) {
        this.anchor = anchor;
    };
    
    // override this method to create a custom node for the overlay
    DraggableOverlay.prototype.createNode = function() {
    	if (this.node != null) return this.node;
        return document.createElement("div");
    };
    
    DraggableOverlay.prototype.initialize = function(map) {
        this.map  = map;
        this.node = this.createNode();
        GEvent.addDomListener(this.node, "click", function (e) {
            if (!this.dragging)
                GEvent.trigger(map, "click", this, this.point, this.point);
        }.bind(this));
        map.getPane(G_MAP_MARKER_PANE ).appendChild(this.node);
        if (this.draggable) {
//        	console.log('is draggable');
            new Draggable(this.node, {
                onStart : function() {
                    this.dragging = true;
                }.bind(this),
                onEnd: function() {
                    // reset map point
                    var latlng = map.fromDivPixelToLatLng(new GPoint(
                            this.node.offsetLeft + this.calculateLabelWidth(),
                            this.node.offsetTop));
                    this.point = latlng;
                    GEvent.trigger(this, "move", this, this.point);
                    setTimeout(function() { this.dragging = false; }.bind(this), 1000); // use a timeout to avoid the 'click' event being fired
                }.bind(this)
            });
        }
        return this.node;
    };
    
//    DraggableOverlay.prototype.overlaps = function(other, d1, d2) {
//    	console.log('overlaps');
//    	var p = this.map.fromLatLngToDivPixel(this.point);
//    	var minX = p.x;
//    	var minY = p.y;
//    	var maxX = p.x + this.node.clientWidth;
//    	var maxY = p.y + this.node.clientHeight;
//    	
//    	var op = this.map.fromLatLngToDivPixel(other.point);
//    	var ominX = op.x;
//    	var ominY = op.y;
//    	var omaxX = op.x + other.node.clientWidth;
//    	var omaxY = op.y + other.node.clientHeight;

    	//console.log(d1.name + ' vs ' + d2.name);
    	//console.log('minX=' + minX + ', maxX=' + maxX + ', minY=' + minY + ', maxY=' + maxY);
    	//console.log('ominX=' + ominX + ', omaxX=' + omaxX + ', ominY=' + ominY + ', omaxY=' + omaxY);
    	
//    	var xBetween = false;
//    	var yBetween = false;
//    	
//    	if (ominX > minX && ominX < maxX) {
//    		xBetween = true;
//    	} else if (omaxX > minX && omaxX < maxX) {
//    		xBetween = true;
//    	}
//    	
//    	if (ominY > minY && ominY < maxY) {
//    		yBetween = true;
//    	} else if (omaxY > minY && omaxY < maxY) {
//    		yBetween = true;
//    	}
//    	
//    	return yBetween && xBetween;
    	
    	/*return
    	    ((((ominX > minX) && (ominX < maxX)) ||
    	      ((omaxX > minX) && (omaxX < maxX))) &&
    	
    	     (((ominY > minY) && (ominY < maxY)) ||
    	      ((omaxY > minY) && (omaxY < maxY))));*/
//    };

    /*DraggableOverlay.prototype.redraw = function(force) {
        this._updateNodePosition(this.node);
    }
    
    DraggableOverlay.prototype._updateNodePosition = function(node) {
        var width       = this.calculateLabelWidth();
        var p           = this.map.fromLatLngToDivPixel(this.point);
        var h           = 0; // parseInt(node.clientHeight);
        node.style.left = (p.x - width) + "px";
        node.style.top  = (p.y - h) + "px";        
    }*/

    DraggableOverlay.prototype.redraw = function(force) {
        this._updateNodePosition(this.node);
    };

    DraggableOverlay.prototype._updateNodePosition = function(node) {
        var p           = this.map.fromLatLngToDivPixel(this.point);
        node.style.left = p.x + "px";
        node.style.top  = p.y + "px";        
    };
    
    DraggableOverlay.prototype.calculateLabelWidth = function() {
        if (this.anchor == 'left')
            return this.node.clientWidth;
        return 0;
    };
    
    DraggableOverlay.prototype.remove = function() {
        this.node.parentNode.removeChild(this.node);
    };
    
    function MapLabelOverlay(label, point) {
        this.label = label;
        this.setPoint(point);
        this.setAnchor(label.anchor);
    }
    
    MapLabelOverlay.prototype = new DraggableOverlay();
    
    MapLabelOverlay.prototype.createNode = function() {
        var node       = document.createElement("div");
        node.className = 'mapLabel';
        node.innerHTML = '&#8226;' + l.name;
        var zoomDiff   = l.zoom - this.map.getZoom();
        var fontSize   = 14 - (zoomDiff * 2);
        node.style.fontSize = fontSize + 'px';
    };

    
    function LocationLabelOverlay (location) {
    	this.location = location;
    };
    
    LocationLabelOverlay.prototype = new GOverlay();

    LocationLabelOverlay.prototype.initialize = function(map) {
    	var div = document.createElement('div');
    	div.style.border = '1px solid black';
    	div.style.background = '#ffffff';
    	div.style.position = 'absolute';
    	div.style.padding = '2px 3px';
    	div.style.width = '100px';
    	div.style.textAlign = 'center';
    	div.className = 'location-label';
    	div.innerHTML = this.location.name;
    	
    	map.getPane(G_MAP_FLOAT_PANE).appendChild(div);
    	
    	this.map = map;
    	this.div = div;
    };

    LocationLabelOverlay.prototype.remove = function() {
    	if (this.div.parentNode) {
    		this.div.parentNode.removeChild(this.div);
    	}
    };

    LocationLabelOverlay.prototype.redraw = function(force) {
    	if (!force) {
    		return;
    	}
    	if (this.map.getZoom() < 15) {
    		this.remove();
    		return;
    	}
    	
    	iconLoc = this.map.fromLatLngToDivPixel(new GLatLng(this.location.lat, this.location.lng));
    	this.div.style.top = ((iconLoc.y - 24) + 12).toString() + 'px';
    	this.div.style.left = ((iconLoc.x - 44) - 13).toString() + 'px'; 
    };
    
    window.DraggableOverlay = DraggableOverlay;
    window.MapLabelOverlay  = MapLabelOverlay;
    window.LocationLabelOverlay  = LocationLabelOverlay;
}



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

/**
 * Location List Control
 * 
 * We defer the definition of this class as it requires the google maps javascript to be loaded to instantiate the parent prototype.
 */
function defineLocationListControl() {

var idSuffix = 0;
    
function LocationListControl(hotelMap) {
    this.idSuffix = idSuffix++;
    this.hotelMap = hotelMap;
    this.list     = null;
    this.open     = false;
    this.subLists = {};
    this.selectedSubList = "showHotelSubList";
}

LocationListControl.prototype = new GControl();

/**
 * Determine whether or not the list has been created yet
 */
LocationListControl.prototype.isInitialized = function() {
    return this.list != null;
}

/**
 * Method called by the Google Maps API to intialize the control
 */
LocationListControl.prototype.initialize = function(map) {
	this.hotelMap.container.parentNode.style.marginBottom = "355px";
    var container = document.createElement("div");
    container.style.display = "none";
    //container.innerHTML = tmpl("map_locationList_openButton", {idSuffix: this.idSuffix});

    //container.onclick = function(e) {
    //    if (this.open) {
    //        this.closeList();
    //    } else {
    //        this.openList();
    //    }
    //}.bind(this);

    //map.getContainer().appendChild(container);
    this.createList();
    return container;
}

LocationListControl.prototype.forEachSubList = function(callback) {
    for (var k in this.subLists) {
        var l = this.subLists[k];
        callback(l);
    }
}

/**
 * Readjust the height of the list such that it matches that of the map provided.
 */
LocationListControl.prototype.setHeightBasedOnMap = function(map) {
	return;
    if (this.list == null) return;
    this.listHeight        = map.getContainer().offsetHeight - 1;
    this.listHeightNoNav   = this.listHeight - 16;
    this.list.style.height = this.listHeight + 'px';
    this.forEachSubList(function (l){
        l.setHeight(this.listHeightNoNav);
    }.bind(this));
}

LocationListControl.prototype.refresh = function() {
    if (this.list == null) return;
    this.forEachSubList(function (l) {
        l.refreshAllLocations();
        l.refreshVisibleLocations();
    }.bind(this));
}

LocationListControl.prototype.getDefaultPosition = function() {
    return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(5, 5));
}

LocationListControl.prototype.getListHeight = function() {
    return this.listHeightNoNav;
}

LocationListControl.prototype.toggleSubList = function(listName, dontOpen) {
    Element.removeClassName(this.list, 'show' + this.selectedSubList + 'SubList');
    this.selectedSubList = listName;
    Element.addClassName(this.list, 'show' + this.selectedSubList + 'SubList');
    this.forEachSubList(function (l) {
        l.updateAllLocationsHeight();
    }.bind(this));
    if (!this.open && !dontOpen) {
        this.openList();
    }
}

LocationListControl.prototype.addSubList = function(
        toggleName,
        allMarkersFunc,
        visibleMarkersFunc) {
    var subList = new LocationSubList(this, this.hotelMap, allMarkersFunc, visibleMarkersFunc, toggleName);
    this.subLists[toggleName] = subList;
    return subList;
}

LocationListControl.prototype.createList = function() {
    this.hotelMap.map.getContainer().style.zIndex = 89;
    this.list           = document.createElement("div");
    this.list.id        = "map_allLocationsList_container" + this.idSuffix;
    this.list.className = "map_allLocationsList_container";
    //document.body.appendChild(this.list);
    Element.insert(this.hotelMap.container.parentNode, {'bottom' : this.list});
    
    this.addSubList('map_toggleHotels',
            function() {
                return this.hotelMap.getAllHotels();
            }.bind(this),
            function() {
                return this.hotelMap.getVisibleHotels();
            }.bind(this)
    );

    this.addSubList('map_toggleLandmarks',
            function() {
                return this.hotelMap.getAllLandmarks();
            }.bind(this),
            function() {
                return this.hotelMap.getVisibleLandmarks();
            }.bind(this)
    );
    
    this.toggleSubList('Hotel', true);
        
    this.setHeightBasedOnMap(this.hotelMap.map);
    
    var offset = Element.cumulativeOffset(this.hotelMap.map.getContainer());
    //this.list.style.left = offset.left + 'px';
    //this.list.style.top  = offset.top  + 'px';
    
    GEvent.addListener(this.hotelMap.map, "zoomend", function(oldLevel, newLevel) {
        this.forEachSubList(function (l) {
            l.refreshVisibleLocations();
        }.bind(this));
    }.bind(this));
    
    GEvent.addListener(this.hotelMap.map, "moveend", function() {
        this.forEachSubList(function (l) {
            l.refreshVisibleLocations();
            
        }.bind(this));
    }.bind(this));
}

LocationListControl.prototype.openList = function() {
    if (this.list == null) {
        this.createList();
    }
    this.list.style.visibility = 'visible';
    return;
    new Effect.Move('map_allLocationsList_container' + this.idSuffix, {
        x: -235,
        y: 0,
        mode: 'relative',
        queue: {
            position: 'front',
            scope: 'allLocations',
            limit: 1
        },
        afterFinish: function() {
            this.open = true;
            $('openLocListButton' + this.idSuffix).innerHTML = 'Close List';
        }.bind(this)
    });
}

LocationListControl.prototype.closeList = function() {
    new Effect.Move('map_allLocationsList_container' + this.idSuffix, {
        x: 235,
        y: 0,
        mode: 'relative',
        queue: {
            position: 'front',
            scope: 'allLocations',
            limit: 1
        },
        afterFinish: function(effect) {
            this.open = false;
            $('openLocListButton' + this.idSuffix).innerHTML = 'Hotel List';
            //effect.element.style.visibility = 'hidden';
        }.bind(this)
    });        
}

return LocationListControl;

}

/**
 * Represents a toggleable section on the locaiton list that will expose both 'all items' and 'now showing' for a particular location type.
 * (e.g. hotels, landmarks).
 */
LOCATION_SUB_LIST_ID_SUFFIX = 0;
function LocationSubList(parent, hotelMap, allMarkersFunc, visibleMarkersFunc, toggleName) {
    this.idSuffix           = LOCATION_SUB_LIST_ID_SUFFIX++;
    this.parent             = parent;
    this.hotelMap           = hotelMap;
    this.allMarkersFunc     = allMarkersFunc;
    this.visibleMarkersFunc = visibleMarkersFunc;
    this.toggleName         = toggleName;

    this.list           = document.createElement("div");
    this.list.className = "map_allLocationsList_subList " + toggleName;
    this.list.innerHTML = tmpl("map_allLocationsList", {idSuffix : this.idSuffix});
    this.parent.list.appendChild(this.list);
    
    var viewAllTab = $("map_allLocationsNav_all" + this.idSuffix);
    var viewVisTab = $("map_allLocationsNav_vis" + this.idSuffix);
    
    $("map_toggleHotels" + this.idSuffix).onclick = function() {
        this.parent.toggleSubList('Hotel');
    }.bind(this);
    
    $("map_toggleLandmarks" + this.idSuffix).onclick = function() {
        this.parent.toggleSubList("Landmark");
    }.bind(this);

    $(this.toggleName + this.idSuffix).removeClassName("toggleInactive");
    $(this.toggleName + this.idSuffix).addClassName("toggleActive");
    
    viewAllTab.onclick = function(e) {
        viewAllTab.removeClassName("map_allLocationsNav_tab_hidden");
        viewVisTab.addClassName("map_allLocationsNav_tab_hidden");
        $("map_allLocationsList_all" + this.idSuffix).style.display = "block";
        $("map_allLocationsList_vis" + this.idSuffix).style.display = "none";
        this.updateAllLocationsHeight();
    }.bind(this);
    
    viewVisTab.onclick = function(e) {
        viewVisTab.removeClassName("map_allLocationsNav_tab_hidden");
        viewAllTab.addClassName("map_allLocationsNav_tab_hidden");
        $("map_allLocationsList_vis" + this.idSuffix).style.display = "block";
        $("map_allLocationsList_all" + this.idSuffix).style.display = "none";
        this.updateAllLocationsHeight();
    }.bind(this);
    
    this.allLocations = this.buildLocationList(
            this.sortMarkers(this.allMarkersFunc()),
            'map_allLocationsList_all',
            this.toggleName + '_items');
    
    this.visibleLocations = this.buildLocationList(
            this.sortMarkers(this.visibleMarkersFunc()),
            'map_allLocationsList_vis',
            this.toggleName + '_items');

    // show visible locations by default
    this.allLocations.style.display     = 'none';
    this.visibleLocations.style.display = 'block';    
}

LocationSubList.prototype.sortMarkers = function(markers) {
    var locations = this.hotelMap.locations;
    var sortedMarkers = markers.clone();
    
    sortedMarkers.sort(function (a, b) {
        var nameA = locations[a._dataIdx].name;
        var nameB = locations[b._dataIdx].name;
        if (nameA > nameB) {
            return 1;
        } else if (nameA == nameB) {
            return 0;
        }
        return -1;
    });
    
    return sortedMarkers;
}

LocationSubList.prototype.refreshVisibleLocations = function() {
    var currentStyle = document.getElementById("map_allLocationsList_vis" + this.idSuffix).style.display;
    this.visibleLocations.remove();
    this.visibleLocations = this.buildLocationList(this.sortMarkers(this.visibleMarkersFunc()), 'map_allLocationsList_vis', this.toggleName + '_items');
    document.getElementById("map_allLocationsList_vis" + this.idSuffix).style.display = currentStyle;
    this.updateAllLocationsHeight();
}

LocationSubList.prototype.refreshAllLocations = function() {
    var currentStyle = $('map_allLocationsList_all' + this.idSuffix).style.display;
    this.allLocations.remove();
    this.allLocations = this.buildLocationList(this.sortMarkers(this.allMarkersFunc()), 'map_allLocationsList_all', this.toggleName + '_items');
    $('map_allLocationsList_all' + this.idSuffix).style.display = currentStyle;        
}

LocationSubList.prototype.buildLocationList = function(markers, listId, itemsTemplate) {
    var parentContainer = document.getElementById("map_allLocationsList_data" + this.idSuffix);
    var container = new Element("div");
    container.id = listId + this.idSuffix;
    container.className = listId;
  
    container.innerHTML += tmpl(itemsTemplate, {
        'markers'   : markers,
        'locations' : this.hotelMap.locations,
        'listId'    : listId,
        'idSuffix'  : this.idSuffix
    });
    parentContainer.appendChild(container);
            
    var items = container.getElementsByClassName("map_allLocationsList_item");
    for (var itemIdx = 0; itemIdx < items.length; itemIdx++) {
        (function() {
            var item = items[itemIdx];
            var dataIdx = item.readAttribute("dataIdx");
            var dataLocId = item.readAttribute("dataLocId");
            item.onmouseover = function() { this.itemMouseOver(item, dataIdx, dataLocId); }.bind(this);
            item.onmouseout  = function() { this.itemMouseOut(item, dataIdx, dataLocId); }.bind(this);
            item.onclick     = function() { this.itemClick(item, dataIdx, dataLocId); }.bind(this);           
        }).bind(this)();
    }
    
    return container;
}

LocationSubList.prototype.itemMouseOver = function(node, dataIdx, dataLocId) {
    node.style.backgroundColor = "#A1D1EC";
    var marker = this.hotelMap.clusters[this.hotelMap.allMarkers['id_' + dataLocId]._cIdx][0];
    if (this.hotelMap.map.getBounds().containsLatLng(marker.getLatLng())) {
        this.hotelMap.showToolTip(marker, {'tooltip' : this.hotelMap.locations[dataIdx].name});
    }
}

LocationSubList.prototype.itemMouseOut = function(node, dataId, dataLocIdx) {
    node.style.backgroundColor = "#FFFFFF";
}

LocationSubList.prototype.itemClick = function(node, dataIdx, dataLocId) {
    STOP_WATCH.go();
    var marker  = this.hotelMap.allMarkers['id_' + dataLocId];
    var locData = this.hotelMap.locations[dataIdx];
    this.hotelMap.zoomTarget = marker;
    var zoom = this.hotelMap.calculateOptimalZoom(marker, this.hotelMap.allMarkers, 15);
    STOP_WATCH.checkpoint("Calculated zoom level based on surrounding markers");
    var currentZoom = this.hotelMap.map.getZoom();
    this.hotelMap.map.setCenter(new GLatLng(locData.lat, locData.lng), zoom);
    STOP_WATCH.checkpoint("Re-centered and zoomed map");
    if (zoom == currentZoom) {
        // if we have not changed zoom level, avoid doing a full refresh
        this.hotelMap.zoomTarget = null;
        this.hotelMap.showMarkerInfo(marker);
        STOP_WATCH.checkpoint("Single zoom refresh");
    }
}

/**
 * This function is required to pad out the height of the data section if it is less than
 * the desired height of the list (i.e. the height of the map).  It should be called whenever the height of
 * either the map, or the data in the list changes.
 */
LocationSubList.prototype.updateAllLocationsHeight = function() {
    var trans = document.getElementById('map_allLocationsList_trans' + this.idSuffix);
    var data  = document.getElementById('map_allLocationsList_data' + this.idSuffix);
    data.style.height = 'auto';
   
    if (data.offsetHeight < this.height) {
        data.style.height = this.height + 'px';
    }
    trans.style.height = data.offsetHeight + 'px';
}

LocationSubList.prototype.setHeight = function(heightPx) {
    this.height = heightPx;
    $("map_allLocationsList_outer" + this.idSuffix).style.height = heightPx + 'px';
}
  

