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 setupDestinationLists(selectedDestIdPath, selectedDestPath, selectIds) {
    if (!selectIds) {
        selectIds = {
            'countrySelect' : 'countrySelect',
            'regionSelect'  : 'regionSelect',
            'areaSelect'    : 'areaSelect'
        };
    }
    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;

    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++;
	}
	if (index == list.length) { // finished
		callback();
	} else {
		window.setTimeout(function() {
			processListAsync(list, batchSize, processor, callback, index);
		}, 0.05);
	}
}  
addScript('http://www.asiawebdirect.com/js/prototype.js',     'Prototype');
addScript('http://www.asiawebdirect.com/js/scriptaculous.js', 'Scriptaculous');
addScript('http://www.asiawebdirect.com/js/effects.js',       'Effect');
addScript('http://www.asiawebdirect.com/js/dragdrop.js',      'Droppables');
addCss('http://www.asiawebdirect.com/css/maps.css', {nodeType: 'div', className: '_marker_class', property: 'zIndex', value: '999'});

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;

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;

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

VERBOSITY_MAXIMUM = 100;
VERBOSITY_MEDIUM  = 50;
VERBOSITY_MINIMUM = 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';
    container.appendChild(this.container);
    this.options    = options;
    this.zoomTarget = null;
    this.locations  = [];
    this.highlightedLocations = null;
    this.origLocations = null;
    this.labels     = [];
    this.allMarkers = [];
    this.visibleMarkers = [];
    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_MAXIMUM) {
      this.map.addControl(new GLargeMapControl(), new GControlPosition(new GSize(5, 5), new GSize(3, 40)));
      this.map.addControl(new GMapTypeControl());
    }
    this.createMapTypes();
    
    GEvent.addListener(this.map, "zoomend", function(oldLevel, newLevel) {
        if (this.showLandmarkControl != null) {
            this.showLandmarkControl.updateCheckbox(newLevel);
        }
        this.refreshAsync();
    }.bind(this));
    
    GEvent.addListener(this.map, "moveend", function() {
        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://www.asiawebdirect.com/chang/images/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://www.asiawebdirect.com/chang/images/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: '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';
        this.landmark_icons[c.name] = this.createIcon('http://www.asiawebdirect.com/chang/images/maps/landmarks/' + 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);
    }
}

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);
        }
    } else if (overlay._markerType == MARKER_TYPE_LABEL) {
        var label = this.labels[overlay._dataIdx];
        this.map.openInfoWindowHtml(overlay.point, new EditLabel(this, label).html);
    }
}

HotelMap.prototype.createMapTypes = function() {
    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() {
    var visibleMarkers = [];
    var mapBounds = this.map.getBounds();
    for (var i = 0; i < this.allMarkers.length; i++) {
        var marker = this.allMarkers[i];
        if (mapBounds.containsLatLng(marker.getLatLng())) {
            visibleMarkers.push(marker);
        }
    }
    this.visibleMarkers = visibleMarkers;
}

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) {
    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];
	}
	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://www.asiawebdirect.com/chang/images/maps/cluster-icon.gif";
			}
		}
		return "http://www.asiawebdirect.com/chang/images/maps/cluster-icon-fade.png";
	}
	return "http://www.asiawebdirect.com/chang/images/maps/cluster-icon.gif";
}

HotelMap.prototype.refreshAsync = function(finishCallback) {
	if (this._refreshing) {
		this._refreshQueued = true;
		if (finishCallback) this._refreshCbQueue.push(finishCallback);
		return;
	}
    this._refreshing = true;
    finishCallbacks = this._refreshCbQueue.clone();
    this._refreshCbQueue = [];
    if (finishCallback) {
    	finishCallbacks.push(finishCallback);
    }

	this.map.clearOverlays();
    var mapBounds = this.map.getBounds();
    
    var allMarkers     = [];
    var lmMarkers      = [];
    var visibleMarkers = [];

    processListAsync(this.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;
	
	        m._dataIdx = i;
	        allMarkers.push(m);
	
	        if (mapBounds.containsLatLng(m.getLatLng()))
	            visibleMarkers.push(m);
	        
	        if (m._markerType == MARKER_TYPE_LANDMARK)
	            lmMarkers.push(m);
    	}.bind(this),
    	function () {
    	    this.visibleMarkers  = visibleMarkers;
    	    this.landmarkMarkers = lmMarkers;
    	    this.allMarkers      = allMarkers.clone(); // we clone the array as the clusterMarkers function removes elements from the original    		
    	    var clusters = this.clusterMarkers(allMarkers, this.clusterRadius, this.map.getZoom());
    	    
    	    processListAsync(clusters, 30, function(clist, _, cIdx) {
    	    	    var c = clist[0];
	    	        c._clusterIdx = cIdx;
	    	        this.addMarkerToolTipEvent(c);
	    	        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() {
    	    	    this.clusters = clusters;
    	    	    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;
    	    	    	this.refreshAsync();
    	    	    } else {
    	    	    	// all pending refreshes complete, fire global event
    	    	    	document.fire("map:refreshComplete");
    	    	    }
    	    	}.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;
    this.landmarkMarkers = lmMarkers;
    this.allMarkers      = allMarkers.clone(); // we clone the array as the clusterMarkers function removes elements from the original
    
    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.shouldAddOverlay = function(o) {
    if (o._markerType == MARKER_TYPE_LANDMARK && this.showLandmarkControl != null) {
        return this.showLandmarkControl.isShowingLandmarks();
    }
    return true;
}

HotelMap.prototype.addMarkerToolTipEvent = function(marker) {
    GEvent.addListener(marker, "mouseover", function() { this.showToolTip(marker, {'showClusterText' : true}); }.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']) {
        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();
    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));
        }
    }
    var zoom = this.map.getBoundsZoomLevel(bounds);
    this.map.setCenter(bounds.getCenter(), zoom);
    if (zoom == currentZoom && forceRefresh) {
    	this.refreshAsync();
    }
}

HotelMap.prototype.showHighlightedLocationPopup = function() {
	for (var mIdx = 0; mIdx < this.allMarkers.length; mIdx++) {
		var marker = this.allMarkers[mIdx];
		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.loadDestination = function(name, options) {
    ajax({
        serviceName: 'loadDestination',
        url: 'http://www.asiawebdirect.com/chang/maps.php/json/loadByDestName?serviceName=loadDestination&destMatch=' + name,
        callback: function(data) {
            var json = data.evalJSON();
            this.locations = json.hotels.concat(json.landmarks);
            this.allMarkers = [];
            this.visibleMarkers = [];
            if (!options['keepMapPos']) {
                this.initializeZoomAndCenter();
            }
            this.refreshAsync(function () {
                if (options['openLocList']) {
                    this.locationList.openList();
                }
                if (options['onload']) {
                	options.onload(this, json);
                }            	
            }.bind(this));
        }.bind(this)
    });
}

/**
 * Cross-domain comptaible 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.locations = data.evalJSON().hotels;
            this.allMarkers = [];
            this.visibleMarkers = [];
            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) {
    var clustered = []
    /* Loop until all markers have been compared. */
    while (markers.length) {
        var marker  = markers.pop();
        if (marker == undefined) {
            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
                
                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) { }
    });
}

/* -------------------------------------------------------------------------------------------- */
  
/**
 * 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) {
        document.getElementById("map_hotelClusterList_zoom").onclick = function(ev) {
            var locData = map.locations[allLocations[0]._dataIdx];
            var zoom    = map.calculateOptimalZoom(allLocations[0], map.allMarkers);
            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, map.allMarkers);
                    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;
    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;
    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, 2),
            '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.
 */
LANDMARK_ZOOM_THRESHOLD  = 15;
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) {
    if (!idPath) idPath = '/139/11/75/115/';
    if (!path)   path   = 'Asia/Thailand/Phuket/Patong Beach/';
        
    this.idSuffix = _idCounter++;
    this.map      = map;
    this.marker   = marker;
    this.landmark = landmark;
    if (landmark == null) {
        this.html = tmpl("template_new_landmark", this.getDefaultLandmarkValues());
    } else {
        this.html = tmpl("template_new_landmark", this.getLandmarkValues(landmark));
    }
    waitFor("editLandMark", DEFAULT_INTERVAL, function() {
        if (this.landmark == null) {
            setupDestinationLists(idPath, path);
        } else {
            setupDestinationLists(this.landmark.id_path, this.landmark.path);
        }
    }.bind(this));

    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("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'),
                    photoUrl:       this.getParam('landmark_photo_url'),
                    websiteUrl:     this.getParam('landmark_website_url'),
                    destinationId:  getSelectedDestinationId()
                },
                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) {
    return $(name + this.idSuffix).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() {
        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);
        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.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.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';
    }
    
    window.DraggableOverlay = DraggableOverlay;
    window.MapLabelOverlay  = MapLabelOverlay;
    
    
}


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

/**
 * 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) {
    var container = document.createElement("div");
    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) {
    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';
    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");
            item.onmouseover = function() { this.itemMouseOver(item, dataIdx); }.bind(this);
            item.onmouseout  = function() { this.itemMouseOut(item, dataIdx); }.bind(this);
            item.onclick     = function() { this.itemClick(item, dataIdx); }.bind(this);           
        }).bind(this)();
    }
    
    return container;
}

LocationSubList.prototype.itemMouseOver = function(node, dataIdx) {
    node.style.backgroundColor = "#A1D1EC";
    var marker = this.hotelMap.clusters[this.hotelMap.allMarkers[dataIdx]._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, dataIdx) {
    node.style.backgroundColor = "#FFFFFF";
}

LocationSubList.prototype.itemClick = function(node, dataIdx) {
    STOP_WATCH.go();
    var marker  = this.hotelMap.allMarkers[dataIdx];
    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';
}  

