Ikariam Xplorer

Create a searchable map on all the islands/cities you have visited.

// ==UserScript==
// @name			Ikariam Xplorer
// @namespace		greasyfork.org
// @homepage		http://greasyfork.org/
// @description		Create a searchable map on all the islands/cities you have visited. 
// @version			0.1.1
// @include			http://s*.ikariam.*/*
// @exclude			http://support*.ikariam.*/*
// @license        	GNU GPL v3 (http://www.gnu.org/copyleft/gpl.html), Copyright (c) 2012-2015, Viper
// @grant unsafeWindow
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_log
// ==/UserScript==
// Version history:
/*
0.1.1/8.7.2015		First release.
					Feature: Autoscan worldmap view to get base island information (Worldscan)
					Feature: Autoscan defined area's island views (Scan)
					Feature: Collect city information from island view
					Feature: Display island/city information on a map interface
					Feature: Filter players, alliances, cities			
					Feature: Collect player score information from island view
					Feature: Collect information from world map
					
*/
/* 
ROADMAP
[HIGH]	Collect information from alliance member list view
[HIGH]	Display allied cities on the map
[HIGH]	Detect and highlight allied cities
[HIGH]	Collect information from high score views.
[HIGH]	Collect information from combat reports
[HIGH]	Display stationed troops and troop movements.
[MED]	Remove highlight colors from used alliance colors
[MED]	Show/highligh player cities on map when hoovering over player's info
[MED]	Grouping (and thus coloring) by other fields in addition to alliance
[LOW] 	"Travel Time" functionality with source and target island selection
[LOW]	Make grid ticks visible when zooming
[LOW]	Make containers "dockable" or at least their visibility selectable
[LOW]	Define and restrict minimum size of window to prevent misaligned/overlapping ui elements)
*/

const version = '0.1.1';
const scriptURL = 'http://userscripts.org/scripts/source/xxxxx.user.js'
const scriptMetaURL = 'http://userscripts.org/scripts/source/xxxxx.meta.js'
const scriptHomepage = 'http://userscripts.org/scripts/show/xxxxx';
const updateCheckInterval = 24*60*60*1000; //24 hours
const versionTag = '// @version';

const mapDBVersion = '001';
const mapDBKey = 'mapDB';

const syslogLogLevel = 4;  // Higher number results in finer logging, 1-4
const syslogJavaConsoleEnabled = false;
const syslogFirebugEnabled = true;
var consoleBackup = unsafeWindow.console;	// store the original console pointer since some pages overwrite/disable it

const defaultWindowWidth = 1024;
const defaultWindowHeight = 810;

const mapXOffset = 20;
const mapYOffset = 45;
const defaultMapScaleRatio = 55;
var mapScaleRatio = defaultMapScaleRatio;
var mapScale = 1;

const islandScale = 4;
const generalsScoreIslandScale = 100;
const totalScoreIslandScale = 5000;
const goldIslandScale = 100000;

const playerScale = 4;
const generalsScorePlayerScale = 100;
const totalScorePlayerScale = 5000;
const goldPlayerScale = 100000;

const IMAGES =
{
	xplorer:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAADAFBMVEUMCAjUiSCMkJT6xTnqzZicimzKzM7rxWjcpizYqlybXwv85Jb532TJsZf76cxQTlC4iEC8jV/Epkj821jEp4SwsbXmw4LgzbXow0zn6ezkhnydZhH04rvcwJTwtzGcglT202f3z1LcmSrClkHctnx8ZiykGxTt4Mz889LmuV/63oL03JTEm1/fuUTw2LTysSzUr3z31XzVtZR+eGz88LjMqJT87pzky6T5+PDc29zvzGjl5+TEwLTCnXzXwKfYMizm2cS2hjD80US0mnzeqEnkzlysdTCdoKPSqHC4h1HEjGzxyFzKmEzkemzsnoyygDL3xUSFiIn88Gf41nTjoSbu1YzUnozisEj87tTNqoyodBjQJBz89YbisjyAgIHIooFERkT046mwrqjjunAsKiy3W0/25MT01lH89ODFm3P03rvr0qvuzHS8jiTS09PgqDz26dT75lz1yE7quDzoulTQsIz42n3YupX89cT8/vzj4uP8yjrMqIS3uLrszXzh0rjv8PGkSjwcGhxgYF5EQDiPemGkjmzAwMTkhkSsWjTUdizUljycgmzYjizkVkykaiDu16a0qpz87qnMPDS8fmScknz86WScJhTHuqecYhW0gEMUEhT86IzVt4zZsVzAkEDQrnCRj4ftu0WVmJvAkkzClXHMokzdoT/EmjSsooyocyTClGPskoQ0NjSseTz883SQiHz03nS8HhzkqpTboTTqsVTbkSOlqKy8Ihz67tznwnTkwoz84ozcqlTU1tzqy4xEQkTcxrT81lzMgmzw0pTixpbmvlyEfnTdxqbcKiTESjy0fkzotpzNnn3fsnH46pzMtpX74lykbhT56rTkvoHMol/8/vS8jiysehz86qTkvnT8+uPMonQkIiRkZlzExsr03qzkykz8+sz89rf89pzy3sSsejT5ykT89mz06sTxvjz89qz30FzYljTiqTTMpmTbwJzs4dTUukzs2LzpsDTNroTy1ISEenTUppzizaze3uTMmlTcJiS0knShIxzcsGTflboUAAABAHRSTlP/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ex4nOAAACN5JREFUeJzll39MG9cdwIOMBrWLGzdWbA3Hs2kWQ2iOcnVjj9WpCwSFkAlqoJyzsRPKYs5nJmzI42xxwtrSTtuqjCzd0pDWyYbi0oQ1CRtBvYQpS5ZUbK2JCURRRFcNEUoghLoyRrW6Nfu+MxAg0LWa+te+YB9+ft8P31/v+95b4/sfZc3XCajN1+trp2rh7dGvDOgaV+uyD42Oer2j3tHRQ7069fhXAYxs0V27uyCjt0dv3727Vbdl5EsCRqq6tt5eJnhga1e1+ksA8rp1P0hI17uP33y+tPT5m49ndc0N6brz/hugeovNduaM7cz2t9KlIG0g+Hn9re3isG3LzBcDUmttd2w225bdUmnr6eJgyGJxhoLB4tOtUuluQN+x3dme+gUAXaoH66uvS9tOB4O8HSES2clAGs9bFEfbpNdHgH3Hk7oqQDfjsR0+/OiPpW3vOemgJYCUyB5TImUMOP+mnYC4OQATPDOrALq0AwMDnpF0aVGQBhU6FuZMcVM8HufCgAmTfLAIjPB4PAPalQHN+fr82hlpWzGNAognNxAslih+IzhOiZnFbdIZfX5+fvNKgDJJmaQ5VdqmCILVyGXCmpqxMNvHajDGBYNhiyJdmtpcViYZfxjQ0KDX66ulbUGaViITwcbfd4DIOS6On8AgTAjRlmC6tApmlumXA3LbdTrdu+ltO3mSV7rYqGbsr6Aul8f9fvmEPIFwKWOcfWebtESXp2tfDmjUdTXl7pYqQD9MRFlQDv1JLnfL5wUjoq4wIu0K6e7criZd41JAXlZvb++b0qM0yQcgeNj46N/lcoqiHPCi5hAsEVBy/FHpm73ZvfvylgCme69dq0xvDcYQ8mN90JXvfZHKpNxWSqDcIgLbQCAUsLSmV8quyaYXA/KyjIfunkiHBID/LPadklMvvioXBIdfoIRMgaLcIoF12VFQ0bbrrvFQVt4iwHSN11uTXhRSKjlC/P9gPGP4/A8CY40yjIAJmaINUBFKni5qrRn1yuoXAQqh8dxvVUDxu1jNnD4l7H1Ozqj8DIhoAyUSXFBQitaPvV5v4QPA+Raj0VhaFEKojx1zTIA+aAjMM3/8JrMtypgBAFZgAnYigrhQUanReKglZQGQlX3rVsup93iSdgEAkgd++w0q/+fPGcL+YWaYsX5DnpkwYYwlIFPFp1pu3crOWgCk9PbK7p8KIsRFcQQh6JmM1e+37n3ne8+ohsEE/yUDGCESWGJDAHWeOiaTyRYsqJndvHnz8Y+CPDKJEYAAgANg+aeb/vYbl9k8LCTiAOnEUehDJP3RE6AyWzMHqJSVl5eXHg3SkENIAUUJAlYZFlSvvfOqijEb3OCFmAwIr4YllHzw6FpdebnsygJgamqqtJiO8XHRgsxMrG8eNst/sulf7zMqK/yZSIXcDRaYeBQqLs2bmnpjdg6QW97QoC6FZQBV7JhwYxNAnxk2mz/d9P1fcX7QHxYzAUGYgHIMcLyitGxcXz4PqNNLJNq1CjvJsZGoKg4hEAWqL/raL3++05525JLf4BZwEOTboNI5DinWaiUSfd0cIE8vadYWd/IIEf40TuWIqralKS0nLQXOgl80yc5yZDgtDL+RS1GHO+JnCSilzrWTAMibA6TkNzdPHu+E/suykT6H6AEjGKyXnk17+dvf2m/AnxJpoOQRlYblMKC9uXk8ZR4wLmmePNHJkyTBanAdi0EEx83utJde3/9bJhHE+UJgEWfvPCFZDCiTSCS7Op1KpSmRxkQWzGahz3xkzUsvxM24mAQmUcysSYksT+4ClbJ5F2YbcnJyPut00rwr0QoEHEYIvBUy+dJPP/wEntgJWJE4ja4Y7+zcBSoNlXOAg3UVFRWvPOkkYS2JlShawJgFSKT85TWPPZsIgTC3HvsQCt3YlVGRUXdwfi1U9iQlvXLDicRCENcCJVj9qk8gDO6fffijF4YNKivD4CzijkByyHnjXlJSz+aFxVTX0dNz4GMnTyqJhcVg8FvBcTNl+c7r+7/r9hsWGgKh5Owf3DjQ09Ox/sFqPAc+PLIjRKKI2NDcmYlU4mo6yTz99pFEChI5wP1gxyOgcO5BP/AVarU5+3Z8gMiYaa6jQQvCngtCGmP4x1loT4m+qmFNiEP076q0Wm3hwQeAe1CYkv4dFgvPsQkn5NDTRAsiDPPDv7jnHYAq4nl6Rz9M195b1BP3VExKJvdtpIMIFiS2AccRL2uGusRQ7rePiF1ZNCBmt9AbsySTkxV7Fu8LHdU5OTP9G0MBMhaHrpboq7iiKAIev3/aLXb1MXCARKGN/TMwu2PJxpJ1LqOiomroKZpEJBHViAS3aMYYNv4xR0KfIBHHx9ZX4RDuWbo3Zk339PT0X+B5DgXiOA6O+V0R237y13hvJFyQJp6/cD8Jcpi1dG/0DV1MAhkCAkJ4dxpzJBAqv7gvntVAAv002mCnL6w/ADMvrl8G8KWch+F7F6/STgvi+xIbLAgRFh9haANX4XxA88kX7iX1JJ0/71sO8LVUHejJ6EhO5kMkB/2ZIDTgiMbfB//bwUb7TAi8C/FXknHVFrYQDwOuztZnZGQUXjkYoO0caScRkTgk4SMSBA+REUSTV64UwqT62au+hwG+odnp7oru+sp/Jtt5KDfEo3DEFOkzXU1DsRjiSJ5Prqmsr+hOrZ8d8q0E8O3JrZrOma6vq6mJ8JYCS4QkyQA+a5JchC6g+UhNzfmZ6emcqtw9vpUBvv5ydfVMo3Zf9uBgHypQYmWLEnoqUtIoeXAwG387o67b6FsN4BtKKWlvbG9vLJEZjYOuDUipLMCHtg2uwQljtvjVZEnKBd/qAF/8nK6hTK1uUDc03TLCGcDonfB6M73GmqbtMFqmbtAdW6rw8H3hWMoUHBhH4LVd19Qrg8PQG106/ZxMlSzXX+nGUlWy7vK62nVLBT5fvlzyZ9NDs1e6Mz1VqL48sFQ8A+tq1VWBFSavfGujP6tvrx3weGy2w/DjGRjQl9XfQCtOXf3eWHy8o36msbFRWz3d/YRi1Wlf6831/wXwH5gC7KdtkjI7AAAAAElFTkSuQmCC'
};

var xplorerDB = null;

var windowResizeTimeOut = null;
var windowRef = null;

var mapContainer = null;
var statusContainer = null;
var legendContainer = null;
var previewContainer = null;
var searchContainer = null;
var ikaCoreAddContainer = null;
var mapCanvas = null;

var dragMouseDown = false;
var dragPrevX = 0;
var dragPrevY = 0;

var allianceInput = null;
var playerInput = null;
var cityInput = null;
var playerStatusSelect = null;
var distanceSelect = null;
var luxuryResourceSelect = null;
var townHallLevelLowSelect = null;
var townHallLevelHighSelect = null;
var militaryScoreLowInput = null;
var militaryScoreHighInput = null;

var reportSelect = null;
var highlightInactiveCheckbox = null;
var highlightVacationCheckbox = null;
var updateButton = null;

var scanCenterXInput = null;
var scanCenterYInput = null;
var scanRangeInput = null;
var scanDelayMinInput = null;
var scanDelayMaxInput = null;
var rescanWaitTimeInput = null;

var ownIslands = [];
var selectedOwnCity = null;
var cities = [];
var alliances = [];
var players = [];
var islands = [];

var filterIndex = 0;
var filtersApplied = false;

var searchStore = [];
var searchStoreSelect = null;

var worldMapViewOriginalHandleMapData;

const HTML_ELEMENT =
{
	br:			'br',
	div:		'div',
	tbody: 		'tbody',
	tr:			'tr',
	th:			'th',
	td:			'td',
	img:		'img',
	button:		'input',
	a:			'a',
	h1:			'h1',
	h2:			'h2',
	h3:			'h3',
	h4:			'h4',
	h5:			'h5'
};

const ALLIANCE_COLOR =
{
	0: 			'green',
	1: 			'red',
	2:			'blue',
	3:			'olive',
	4:			'grey',
	5:			'yellow',
	6:			'aqua',
	7:			'purple',
	8:			'fuchsia',
	9:			'orange',
	10:			'orchid',
	11:			'peru',
	12:			'rosybrown',
	13:			'salmon',
	14:			'skyblue'
}

const ALLIANCE_TEXT_COLOR =
{
	0: 			'white',
	1: 			'white',
	2:			'white',
	3:			'white',
	4:			'white',
	5:			'black',
	6:			'black',
	7:			'white',
	8:			'white',
	9:			'white',
	10:			'white',
	11:			'white',
	12:			'white',
	13:			'white',
	14:			'white'
}

const REPORT_TYPE = {
	rtIslands: 			'Islands',
	rtIslandsGenerals: 	'Islands - Generals',
	rtIslandsScore: 	'Islands - Score',
	rtIslandsGold:		'Islands - Gold',
	rtPlayers: 			'Players',
	rtPlayersGenerals: 	'Players - Generals',
	rtPlayersScore: 	'Players - Score',
	rtPlayersGold:		'Players - Gold'
};

const REPORT_TYPE_INDEX = [
	REPORT_TYPE.rtIslands,
	REPORT_TYPE.rtIslandsGenerals,
	REPORT_TYPE.rtIslandsScore,
	REPORT_TYPE.rtIslandsGold,
	REPORT_TYPE.rtPlayers,
	REPORT_TYPE.rtPlayersGenerals,
	REPORT_TYPE.rtPlayersScore,
	REPORT_TYPE.rtPlayersGold
];

const PLAYER_STATUS_SELECT = {
	psAny:		'Any',
	psActive:	'Active',
	psVacation:	'Vacation',
	psInactive: 'Inactive'
};

const PLAYER_STATUS_SELECT_INDEX = [
	PLAYER_STATUS_SELECT.psAny,
	PLAYER_STATUS_SELECT.psActive,
	PLAYER_STATUS_SELECT.psVacation,
	PLAYER_STATUS_SELECT.psInactive
];

const LUXURY_RESOURCE_SELECT = {
	lrAny:		'Any',
	lrWine:		'Wine',
	lrMarble:	'Marble',
	lrCrystal: 	'Crystal',
	lrSulfur:	'Sulfur'
};

const LUXURY_RESOURCE_SELECT_INDEX = [
	LUXURY_RESOURCE_SELECT.lrAny,
	LUXURY_RESOURCE_SELECT.lrWine,
	LUXURY_RESOURCE_SELECT.lrMarble,
	LUXURY_RESOURCE_SELECT.lrCrystal,
	LUXURY_RESOURCE_SELECT.lrSulfur
];

const ISLAND_LUXURY = ['wine','sulphur', 'marble', 'crystal'];

const PLAYER_STATUS = {
	0:			'active',
	1:			'vacation',
	2:			'inactive'
};

/*
const ISLAND_LUXURY =
{
	0: 			'unknown',
	1: 			'wine',
	2:			'marble',
	3:			'crystal',
	4:			'sulfur'
}
*/

// remove multiple, leading or trailing spaces
function trim(s) {
	s = s.replace(/(^\s*)|(\s*$)/gi,"");
	s = s.replace(/[ ]{2,}/gi," ");
	s = s.replace(/\n /,"\n");
	return s;
}

// Array.unique( strict ) - Remove duplicate values (have to sort array before using)
Array.prototype.unique = function() {
	var result = [];
	for(i=0; i < this.length; i++ ) {
		if(i==0) result.push(this[i]);
		if((i>0) && (this[i].toCSVString().localeCompare(this[i-1].toCSVString()) != 0)) {
			result.push(this[i]);
		}
	}
	return result;
};

Array.prototype.removeById = function(id) {
	for(i=0; i < this.length; i++ ) {
		if(this[i].id == id) {
			this.splice(i,1);
			break;
		}
	}
}


function removeElementsByClassname(root, className, parent) {
	var elems = root.getElementsByClassName(className);
	while(elems.length > 0) {
		parent.removeChild(elems[0]);
	}
}

function stringToBoolean(str){
	return (str == 'true');
}


function sumArray(array) {
	var result = 0;
	for (var i in array) {
		if(typeof(array[i] == 'number')){result += array[i];}
	}
	return result;
}

function getTimestamp() {
	var currentTime = new Date();
	var year = currentTime.getFullYear();
	var month = currentTime.getMonth();
	var day = currentTime.getDay();
	var hours = currentTime.getHours();
	var minutes = currentTime.getMinutes();
	var seconds = currentTime.getSeconds();
	var ts = year + '-';
	if(month < 10){ts = ts + '0' + month + '-';}
	else{ts = ts + month + '-';}
	if(day < 10){ts = ts + '0' + day + ' ';}
	else{ts = ts + day + ' ';}
	if(hours < 10){ts = ts + '0' + hours + ':';}
	else{ts = ts + hours + ':';}
	if(minutes < 10){ts = ts + '0' + minutes + ':';}
	else{ts = ts + minutes + ':';}
	if(seconds < 10){ts = ts + '0' + seconds;}
	else{ts = ts + seconds;}
	
	return ts;	
}


// Firebug console log
 function consoleLog(object){
	console.log(object);
	//if(consoleBackup){
	//	consoleBackup.log(object);
	//}
}

// Firebug console dir
 function consoleDir(object){
	console.dir(object);
}

// Firebug console time
function consoleTime(name){
	if (syslogLogLevel > 2)  {
		console.time(name);
	}	
}

// Firebug console timeEnd
function consoleTimeEnd(name){
	if (syslogLogLevel > 2)  {
		console.timeEnd(name);
	}	
}

// Generic syslog targeting Java and/or Firebug console 
function syslog(logMessage, logLevel){
	if(logLevel == null){
		logLevel = 4;	
	}
	var syslogMessage = getTimestamp() + ' ' + arguments.callee.caller.name + ' ("' + logMessage + '")';
	// log only if loglevel lower than globally set log level
 	if(logLevel <= syslogLogLevel){
 		// Log into Java Console if enabled
		if(syslogJavaConsoleEnabled){
			GM_log(syslogMessage);	
		}
		// Log into Firebug if enabled
		if(syslogFirebugEnabled){
			consoleLog(syslogMessage);
		}
	}
}

function intToString(num){
	return num + '';
}


function removeAllChildElements(item) {
	while(item.hasChildNodes()) {
		item.removeChild(item.firstChild);
	}
}

function xplorerSearchDiv() {
	result = document.createElement('div');
	result.setAttribute('class', 'xplorer_searchDiv');
	return result;
}

function xplorerSearchInputDiv(parent, caption, width, value, onKeyUp) {
	var div = xplorerSearchDiv();
	div.appendChild(xplorerCaption(caption));
	var input = xplorerInput();
	input.style.width = width;
	input.value = value;
	if(onKeyUp != '') input.addEventListener('keyup', onKeyUp, false);
	div.appendChild(input);
	parent.appendChild(div);
	return input;
}

function xplorerSearchSelectDiv(parent, caption, values, onChange) {
	var div = xplorerSearchDiv();
	div.appendChild(xplorerCaption(caption));
	var select = xplorerSelect();
	if(onChange != '') select.addEventListener('change', onChange, false);
	for(i=0; i < values.length; i++ ) {
		select.appendChild(new Option(values[i],i));
	}	
	div.appendChild(select);
	parent.appendChild(div);
	return select;
}

function xplorerButtonsDiv(buttonDefs) {
	var div = htmlDiv();
	div.style.clear = 'both';
	for (var i = 0; i < buttonDefs.length; i++) {
		var button = xplorerButton(buttonDefs[i].name);
		button.addEventListener('click', buttonDefs[i].onClick, false);
		div.appendChild(button);
	}
	return div;
}

function xplorerHeading(text) {
	result = document.createElement('div');
	result.style.clear = 'both';
	result.setAttribute('class', 'xplorer_heading');
	result.innerHTML = text;
	return result;
}

function xplorerCaption(text) {
	result = document.createElement('div');
	result.setAttribute('class', 'xplorer_caption');
	result.innerHTML = text;
	return result;
}

function xplorerButton(text) {
	result = document.createElement('input');
	result.setAttribute('class', 'xplorer_button');
	result.setAttribute('type', 'button');
	result.setAttribute('name', text);
	result.setAttribute('value', text);
	return result;
}

function xplorerInput() {
	result = document.createElement('input');
	result.setAttribute('class', 'xplorer_input');
	return result;
}

function xplorerSelect() {
	result = document.createElement("select");
	result.setAttribute('class', 'xplorer_select');
	return result;
}

function xplorerCheckbox() {
	result = document.createElement("input");
	result.setAttribute('class', 'xplorer_checkbox');
	result.setAttribute('type', 'checkbox');
	return result;
}
function xplorerLabel(text) {
	result = document.createElement("label");
	result.setAttribute('class', 'xplorer_label');
	result.textContent = text;
	return result;
}


function htmlElement(element, childElement){
	result = document.createElement(element);
	result.setAttribute('class', 'xplorer_' + element);
	if(childElement) {
		if(typeof childElement == "string") {
			result.innerHTML = childElement;
		}
		else {	
			result.appendChild(childElement);
		}
	}
	return result;
}

function htmlBr(childElement){
	return htmlElement(HTML_ELEMENT.br, childElement);
}

function htmlDiv(childElement){
	return htmlElement(HTML_ELEMENT.div, childElement);
}

function htmlH3(childElement){
	return htmlElement(HTML_ELEMENT.h4, childElement);
}

function htmlH4(childElement){
	return htmlElement(HTML_ELEMENT.h4, childElement);
}

function htmlH5(childElement){
	return htmlElement(HTML_ELEMENT.h5, childElement);
}

function htmlTableBody(childElement){
	return htmlElement(HTML_ELEMENT.tbody, childElement);
}

function htmlTableRow(childElement){
	return htmlElement(HTML_ELEMENT.tr, childElement);
}

function htmlTableHeaderCell(childElement) {
	return htmlElement(HTML_ELEMENT.th, childElement);
}

function htmlTableCell(childElement) {
	return htmlElement(HTML_ELEMENT.td, childElement);
}

function htmlImg(image, title, width, height){
	result = htmlElement(HTML_ELEMENT.img);
	result.setAttribute('src', image);
	if(title) result.setAttribute('title', title);
	if(width) result.setAttribute('width', width);
	if(height) result.setAttribute('height', height);
	return result;
}

function htmlButton(title){
	result = htmlElement(HTML_ELEMENT.button);
	if(title) {
		result.setAttribute('type', 'button');
		result.setAttribute('name', title);
		result.setAttribute('value', title);
	}
	return result;
}

function htmlAHref(url, childElement){
	result = htmlElement(HTML_ELEMENT.a, childElement);
	result.setAttribute('href', url);
	return result;
}

function getDomain(){
	return document.domain;
}

function formatTime(time, compact) {
	if (!compact) {
		var d = Math.floor(time / (60*24))
		var h = Math.floor((time-d*60*24) / 60);
		var m = Math.floor(time - 60*h - 60*24*d);
		// minutes are always present, days and hours omitted if zero
		return (d > 0? d+"d ":"")+(h > 0? h+"h ":"")+m+"m";
	}
	else {
		// compact format
		// if under a day -> show hours
		if (time < (24*60)) {return Math.round(time/60)+'h';}
		// show days
		else {return Math.round(time/60/24)+'d';}
	}
}

function $(id){
	return document.getElementById(id);
}

function getHrefParam(href, param) {
  param = param.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexStr = "[\\?&]"+param+"=([^&#]*)";
  var regex = new RegExp( regexStr );
  var results = regex.exec(href);
  if( results == null )
    return "";
  else
    return results[1];
}

function getRequestParam(param) {
  return getHrefParam(window.location.href, param);
}

function ikariamGetServer() {
	var domain = getDomain();
	return domain.substring(domain.indexOf('.') + 1, domain.length);
}

function ikariamGetWorld() {
	var domain = getDomain();
	return domain.substring(1, domain.indexOf('.'));
}

function ikariamGetCurrentView(){
	var result = getRequestParam('view');
	if(!result) {
		result = getRequestParam('action');
	}
	return result;
}

function ikariamIsCurrentView(name){	
	var result = name.localeCompare(ikariamGetCurrentView())
	return (result == 0);
}

function ikariamIsAllyListView() {
	return (ikariamIsCurrentView('diplomacyAdvisorAlly') && (getRequestParam('listAllyMembers') == 1));
}

function ikariamIsIslandView() {
	return ($('island') != null);
}

function ikariamIsMilitaryMovementsView() {
	return ($('fleetMovements') != null);
}

function ikariamIsWorldMapView() {
	return ($('worldmap_iso') != null);
}


function ikariamScoreToNum(score) {
	return parseInt(trim(score.replace (/,/g, "")));
}

function ikariamParseCoordinatesFromString(str, coords) {
	coords.x = parseInt(str.substring(str.indexOf('[')+1, str.indexOf(':')));
	coords.y = parseInt(str.substring(str.indexOf(':')+1, str.indexOf(']')));
}

ikariamCalculateDistance = function(coordsA, coordsB) {
	return Math.sqrt(Math.pow(coordsA.x-coordsB.x,2)+Math.pow(coordsA.y-coordsB.y,2));
}

// travel time for transport ships
ikariamCalculateTravelTime = function(coordsA, coordsB) {
	var result = (1200/60) * ikariamCalculateDistance(coordsA, coordsB);
	if (result == 0) return 10;
	else return result;
}

ikariamTravelTimeAsString = function(coordsA, coordsB, compact) {
	return formatTime(ikariamCalculateTravelTime(coordsA, coordsB), compact);
}


/*
 *   Returns an array containing a list of own cities
 *   Reads the information from the city selection drop down
 *   so can be called from any view
  */
function ikariamGetOwnCities(){
	var result = [];
	
	try {
		var cityElements = unsafeWindow.dataSetForView.relatedCityData;
	}
	catch(e) {
		syslog('Error retrieving own cities: "' + e.description + '"', 1);
		return result;
	}

	for (key in cityElements) {
		if ((key == 'additionalInfo') || (key == 'selectedCity')) continue;
		var city = new mdbCity();
		city.island = new mdbIsland();
		city.id = parseInt(cityElements[key].id);
		city.name = cityElements[key].name;
		city.luxuryResource = parseInt(cityElements[key].trafeGood);
		city.selected = (key == cityElements.selectedCity);
		ikariamParseCoordinatesFromString(cityElements[key].coords, city.island.coords);
		city.relationship = cityElements[key].relationship;
		result.push(city);
	}
	return result;
}

function ikariamAllyListViewGetAllianceId() {
	return parseInt(getHrefParam($('allyinfo').getElementsByClassName('icon_msg')[0].getAttribute('href'), 'allyId'));
}

function ikariamAllyListViewGetAllianceInfo(alliance) {
	alliance.name = $('allyinfo').getElementsByTagName('td')[0].title; 
	alliance.longName = $('allyinfo').getElementsByTagName('td')[1].title; 
	alliance.lastUpdate = new Date().getTime();
}

function ikariamAllyListViewGetPlayerInfos() {
	var memberList = $('memberList');
	var playerInfsTmp1 = Array.prototype.slice.call(memberList.getElementsByClassName('default'));
	var playerInfsTmp2 = Array.prototype.slice.call(memberList.getElementsByClassName('alt'));
	return playerInfsTmp1.concat(playerInfsTmp2);
}

function ikariamAllyListViewGetPlayerId(playerInfo) {
	var sendMessageAction = playerInfo.getElementsByClassName('message')[0];
	if(sendMessageAction) {
		return parseInt(getHrefParam(playerInfo.getElementsByClassName('message')[0].getAttribute('href'), 'receiverId'));
	}
	// own information stored under id = 0 since real id is not available in this view
	else { 
		return 0;
	}
}

function ikariamAllyListViewGetPlayerInfo(playerInfo, player) {
	// player name
	player.name = playerInfo.childNodes[3].textContent;
	// player total score
	player.score = ikariamScoreToNum(playerInfo.childNodes[9].textContent);
	// player rank
	player.rank = playerInfo.childNodes[7].textContent;
	// online status -> sets both player.status and player.onlineStatus
	var lastOnlineText = playerInfo.childNodes[1].getAttribute('title');
	player.lastOnline = lastOnlineText.substring(lastOnlineText.indexOf(':')+2, lastOnlineText.length);
	player.onlineStatus = playerInfo.childNodes[1].getAttribute('class');
	switch(player.onlineStatus)	{
		case 'online': case 'offline':
			player.status = 'active';
			break;
		case 'inactive':
			player.status = 'inactive';
			break;
		case 'vacation':
			player.status = 'vacation';
			break;
	}
}

function ikariamAllyListViewGetCityInfos(playerInfo) {
	return playerInfo.getElementsByClassName('city');
}

function ikariamAllyListViewGetCityId(cityInfo) {
	var href = cityInfo.getAttribute('href');
	return parseInt(getHrefParam(href, 'selectCity'));
}

function ikariamAllyListViewGetCityInfo(cityInfo, city) {
	var cityText = cityInfo.textContent;
	city.name = cityText.substring(0, cityText.indexOf('[')-1);
}

function ikariamAllyListViewGetIslandId(cityInfo) {
	var href = cityInfo.getAttribute('href');
	return parseInt(getHrefParam(href, 'id'));
}

function ikariamAllyListViewGetIslandInfo(cityInfo, island) {
	ikariamParseCoordinatesFromString(cityInfo.textContent, island.coords)
	island.lastUpdate = new Date().getTime();
	island.setProcessLevel(2);
}


function ikariamIslandViewGetIslandId() {
	return parseInt(unsafeWindow.ikariam.backgroundView.screen.data.id);
}

function ikariamIslandViewGetIslandInfo(island) {
	var data = unsafeWindow.ikariam.backgroundView.screen.data;
	island.name = data.name;
	island.coords.x = parseInt(data.xCoord);
	island.coords.y = parseInt(data.yCoord);
	island.sawmillLevel = parseInt(data.resourceLevel);
	island.luxury = parseInt(data.tradegood);
	island.luxuryLevel = parseInt(data.tradegoodLevel);
	island.wonder = parseInt(data.wonder);
	island.wonderLevel = parseInt(data.wonderLevel);
	island.lastUpdate = new Date().getTime();
	island.setProcessLevel(3);
}

function ikariamIslandViewGetCityInfos() {
	return unsafeWindow.ikariam.backgroundView.screen.data.cities;
}

function ikariamIslandViewGetCityId(cityInfo) {
	return parseInt(cityInfo.id);
}

function ikariamIslandViewGetCityInfo(cityInfo, city) {
	city.name = cityInfo.name;
	// city townhall size
	city.townHallSize = parseInt(cityInfo.level);
	city.lastUpdate = new Date().getTime();
}

function ikariamIslandViewGetAllianceId(cityInfo) {
	return parseInt(cityInfo.ownerAllyId);
}

function ikariamIslandViewGetAllianceInfo(cityInfo, alliance) {
	if(cityInfo.ownerAllyId == 0) alliance.name = '-'
	else alliance.name = cityInfo.ownerAllyTag;
	alliance.lastUpdate = new Date().getTime();
}

function ikariamIslandViewGetPlayerId(cityInfo) {
	return parseInt(cityInfo.ownerId);
}

function ikariamIslandViewGetPlayerInfo(cityInfo, avatarScores, player) {
	player.name = cityInfo.ownerName;
	player.status = cityInfo.state;
	player.buildersScore = ikariamScoreToNum(avatarScores[player.id].building_score_main)/100;
	player.scientistScore = ikariamScoreToNum(avatarScores[player.id].research_score_main)/100;
	player.generals = ikariamScoreToNum(avatarScores[player.id].army_score_main)/100;
	player.gold = ikariamScoreToNum(avatarScores[player.id].trader_score_secondary);
	// this is now missing the number of citizens, could not find it anywhere
	player.score = player.buildersScore + player.scientistScore + player.generals;
	player.lastUpdate = new Date().getTime();
}

function ikariamMilitaryMovementsViewGetMovementInfos() {
	return $('fleetMovements').getElementsByClassName('locationEvents')[0].getElementsByTagName('tr');
}

function ikariamMilitaryMovementsViewGetMovementId(movementInfo) {
	return parseInt(movementInfo.getElementsByTagName('td')[1].id.substring(8));
}


function ikariamAllyListViewGetPlayerInfos() {
	var memberList = $('memberList');
	var playerInfsTmp1 = Array.prototype.slice.call(memberList.getElementsByClassName('default'));
	var playerInfsTmp2 = Array.prototype.slice.call(memberList.getElementsByClassName('alt'));
	return playerInfsTmp1.concat(playerInfsTmp2);
}

function ikariamWorldMapViewGetIslandInfo(islandInfo, island) {
	island.name = islandInfo[1];
	island.luxury = parseInt(islandInfo[2]);
	island.wonder = parseInt(islandInfo[3]);
	island.islandType = parseInt(islandInfo[5]);
	island.cityCount = parseInt(islandInfo[7]);
	// change 5.7.2015, do not modify lastUpdate when in worldmapview
	// island.lastUpdate = new Date().getTime();
	island.setProcessLevel(1);
}

function getArrayItemByID(array, id) {
	for (var i = 0; i < array.length; i++) {
		if(array[i].id == id) return array[i];
	}
	return null;
}

function getArrayItemByName(array, name) {
	for (var i = 0; i < array.length; i++) {
		if(array[i].name == name) return array[i];
	}
	return null;
}

function getArrayItemByCoordinates(array, x, y) {
	for (var i = 0; i < array.length; i++) {
		if((array[i].coords.x == x) && (array[i].coords.y == y)) return array[i];
	}
	return null;
}

function xplorerStoreMemberCities(memberCities) {
	var key = getDomain() + '.xplorerMemberCities';
	GM_setValue(key, uneval(memberCities));
}

function xplorerLoadMemberCities() {
	var key = getDomain() + '.xplorerMemberCities';
	return (eval(GM_getValue(key, '')));
}



//testing data types accurately in JavaScript (opposed to "typeof")
//from http://bonsaiden.github.com/JavaScript-Garden/
function is(type, obj) {
    var clas = Object.prototype.toString.call(obj).slice(8, -1);
    return obj !== undefined && obj !== null && clas === type;
}

function compareById(a, b) {
	if(is("Number", a) && is("Number", b)) {
		return a - b;
	}
	else {
		if(is("Object", a) && is("Number", b)) {
			return a.id - b;
		}
		else {
			return a.id - b.id;
		}
	}
}

function compareByName(a, b) {
	if(is("String", a) && is("String", b)) {
		return a.localeCompare(b);
	}
	else {
		if(is("Object", a) && is("String", b)) {
			return a.name.localeCompare(b);
		}
		else {
			return a.name.localeCompare(b.name);
		}
	}
}

function stringCompare(a, b, index) {
	if(is("String", a) && is("String", b)) {
		return a.localeCompare(b);
	}
	else {
		if(is("Object", a) && is("String", b)) {
			return a[index].localeCompare(b);
		}
		else {
			return a[index].localeCompare(b[index]);
		}
	}
}


/**************************************************** 
					sortedList
****************************************************/
function sortedList(compareFunction, indexProperty) {
	this.list = [];
	this.compareFunction = compareFunction;
	this.indexProperty = indexProperty;
}

sortedList.prototype.add = function(item) {
	var position = this.indexOf(item[this.indexProperty], true);
	this.list.splice(position, 0, item);
}

sortedList.prototype.toJSON = function() {
	var result = [];
	for (var i = 0; i < this.list.length; i++) {
		result.push(this.list[i].toJSON());
	}
	return result;
}

sortedList.prototype.indexOf = function(value, findNearest, compareFunction) {
	var compFunc;
	if(compareFunction) { compFunc = compareFunction;}
	else compFunc = this.compareFunction;
	var low = 0;
	var high = this.list.length - 1;
	var i, comparison;
	while (low <= high) {
		i = Math.floor((low + high) / 2);
		comparison = compFunc(this.list[i], value, this.indexProperty);
		if (comparison < 0) {
			low = i + 1; 
			continue
		}
		if (comparison > 0) {
			high = i - 1;
			continue;
		}
		return i;
	}
	// return nearest item (used internally when adding) if requested
	if (findNearest) return low;
	// otherwise no match
	return -1;
}

sortedList.prototype.find = function(value, findNearest, compareFunction) {
	return this.list[this.indexOf(value, findNearest, compareFunction)];
}

sortedList.prototype.filterString = function(value, valueAttribute, filterMatchAttribute, filterIndex, caseSensitive, subString) {
	var compValue = '';
	if(!caseSensitive) compValue = value.toLowerCase();
	else compValue = value;
	var compValues = compValue.split(" ");
	// the items contained in the list must have an attribute named with the value of valueAttribute
	// and they must have an array attribute named with the value of filterMatchAttribute
	for (var i = 0; i < this.list.length; i++) {
		var found = false;
		var itemValue = this.list[i][valueAttribute];
		if(!caseSensitive) itemValue = itemValue.toLowerCase();
		for (var j = 0; j < compValues.length; j++) {
			if((j > 0) && (compValues[j] == '')) continue;
			if(!subString) found = (itemValue == compValues[j]);
			else found = (itemValue.indexOf(compValues[j]) != -1);
			if (found) break;
		}
		this.list[i][filterMatchAttribute][filterIndex] = found;
	}
}



/**************************************************** 
					mdbCoordinates
****************************************************/
function mdbCoordinates() {
	this.x = 0;
	this.y = 0;
}

mdbCoordinates.prototype.asString = function() {
	return '[' + this.x + ':' + this.y + ']';
}

mdbCoordinates.prototype.isInRange = function(centerX, centerY, range) {
	return ((this.x >= (centerX-range)) && (this.x <= (centerX+range)) && (this.y >= (centerY-range)) && (this.y <= (centerY+range)));
}



/**************************************************** 
					mdbIsland
****************************************************/
function mdbIsland() {
	this.id = '';
	this.name = '';
	this.luxury = 0;
	this.coords = new mdbCoordinates();
	this.lastUpdate = 0;
	this.wonder = 0;
	this.wonderLevel = 0;
	this.sawmillLevel = 0;
	this.luxuryLevel = 0;
	this.cityCount = 0;
	this.processLevel = 0; // 1=world map view, 2=ally list view, 3=island view
	this.cities = [];
	this.islandBox = null;
	this.filterMatch = [true];
}

mdbIsland.prototype.setProcessLevel = function(processLevel) {
	if((processLevel > this.processLevel) || isNaN(this.processLevel)) this.processLevel = processLevel;
}

mdbIsland.prototype.toJSON = function() {
	var result = [];
	result.push(this.id, this.name, this.luxury, this.coords.x, this.coords.y, this.lastUpdate, this.wonder, this.wonderLevel, this.sawmillLevel, this.luxuryLevel, this.cityCount, this.processLevel);
	return result;
}

mdbIsland.prototype.fromJSON = function(json) {
	this.id 		= parseInt(json[0]);
	this.name 		= json[1];
	this.luxury		= json[2];
	this.coords.x 	= parseInt(json[3]);
	this.coords.y 	= parseInt(json[4]);
	this.lastUpdate = parseInt(json[5]);
	this.wonder 	= parseInt(json[6]);
	this.wonderLevel = parseInt(json[7]);
	this.sawmillLevel = parseInt(json[8]);
	this.luxuryLevel = parseInt(json[9]);
	this.cityCount = parseInt(json[10]);
	this.processLevel = parseInt(json[11]);
}

mdbIsland.prototype.filterMatches = function() {
	for (var i = 0; i < this.filterMatch.length; i++) {
		if (this.filterMatch[i]) return true;
	}
	return false;
}

mdbIsland.prototype.clearFilterMatch = function() {
	this.filterMatch = [];
}

// "Island";X-coordinate;Y-coordinate;"Luxury"
mdbIsland.prototype.toCSVString = function() {
	var result = '"' + this.name + '";';
	result += this.coords.x + ';';
	result += this.coords.y + ';';
	result += '"' + this.luxury + '";';
	return result;
}

mdbIsland.prototype.containsSelectedCity = function() {
	var result = false;
	for (var i = 0; i < this.cities.length; i++) {
		if(this.cities[i].selected) {
			result = true;
			break;
		}
	}
	return result;
}

mdbIsland.prototype.daysSinceLastUpdate = function() {
	return Math.floor((new Date().getTime() - this.lastUpdate)/(1000*60*60*24));
}

mdbIsland.prototype.citiesAsString = function() {
	var result = '';
	for (var i = 0; i < this.cities.length; i++) {
		result += this.cities[i].name;
		if(i < this.cities.length - 1) result += ', ';
	}
	return result;
}

mdbIsland.prototype.getRelationship = function() {
	var result = [];
	for (var i = 0; i < this.cities.length; i++) {
		var relationship = this.cities[i].relationship;
		if((relationship != '') && (result.indexOf(relationship) == -1)) result.push(relationship);
	}
	result.sort();
	return result;
}

mdbIsland.prototype.filter = function() {
	if(this.islandBox == null) return;
	// check filtering for the whole island
	var islandMatches = !filtersApplied;
	for (var i = 0; i < this.cities.length; i++) {
		var city = this.cities[i];
		if(city.filterMatches() && city.player.filterMatches() && city.player.alliance.filterMatches()) {
			islandMatches = true;
			break;
		}
	}
	// hide or show islandBox
	if(islandMatches) this.islandBox.style.visibility = 'visible';
	else this.islandBox.style.visibility = 'hidden';
}


mdbIsland.prototype.draw = function(alliances, parent, reportType, highlightInactive, highlightVacation) {
	// do not draw empty islands at all
	if(this.cityCount == 0) return;
	var containsInactive = false;
	var containsVacation = false;
				
	var valueScale = islandScale;
	var size = valueScale * mapScale;
		
	// to put larger boxes on the bottom...
	var zindex = (5000 - size);
	var left = mapXOffset + (this.coords.x*mapScaleRatio*mapScale/10)-Math.round(size/2);
	var top = mapYOffset + (this.coords.y*mapScaleRatio*mapScale/10)-Math.round(size/2);
	if (left < 0) left = 0;
	if (top < 0) top = 0;
	// color by lastUpdate
	/*
	var daysSinceLastUpdate = this.daysSinceLastUpdate();
	var color = 'Gray';
	if (daysSinceLastUpdate < 7) color = 'Green';
	else if (daysSinceLastUpdate < 30) color = "Orange";
	else color = "Red";
	if(this.processLevel != 3) color = 'Gray';
	*/
	// color by relationship
	var color = 'background-color: gray';
	var relationships = this.getRelationship().join();
	if(relationships != '') syslog(relationships);
	switch (relationships) {
		case 'ownCity':
			color = 'background-color: blue';
			break;
		case 'deployedCities':
			color = 'background-color: green';
			break;
		case 'occupiedCities':
			color = 'background-color: red';
			break;
		case 'deployedCities,ownCity':
			color = 'background: linear-gradient(to right, blue 50%, green 50%)';
			break;
		case 'occupiedCities,ownCity':
			color = 'background: linear-gradient(to right, blue 50%, red 50%)';
			break;
		case 'deployedCities,occupiedCities,ownCity':
			color = 'background: linear-gradient(to right, blue 34%, green 33%, red 33%)';
			break;
		case 'deployedCities,occupiedCities':
			color = 'background: linear-gradient(to right, green 50%, red 50%)';
			break;
	}
	if(this.processLevel != 3) color = 'background-color: LightGray';
	
	this.islandBox = htmlDiv();
	this.islandBox.setAttribute('class', 'mdbIsland');
	this.islandBox.setAttribute('id', this.id);
	this.islandBox.setAttribute('x', this.coords.x);
	this.islandBox.setAttribute('y', this.coords.y);
	this.islandBox.setAttribute('title', this.name + ' ' + this.coords.asString());
	this.islandBox.setAttribute('style', 'z-index:'+zindex+';opacity:.60; left:' + left + 'px; top:' + top + 'px; height:' + size + 'px; width:' + size + 'px;' + color);
	// #### Gradient example
	//this.islandBox.setAttribute('style', 'z-index:'+zindex+';opacity:.60; left:' + left + 'px; top:' + top + 'px; height:' + size + 'px; width:' + size + 'px; background: linear-gradient(to right, red 50%, green 50%)');
	/*
	// highligh inactive 
	if ((highlightInactive && containsInactive) || (highlightVacation && containsVacation)) {
		this.islandBox.style.border = '2px solid yellow';
	}
	*/
	this.islandBox.addEventListener('mouseover', onmdbIslandMouseOver, false);
	this.islandBox.addEventListener('click', onmdbIslandClick, false);
	// http://s2.nn.ikariam.com/index.php?currentIslandId=908&view=island&xcoord=33&ycoord=43&submit.x=0&submit.y=0
	this.islandBox.setAttribute('href', '?currentIslandId=' + this.id + '&view=island&xcoord=' + this.coords.x + '&ycoord=' + this.coords.y + '&submit.x=0&submit.y=0');
	parent.appendChild(this.islandBox);
}

function onmdbIslandMouseOver() {
	consoleTime('xplorer.onmdbIslandMouseOver');
	updatePreview(islands.find(parseInt(this.getAttribute('id'))));
	consoleTimeEnd('xplorer.onmdbIslandMouseOver');
}

function onmdbIslandClick(e) {
	// navigate the main (calling) browser window to chosen city's island view
	window.location = this.getAttribute('href');	
}

mdbIsland.prototype.preview = function(parent) {
	// Table body
	var tblBody = htmlTableBody();
	// Header row
	var tblHeader = htmlTableRow();
	tblHeader.appendChild(htmlTableHeaderCell('Player'));
	tblHeader.appendChild(htmlTableHeaderCell('Status'));
	tblHeader.appendChild(htmlTableHeaderCell('Alliance'));
	tblHeader.appendChild(htmlTableHeaderCell('Generals'));
	tblHeader.appendChild(htmlTableHeaderCell('Score'));
	tblHeader.appendChild(htmlTableHeaderCell('Gold'));
	tblHeader.appendChild(htmlTableHeaderCell('City'));
	tblHeader.appendChild(htmlTableHeaderCell('TH'));
	tblHeader.appendChild(htmlTableHeaderCell('Luxury'));
	//tblHeader.appendChild(htmlTableHeaderCell('fCity'));
	//tblHeader.appendChild(htmlTableHeaderCell('fPlayer'));
	//tblHeader.appendChild(htmlTableHeaderCell('fAlliance'));
	tblBody.appendChild(tblHeader);
	for (var i = 0; i < this.cities.length; i++) {
		var tr = htmlTableRow();
		var city = this.cities[i];
		if(!city.filterMatches() || !city.player.filterMatches() || !city.player.alliance.filterMatches()) continue;
		tr.appendChild(htmlTableCell(city.player.name));
		tr.appendChild(htmlTableCell(city.player.status));
		tr.appendChild(htmlTableCell(city.player.alliance.name));
		tr.appendChild(htmlTableCell(intToString(city.player.generals)));
		tr.appendChild(htmlTableCell(intToString(city.player.score)));
		tr.appendChild(htmlTableCell(intToString(city.player.gold)));
		// eg. http://s1.nn.ikariam.com/index.php?view=island&cityId=90272
		var cityLink = htmlAHref('?view=island&cityId=' + city.id, this.cities[i].name);
		cityLink.addEventListener('click', onCityLinkClick, false);
		tr.appendChild(htmlTableCell(cityLink));
		tr.appendChild(htmlTableCell(intToString(city.townHallSize)));
		tr.appendChild(htmlTableCell(intToString(this.luxury)));
		/*
		tr.appendChild(htmlTableCell(city.filterMatches().toString()));
		tr.appendChild(htmlTableCell(city.player.filterMatches().toString()));
		tr.appendChild(htmlTableCell(city.player.alliance.filterMatches().toString()));
		*/
		tblBody.appendChild(tr);
	}
	parent.appendChild(tblBody);
}

function onCityLinkClick(e) {
	// navigate the main (calling) browser window to chosen city's island view
	window.location = this.href;
	// prevent loading the page to the map window
	e.preventDefault();
}

mdbIsland.prototype.getPreviewHeading = function() {
	return this.name + ' ' + this.coords.asString() + ' ' + ikariamTravelTimeAsString(this.coords, selectedOwnCity.island.coords, false) + ' (' + this.daysSinceLastUpdate() + ' days)';
}



/**************************************************** 
					mdbAlliance
****************************************************/
function mdbAlliance() {
	this.id = 0;
	this.name = '';
	this.longName ='';
	this.lastUpdate = 0;
	this.players = [];
	this.filterMatch = [true];
}

mdbAlliance.prototype.toJSON = function() {
	var result = [];
	result.push(this.id, this.name, this.longName, this.lastUpdate);
	return result;
}

mdbAlliance.prototype.fromJSON = function(json) {
	this.id 		= parseInt(json[0]);
	this.name 		= json[1];
	this.longName 	= json[2];
	this.lastUpdate = parseInt(json[3]);
}

mdbAlliance.prototype.filterMatches = function() {
	for (var i = 0; i < this.filterMatch.length; i++) {
		if (this.filterMatch[i]) return true;
	}
	return false;
}

mdbAlliance.prototype.clearFilterMatch = function() {
	this.filterMatch = [];
}



/**************************************************** 
					mdbPlayer
****************************************************/
function mdbPlayer() {
	this.id = 0;
	this.name = '';
	this.status ='';
	this.lastOnline ='';
	this.onlineStatus ='';
	this.allianceId = 0;
	this.alliance = null;
	this.score = 0;
	this.gold = 0;
	this.generals = 0;
	this.buildersScore = 0;
	this.scientistScore = 0;
	this.lastUpdate = 0;
	this.attackScore = 0;
	this.defenceScore = 0;
	this.cities = [];
	this.filterMatch = [true];
}

mdbPlayer.prototype.toJSON = function() {
	var result = [];
	result.push(this.id, this.name, this.status, this.lastOnline, this.onlineStatus, this.allianceId, this.score, this.gold, this.generals, this.buildersScore, this.scientistScore, this.lastUpdate, this.attackScore, this.defenceScore);
	return result;
}

mdbPlayer.prototype.fromJSON = function(json) {
	this.id = 				parseInt(json[0]);
	this.name = 			json[1];
	this.status = 			json[2];
	this.lastOnline = 		json[3];
	this.onlineStatus = 	json[4];
	this.allianceId = 		parseInt(json[5]);
	this.score = 			parseInt(json[6]);
	this.gold = 			parseInt(json[7]);
	this.generals = 		parseInt(json[8]);
	this.buildersScore =	parseInt(json[9]);
	this.scientistScore = 	parseInt(json[10]);
	this.lastUpdate =		parseInt(json[11]);
	this.attackScore = 		parseInt(json[12]);
	this.defenceScore = 	parseInt(json[13]);
}

mdbPlayer.prototype.filterMatches = function() {
	//if (this.filterMatch == []) return true;
	for (var i = 0; i < this.filterMatch.length; i++) {
		if (this.filterMatch[i]) return true;
	}
	return false;
}

mdbPlayer.prototype.clearFilterMatch = function() {
	this.filterMatch = [];
}

mdbPlayer.prototype.setAlliance = function(alliance) {
	this.alliance = alliance;
	this.allianceId = alliance.id;
}

// "Player";"Alliance";"Status";"Score";"Generals";"Gold";
mdbPlayer.prototype.toCSVString = function() {
	var result = '"' + this.name + '";';
	result += '"' + this.alliance + '";';
	result += '"' + this.status + '";';
	result += this.score + ';';
	result += this.generals + ';';
	result += this.gold + ';';
	return result;
}

mdbPlayer.prototype.getCenterOfCities = function() {
	var result = new mdbCoordinates();
	for (var i = 0; i < this.cities.length; i++) {
		result.x = result.x + parseInt(this.cities[i].island.coords.x);
		result.y = result.y + parseInt(this.cities[i].island.coords.y);
	}
	result.x = Math.round(result.x / this.cities.length);
	result.y = Math.round(result.y / this.cities.length);
	return result;
}

mdbPlayer.prototype.draw = function(alliances, parent, reportType, highlightInactive, highlightVacation) {
	var isInactive = false;
	var isOnVacation = false;
	var valueScale = 0;
	if(this.status == 'inactive') isInactive = true;
	if(this.status == 'vacation') isOnVacation = true;
	var playerValue = 0;
	switch (reportType)	{
		case REPORT_TYPE.rtPlayers:
		  playerValue = 1;
		  valueScale = playerScale;
		  break;
		case REPORT_TYPE.rtPlayersGenerals:
		  playerValue = this.generals;
		  valueScale = generalsScorePlayerScale;
		  break;
		case REPORT_TYPE.rtPlayersScore:
		  playerValue = this.score;
		  valueScale = totalScorePlayerScale;
		  break;
		case REPORT_TYPE.rtPlayersGold:
		  playerValue = this.gold;
		  valueScale = goldPlayerScale;
		  break;
		default:
		  syslog('mdbPlayer: Unknown report type ' + reportType);
		  return;
	}
	if(reportType == REPORT_TYPE.rtPlayers) var size = playerValue*valueScale*mapScale
	else var size = Math.round(Math.sqrt(playerValue/valueScale))*mapScale;
	// to put larger boxed on the bottom...
	var zindex = (5000 - size);
	// use the average of city mdbCoordinates as the reporting center
	var coords = this.getCenterOfCities();
	var left = mapXOffset + (coords.x*mapScaleRatio*mapScale/10)-Math.round(size/2);
	var top = mapYOffset + (coords.y*mapScaleRatio*mapScale/10)-Math.round(size/2);
	if (left < 0) left = 0;
	if (top < 0) top = 0;
	var color = ALLIANCE_COLOR[alliances.indexOf(this.alliance.id)];
	if(!color) color = 'Gray';
	var playerBox = htmlDiv();
	playerBox.setAttribute('class', 'mdbPlayer');
	playerBox.setAttribute('id', this.name);
	playerBox.setAttribute('title', this.name + ' ' + coords.asString());
	playerBox.setAttribute('style', 'z-index:'+zindex+';opacity:.60; left:' + left + 'px; top:' + top + 'px; height:' + size + 'px; width:' + size + 'px; background-color:' + color);
	// highligh inactive 
	if ((highlightInactive && isInactive) || (highlightVacation && isOnVacation)) {
		playerBox.style.border = '2px solid yellow';
	}
	playerBox.addEventListener('mouseover', onmdbPlayerMouseOver, false);
	parent.appendChild(playerBox);
}

function onmdbPlayerMouseOver() {
	updatePreview(players.find(this.getAttribute('id')));
}

mdbPlayer.prototype.preview = function(parent) {
	// Table body
	var tblBody = htmlTableBody();
	// Header row for player info
	var tblHeader = htmlTableRow();
	tblHeader.appendChild(htmlTableHeaderCell('Player'));
	tblHeader.appendChild(htmlTableHeaderCell('Status'));
	tblHeader.appendChild(htmlTableHeaderCell('Alliance'));
	tblHeader.appendChild(htmlTableHeaderCell('Generals'));
	tblHeader.appendChild(htmlTableHeaderCell('Score'));
	tblHeader.appendChild(htmlTableHeaderCell('Gold'));
	tblBody.appendChild(tblHeader);
	// Player info content
	var tr = htmlTableRow();
	tr.appendChild(htmlTableCell(this.name));
	tr.appendChild(htmlTableCell(this.status));
	tr.appendChild(htmlTableCell(this.alliance));
	tr.appendChild(htmlTableCell(intToString(this.generals)));
	tr.appendChild(htmlTableCell(intToString(this.score)));
	tr.appendChild(htmlTableCell(intToString(this.gold)));
	tblBody.appendChild(tr);
	// Header row for city information
	var tblHeader = htmlTableRow();
	tblHeader.appendChild(htmlTableHeaderCell('City'));
	tblHeader.appendChild(htmlTableHeaderCell('TH'));
	tblHeader.appendChild(htmlTableHeaderCell('Island'));
	tblHeader.appendChild(htmlTableHeaderCell('Travel'));
	tblBody.appendChild(tblHeader);
	// City info content
	for (var i = 0; i < this.cities.length; i++) {
		var city = this.cities[i];
		var tr = htmlTableRow();
		var cityLink = htmlAHref('?view=island&cityId=' + city.id, city.name);
		cityLink.addEventListener('click', onCityLinkClick, false);
		tr.appendChild(htmlTableCell(cityLink));
		tr.appendChild(htmlTableCell(intToString(city.townHallSize)));
		tr.appendChild(htmlTableCell(city.island.name + ' ' + city.island.coords.asString()));
		var travelTime = ikariamTravelTimeAsString(city.island.coords, selectedOwnCity.island.coords, false);
		tr.appendChild(htmlTableCell(travelTime));
		tblBody.appendChild(tr);
	}
	parent.appendChild(tblBody);
}

mdbPlayer.prototype.getPreviewHeading = function() {
	return this.name + ' ' + this.getCenterOfCities().asString() + ' ' + ikariamTravelTimeAsString(this.getCenterOfCities(), selectedOwnCity.island.coords, false);
}

// Compare function for player sorting
function mdbPlayerCompareByGenerals(a,b) {
	return (b.generals - a.generals);
}

// Compare function for city sorting (city name)
function mdbCityCompareByName(a,b) {
	return a.name.localeCompare(b);
}

// Compare function for city sorting (player name)
function mdbCityCompareByPlayerName(a,b) {
	return a.player.name.localeCompare(b.player.name);
}

// Compare function for city sorting (player generals)
function mdbCityCompareByPlayerGenerals(a,b) {
	return (b.player.generals - a.player.generals);
}



/**************************************************** 
					mdbCity
****************************************************/
function mdbCity() {
	this.id = '';
	this.name = '';
	this.townHallSize = 0;
	this.playerId = 0;
	this.player = null;
	this.islandId = 0;
	this.island = null;
	this.selected = false;
	this.lastUpdate = 0;
	this.wallLevel = 0;
	this.warehouseLevel = 0;
	this.relationship = '';
	this.filterMatch = [true];
}

mdbCity.prototype.toJSON = function() {
	var result = [];
	result.push(this.id, this.name, this.townHallSize, this.playerId, this.islandId, this.lastUpdate, this.wallLevel, this.warehouseLevel);
	return result;
}

mdbCity.prototype.fromJSON = function(json) {
	this.id 			= parseInt(json[0]);
	this.name 			= json[1];
	this.townHallSize 	= parseInt(json[2]);
	this.playerId		= parseInt(json[3]);
	this.islandId		= parseInt(json[4]);
	this.lastUpdate		= parseInt(json[5]);
	this.wallLevel		= parseInt(json[6]);
	this.warehouseLevel = parseInt(json[7]);
}

mdbCity.prototype.filterMatches = function() {
	for (var i = 0; i < this.filterMatch.length; i++) {
		if (this.filterMatch[i]) {
			return true;
		}
	}
	return false;
}

mdbCity.prototype.clearFilterMatch = function() {
	this.filterMatch = [];
}

mdbCity.prototype.setPlayer = function(player) {
	this.player = player;
	this.playerId = player.id;
}

mdbCity.prototype.setIsland = function(island) {
	this.island = island;
	this.islandId = island.id;
}

mdbCity.prototype.toThetaAtiensString = function() {
	// format is "// TARGET=alliance,player,city,x-coordinate,y-coordinate,enemy"
	var result = '// TARGET=';
	result = result + this.player.alliance.name + ',';
	result = result + this.player.name + ',';
	result = result + this.name + ',';
	result = result + this.island.coords.x + ',';
	result = result + this.island.coords.y + ',';
	result = result + 'enemy';
	return result;
}

// "Name";TownHallSize;IslandCSVString;PlayerCSVString;
mdbCity.prototype.toCSVString = function() {
	var result = '"' + this.name + '";';
	result += this.townHallSize + ';';
	result += this.island.toCSVString();
	result += this.player.toCSVString();
	return result;
}



/**************************************************** 
					mapDB
****************************************************/
function mapDB() {
	this.version = mapDBVersion;
	this.keyRoot = getDomain() + '.' + mapDBKey + '.' + this.version;
	this.alliances = new sortedList(compareById, 'id');
	this.players = new sortedList(compareById, 'id');
	this.islands = new sortedList(compareById, 'id');
	this.cities = new sortedList(compareById, 'id');
}

mapDB.prototype.save = function() {
	syslog('mapDB.save started', 3);
	GM_setValue(this.keyRoot + '.alliances', uneval(this.alliances.toJSON()));
	GM_setValue(this.keyRoot + '.players', uneval(this.players.toJSON()));
	GM_setValue(this.keyRoot + '.islands', uneval(this.islands.toJSON()));
	GM_setValue(this.keyRoot + '.cities', uneval(this.cities.toJSON()));
	syslog('mapDB.save finished', 3);
}

mapDB.prototype.load = function() {
	syslog('mapDB.load started', 3);
	this.alliances.list = [];
	this.players.list = [];
	this.islands.list = [];
	this.cities.list = [];
	var alliances = eval(GM_getValue(this.keyRoot + '.alliances', ''));
	if(alliances) {
		for (i = 0; i < alliances.length; i++ ) {
			var alliance = new mdbAlliance();
			alliance.fromJSON(alliances[i]);
			this.alliances.list.push(alliance); //
		}
	}
	var players = eval(GM_getValue(this.keyRoot + '.players', ''));
	if (players) {
		for (i = 0; i < players.length; i++ ) {
			var player = new mdbPlayer();
			player.fromJSON(players[i]);
			this.players.list.push(player);
			player.setAlliance(this.alliances.find(player.allianceId));
			player.alliance.players.push(player);
		}
	}
	var islands = eval(GM_getValue(this.keyRoot + '.islands', ''));
	if (islands) {
		for (i = 0; i < islands.length; i++ ) {
			var island = new mdbIsland();
			island.fromJSON(islands[i]);
			this.islands.list.push(island);
		}
	}
	var cities = eval(GM_getValue(this.keyRoot + '.cities', ''));
	if (cities) {
		for (i = 0; i < cities.length; i++ ) {
			var city = new mdbCity();
			city.fromJSON(cities[i]);
			this.cities.list.push(city);
			city.setPlayer(this.players.find(city.playerId));
			city.player.cities.push(city);
			city.setIsland(this.islands.find(city.islandId));
			city.island.cities.push(city);
		}
	}
	syslog('mapDB.load finished', 3);
}

mapDB.prototype.addAlliance = function(allianceId) {
	var alliance = new mdbAlliance();
	alliance.id = allianceId;
	this.alliances.add(alliance);
	return alliance;
}

mapDB.prototype.addPlayer = function(playerId, alliance) {
	var player = new mdbPlayer();
	player.id = playerId;
	player.setAlliance(alliance);
	this.players.add(player);
	alliance.players.push(player);
	return player;
}

mapDB.prototype.addIsland = function(islandId) {
	var island = new mdbIsland();
	island.id = islandId;
	this.islands.add(island);
	return island;
}

mapDB.prototype.addCity = function(cityId, player, island) {
	var city = new mdbCity();
	city.id = cityId;
	city.setPlayer(player);
	city.setIsland(island);
	this.cities.add(city);
	island.cities.push(city);
	player.cities.push(city);
	return city;
}

// Added 5.7.2015
mapDB.prototype.moveCity = function(city, newIsland) {
	syslog('moveCity started');
	// find the old island and remove city from it
	var oldIsland = this.islands.find(city.islandId);
	oldIsland.cities.removeById(city.id);
	city.setIsland(newIsland);
	newIsland.cities.push(city);
	syslog('moveCity finished');
	return true;
}

// Added 7.7.2015
mapDB.prototype.movePlayer = function(player, newAlliance) {
	syslog('movePayer started');
	// find the old alliance and remove player from it
	var oldAlliance = this.alliances.find(player.allianceId);
	oldAlliance.players.removeById(player.id);
	player.setAlliance(newAlliance);
	newAlliance.players.push(player);
	syslog('movePlayer finished');
	return true;
}


/**************************************************** 
					xplorer
****************************************************/
function xplorer() {
	this.db = new mapDB();
	xplorerDB = this.db;
}

xplorer.prototype.init = function() {
	consoleTime('xplorer.init');
	syslog('xplorer.init', 2);
	// add icon
	var button = htmlImg(IMAGES.xplorer, 64,64);
	button.setAttribute('style', 'position: absolute; top: 55px; left: 640px; cursor: pointer');
	button.setAttribute('title', 'Ikariam Xplorer');
	button.addEventListener('click', xplorerOnClickHandler, false);
	var header = document.getElementById('GF_toolbar');
	header.appendChild(button);
	syslog('xplorer.init finished', 2);
	consoleTimeEnd('xplorer.init');
}

xplorer.prototype.processViews = function() {
	consoleTime('xplorer.processViews');
	syslog('xplorer.processViews', 2);
	
	/*
	if (ikariamIsAllyListView()) {
		consoleTime('xplorer.ProcessAllyList');
		processAllyList();
		consoleTimeEnd('xplorer.ProcessAllyList');
	}
	*/

	if (ikariamIsIslandView()) {
		consoleTime('xplorer.processIslandView');
		setTimeout(function() {processIslandView();}, 0);
		consoleTimeEnd('xplorer.processIslandView');
	}

	/*
	if (ikariamIsMilitaryMovementsView()) {
		consoleTime('xplorer.processMilitaryMovementsView');
		processMilitaryMovementsView();
		consoleTimeEnd('xplorer.processMilitaryMovementsView');
	}
	*/
	
	if (ikariamIsWorldMapView()) {
		consoleTime('xplorer.processWorldMapView');
		initWorldMapView();
		setTimeout(function() {processWorldMapView();}, 0);
		consoleTimeEnd('xplorer.processWorldMapView');
	}
	
	syslog('xplorer.processViews finished', 2);
	consoleTimeEnd('xplorer.processViews');
}




function liveFilter() {
	consoleTime('xplorer.liveFilter');
	
	// check if any filter is applied 
	// #### add all the filter inputs/selects
	filtersApplied = ((allianceInput.value != '') || (playerInput.value != '') || (cityInput.value != ''));
	
	//filter map
	filterMap()
	
	// update legend
	updateLegend(alliances);
	
	// update status
	var searchStatus = 'Filtering finished at ' + getTimestamp();
	updateStatus(alliances, players, islands, cities, searchStatus);
	consoleTimeEnd('xplorer.liveFilter');
}

function filterMap() {
	for (var i = 0; i < islands.list.length; i++) { 
		islands.list[i].filter();
	}
}

function drawMap() {
	syslog('drawMap started', 3);

	// first clear the old elements from the map
	removeElementsByClassname(windowRef.document, 'mdbIsland', mapContainer);
	removeElementsByClassname(windowRef.document, 'mdbPlayer', mapContainer);
	removeElementsByClassname(windowRef.document, 'ownmdbIsland', mapContainer);
	
	// then draw all the islands
	for (var i = 0; i < islands.list.length; i++) { 
		islands.list[i].draw(alliances, mapContainer, null, highlightInactiveCheckbox.checked, highlightVacationCheckbox.checked);
	}
	
	// and finally apply any potential filters
	filterMap();
	
	/* all this is disabled for now
	
	// draw own islands
	drawOwnIslands(mapContainer);
	
	// draw the data to the map
	var reportType = REPORT_TYPE_INDEX[parseInt(reportSelect.options[reportSelect.selectedIndex].value)];
	switch (reportType) {
		case REPORT_TYPE.rtIslands: case REPORT_TYPE.rtIslandsGenerals: case REPORT_TYPE.rtIslandsScore: case REPORT_TYPE.rtIslandsGold: 
			for (var i = 0; i < islands.list.length; i++) { 
				islands.list[i].draw(alliances, mapContainer, reportType, highlightInactiveCheckbox.checked, highlightVacationCheckbox.checked);
			}
			break;
		// TODO player based views need to be updated
		case REPORT_TYPE.rtPlayers: case REPORT_TYPE.rtPlayersGenerals: case REPORT_TYPE.rtPlayersScore: case REPORT_TYPE.rtPlayersGold: 
			for (var i = 0; i < players.list.length; i++) { 
				players.list[i].draw(alliances, mapContainer, reportType, highlightInactiveCheckbox.checked, highlightVacationCheckbox.checked);
			}
			break;

		default: 
			syslog('drawMap: Unknown report type ' + reportType);
			break;
	}
	*/
	syslog('drawMap finished', 3);
}


// #### do we need this any more? Own islands should be highlighted when drawing normal islands
function drawOwnIslands(parent) {

	/*
	for (var i = 0; i < ownIslands.length; i++) {
		var island = ownIslands[i];
		var size = (islandScale-1)*mapScale
		// own islands on the bottom
		var zindex = 5000;
		// use the average of city mdbCoordinates as the reporting center
		var coords = island.coords;
		var left = mapXOffset + (coords.x*mapScaleRatio*mapScale/10)-Math.round(size/2);
		var top = mapYOffset + (coords.y*mapScaleRatio*mapScale/10)-Math.round(size/2);
		if (left < 0) left = 0;
		if (top < 0) top = 0;
		var color = 'black';
		var ownIslandBox = htmlDiv();
		ownIslandBox.setAttribute('class', 'ownmdbIsland');
		ownIslandBox.setAttribute('id', island.name);
		ownIslandBox.setAttribute('title', island.citiesAsString() + ' ' + island.coords.asString());
		ownIslandBox.setAttribute('style', 'z-index:'+zindex+';opacity:.60; left:' + left + 'px; top:' + top + 'px; height:' + size + 'px; width:' + size + 'px; background-color:' + color);
		// highligh selected one
		if (island.containsSelectedCity()) {
			ownIslandBox.style.border = '2px solid red';
		}
		parent.appendChild(ownIslandBox);
	}
	*/
}

xplorerOnClickHandler = function() {
	displayMapWindow();
}


function updatePreview(item) {
	removeAllChildElements(previewContainer);
	previewContainer.appendChild(xplorerHeading('Preview - ' + item.getPreviewHeading()));
	item.preview(previewContainer);
}

function updateLegend(alliances) {
	removeAllChildElements(legendContainer);
	var tblBody = htmlTableBody();
	var tblHeader = htmlTableRow();
	for (var i = 0; i < alliances.list.length; i++) {
		var th = htmlTableHeaderCell(alliances.list[i].name);
		th.setAttribute('style', 'opacity:0.8;background:none;background-color:'+ ALLIANCE_COLOR[i] + ';color:' +  ALLIANCE_TEXT_COLOR[i]);
		tblHeader.appendChild(th);
	}
	tblBody.appendChild(tblHeader);
	legendContainer.appendChild(tblBody);
}

function updateStatus(alliances, players, islands, cities, searchStatus) {
	removeAllChildElements(statusContainer);
	var tblBody = htmlTableBody();
	var tblHeader = htmlTableRow();
	tblHeader.appendChild(htmlTableHeaderCell('Server/world: ' + ikariamGetServer() + '/' + ikariamGetWorld()));
	tblHeader.appendChild(htmlTableHeaderCell('Alliances: ' + alliances.list.length));
	tblHeader.appendChild(htmlTableHeaderCell('Players: ' + players.list.length));
	tblHeader.appendChild(htmlTableHeaderCell('Islands: ' + islands.list.length));
	tblHeader.appendChild(htmlTableHeaderCell('Cities: ' + cities.list.length));
	tblHeader.appendChild(htmlTableHeaderCell('Zoom: ' + mapScale));
	tblHeader.appendChild(htmlTableHeaderCell('Status: ' + searchStatus));
	tblBody.appendChild(tblHeader);
	statusContainer.appendChild(tblBody);
}


function createSearchContainer(parent) {
	var result = htmlDiv();
	result.setAttribute('class', 'searchContainer');
	result.setAttribute('style', 'display:none');
	parent.appendChild(result);

	// Filter
	result.appendChild(xplorerHeading('Filter'));
	allianceInput = xplorerSearchInputDiv(result, 'Alliance', '90px', '', onAllianceInputKeyUp);
	playerInput = xplorerSearchInputDiv(result, 'Player', '90px', '', onPlayerInputKeyUp);
	cityInput = xplorerSearchInputDiv(result, 'City', '90px', '', onCityInputKeyUp);
	/*
	playerStatusSelect = xplorerSearchSelectDiv(result, 'Status',  PLAYER_STATUS_SELECT_INDEX, onSelectChange);
	var distanceValues = [];
	for(i=1; i < 100; i++ ) distanceValues.push(i);
	distanceSelect = xplorerSearchSelectDiv(result, 'Distance',  distanceValues, onSelectChange);
	luxuryResourceSelect = xplorerSearchSelectDiv(result, 'Luxury',  LUXURY_RESOURCE_SELECT_INDEX, onSelectChange);
	*/
	// Town hall size drop down (distanceSelect as global var)
	/*
	var townHallLevelDiv = xplorerSearchDiv();
	townHallLevelDiv.appendChild(xplorerCaption('Townhall size'));
	townHallLevelLowSelect = xplorerSelect();
	townHallLevelLowSelect.appendChild(new Option('Any',0));
	for(i=1; i < 41; i++ ) {
		townHallLevelLowSelect.appendChild(new Option(i,i));
	}
	townHallLevelDiv.appendChild(townHallLevelLowSelect);
	townHallLevelHighSelect = xplorerSelect();
	townHallLevelHighSelect.appendChild(new Option('Any',0));
	for(i=1; i < 41; i++ ) {
		townHallLevelHighSelect.appendChild(new Option(i,i));
	}
	townHallLevelDiv.appendChild(townHallLevelHighSelect);
	result.appendChild(townHallLevelDiv);
	
	
	// Military score input min-max (input fields are stored in a global variable)
	var militaryScoreDiv = xplorerSearchDiv();
	militaryScoreDiv.appendChild(xplorerCaption('Military score'));
	militaryScoreLowInput = xplorerInput();
	militaryScoreLowInput.style.width = '40px';
	militaryScoreDiv.appendChild(militaryScoreLowInput);
	militaryScoreHighInput = xplorerInput();
	militaryScoreHighInput.style.width = '40px';
	militaryScoreDiv.appendChild(militaryScoreHighInput);
	result.appendChild(militaryScoreDiv);
*/
	
	// #### buttons hidden for now (no use)
	/*
	var searchButtonDefs = [{name: 'Search', onClick: onSearchClick}, {name:'Clear map', onClick: onClearMapClick}, {name: 'Export', onClick: onExportClick}];
	result.appendChild(xplorerButtonsDiv(searchButtonDefs));
	*/
	
	// Map settings
	result.appendChild(xplorerHeading('Map settings'));
	// Zoom buttons
	result.appendChild(xplorerLabel('Zoom'));
	var zoomInButton = xplorerButton('+');
	zoomInButton.addEventListener('click', onZoomInClick, false);
	result.appendChild(zoomInButton);
	var zoomOutButton = xplorerButton('-');
	zoomOutButton.addEventListener('click', onZoomOutClick, false);
	result.appendChild(zoomOutButton);
	
	// #### hidden for now
	// Report drop down (stored as a global variable)
	/*
	reportSelect = xplorerSelect();
	for(i=0; i < REPORT_TYPE_INDEX.length; i++ ) {
		reportSelect.appendChild(new Option(REPORT_TYPE_INDEX[i],i));
	}
	reportSelect.addEventListener('change', onReportSelectChange, false);
	result.appendChild(reportSelect);
	result.appendChild(htmlBr());
	*/
	
	// Highlight inactive/vacation checkboxes
	result.appendChild(xplorerLabel('Highlight: Inactive'));
	highlightInactiveCheckbox = xplorerCheckbox();
	highlightInactiveCheckbox.checked = false;
	highlightInactiveCheckbox.addEventListener('change', onHighlightCheckboxChange, false);
	result.appendChild(highlightInactiveCheckbox);
	result.appendChild(xplorerLabel('Vacation'));
	highlightVacationCheckbox = xplorerCheckbox();
	highlightVacationCheckbox.checked = false;
	highlightVacationCheckbox.addEventListener('change', onHighlightCheckboxChange, false);
	result.appendChild(highlightVacationCheckbox);

	// Scan
	result.appendChild(xplorerHeading('Scan'));
	scanCenterXInput = xplorerSearchInputDiv(result, 'Center-x', '50px', '');
	scanCenterYInput = xplorerSearchInputDiv(result, 'Center-y', '50px', '');
	scanRangeInput = xplorerSearchInputDiv(result, 'Range', '45px', '5');
	scanDelayMinInput = xplorerSearchInputDiv(result, 'Delay-min', '60px', '5000');
	scanDelayMaxInput = xplorerSearchInputDiv(result, 'Delay-max', '60px', '8000');
	rescanWaitTimeInput = xplorerSearchInputDiv(result, 'Rescan wait (days)', '120px', '7');
	var scanButtonDefs = [{name: 'Scan', onClick: onScanClick}, {name:'WorldScan', onClick: onWorldScanClick}];
	result.appendChild(xplorerButtonsDiv(scanButtonDefs));

	return result;
}

function onNewClick() {
	syslog('onNewClick',1);
}

function onDeleteClick() {
	syslog('onDeleteClick',1);
}

// ?view=worldmap_iso&islandX=43&islandY=42
function onWorldScanClick() {
	if (!windowRef.confirm('This will view/scan world map view at four points. Continue?')) return;
	// four fixed points which cover the whole world map (8 second delay between)
	browseToWithDelay('?view=worldmap_iso&islandX=25&islandY=25', 0, '');
	browseToWithDelay('?view=worldmap_iso&islandX=75&islandY=25', 8000, '');
	browseToWithDelay('?view=worldmap_iso&islandX=25&islandY=75', 16000, '');
	browseToWithDelay('?view=worldmap_iso&islandX=75&islandY=75', 24000, '');
}

function onScanClick() {
	var scanCenterX = parseInt(scanCenterXInput.value);
	var scanCenterY = parseInt(scanCenterYInput.value);
	var scanRange = parseInt(scanRangeInput.value);
	var minDelay = parseInt(scanDelayMinInput.value);
	var maxDelay = parseInt(scanDelayMaxInput.value);
	var rescanWaitTime = parseInt(rescanWaitTimeInput.value);
	var baseDelay = 0;
	var delay = 0;
	var expIslands = [];
	
	// first go through all islands to find the ones which are in range
	// and have not been scanned for over 7 days
	for (var i = 0; i < islands.list.length; i++) {
		var island = islands.list[i];
		if (island.coords.isInRange(scanCenterX, scanCenterY, scanRange)) {
			syslog('Is in range, x=' + island.coords.x + ', y=' + island.coords.y + ', id=' + island.id + ', updated=' + island.daysSinceLastUpdate() + ', citycount=' + island.cityCount);
			// Must have cities and either over 7 days since last update or island view not visited at all
			if((island.cityCount > 0) && ((island.daysSinceLastUpdate() > rescanWaitTime) || (island.processLevel != 3))) {
				expIslands.push(island);
			}
		}
	}
	
	// ask confirmation before proceeding
	var duration = Math.round(((minDelay + maxDelay)/2/1000/60)*expIslands.length);
	syslog((minDelay + maxDelay)/2);
	if (!windowRef.confirm('Found ' + expIslands.length + ' islands to be scanned. Scanning will take ' + duration + ' minutes. Continue?')) return;
	
	// launch scanning
	for (var i = 0; i < expIslands.length; i++) {
		var island = expIslands[i];
		var islandLink = htmlAHref('?view=island&islandId=' + island.id, island.name);
		delay = Math.random() * (maxDelay - minDelay) + minDelay;
		syslog(islandLink.href + ", delay=" + baseDelay);
		var status = '';
		if (i == expIslands.length - 1) status = 'Scanning finished';
		else {
			status = 'Scanning progress ' + (i+1) + '/' + expIslands.length + ' (' + Math.round((expIslands.length-i)/expIslands.length*duration) + ' minutes left)'
		}
		browseToWithDelay(islandLink.href, baseDelay, status);
		baseDelay = baseDelay + delay;
	}
}


function onSearchClick() {
	/*
	syslog('Ikariam Xplorer - Search activated',1);
	updateStatus(alliances, players, islands, cities, 'Search in progress...');
	
	// TODO replace ikaCore with own data
	var searchParams = new ikaCoreSearchParams();
	searchParams.server = ikariamGetServer();
	searchParams.world = ikariamGetWorld();
	searchParams.alliance = allianceInput.value;
	searchParams.player = playerInput.value;
	searchParams.city = cityInput.value;
	if(playerStatusSelect.selectedIndex > 0) {
		searchParams.status = playerStatusSelect.selectedIndex - 1;
	}
	searchParams.distance = distanceSelect.selectedIndex;
	searchParams.luxuryResource = luxuryResourceSelect.selectedIndex;
	if(townHallLevelLowSelect.selectedIndex > 0) searchParams.townHallLevelLow = townHallLevelLowSelect.selectedIndex;
	if(townHallLevelHighSelect.selectedIndex > 0) searchParams.townHallLevelHigh = townHallLevelHighSelect.selectedIndex;
	var milLow = parseInt(militaryScoreLowInput.value);
	if(!isNaN(milLow)) searchParams.militaryScoreLow = milLow;
	var milHigh = parseInt(militaryScoreHighInput.value);
	if(!isNaN(milHigh)) searchParams.militaryScoreHigh = milHigh;

	searchIkaCore(searchParams, SearchResultHandler);
	syslog('Ikariam Xplorer - Search finished',1);
	*/
}

function onClearMapClick() {
	syslog('Ikariam War Room - Clear Map activated',1);
	
	/*
	cities = [];
	players = [];
	alliances = [];
	islands = [];
	drawMap();
	updateLegend(alliances);
	updateStatus(alliances, players, islands, cities, 'Map cleared');
	// TODO replace with filter clear
	*/
	syslog('Ikariam War Room - Clear Map finished',1);
}
function onExportClick() {
	syslog('Ikariam War Room - Export activated',1);
	explore();
	//displayExportWindow(cities, alliances, players);
	updateStatus(alliances, players, islands, cities, 'Export finished');
	syslog('Ikariam War Room - Export finished',1);
}

function onZoomInClick() {
	syslog('onZoomInClick');
	mapScale += 1;
	onZoomChange();
}

function onZoomOutClick() {
	syslog('onZoomOutClick');
	mapScale -= 1;
	if(mapScale < 1) mapScale = 1;
	onZoomChange();
}

function onZoomChange() {
	// calculate the current center position as factor
	var xFactor = (mapContainer.scrollLeft + mapContainer.clientWidth/2) / mapContainer.scrollWidth;
	var yFactor = (mapContainer.scrollTop + mapContainer.clientHeight/2) / mapContainer.scrollHeight;
	drawGrid();
	drawMap();
	// re-position the scrollbars to get a real zoom effect
	mapContainer.scrollLeft = xFactor * mapContainer.scrollWidth - mapContainer.clientWidth/2;
	mapContainer.scrollTop = yFactor * mapContainer.scrollHeight - mapContainer.clientHeight/2;
}

function onReportSelectChange() {
	drawMap();
}

function onHighlightCheckboxChange() {
	drawMap();
}

function createLegendContainer(parent) {
	var result = htmlDiv();
	result.setAttribute('class', 'legendContainer');
	result.setAttribute('style', 'display:none;overflow:auto');
	parent.appendChild(result);
	return result;
}

function createStatusContainer(parent) {
	var result = htmlDiv();
	result.setAttribute('class', 'statusContainer');
	result.setAttribute('style', 'display:none;overflow:auto');
	parent.appendChild(result);
	return result;
}

function createPreviewContainer(parent) {
	var result = htmlDiv();
	result.setAttribute('class', 'previewContainer');
	result.setAttribute('style', 'display:none;overflow:auto');
	parent.appendChild(result);
	result.appendChild(xplorerHeading('Preview'));
	return result;
}

function createMapContainer(parent) {
	var result = htmlDiv();
	result.setAttribute('class', 'mapContainer');
	result.setAttribute('style', 'display:none;overflow:auto');
	result.addEventListener('dragstart', onMapContainerDragStart, false);
	parent.appendChild(result);
	// Heading
	var heading = xplorerHeading('Ikariam Xplorer ' + version);
	
	// Update button
	updateButton = xplorerButton('Update');
	updateButton.setAttribute('class', 'xplorer_Update_button');
	updateButton.style.display = 'none';
	updateButton.addEventListener('click', onUpdateClick, false);
	heading.appendChild(updateButton);
	result.appendChild(heading);
	
	// Canvas
	mapCanvas = document.createElement('canvas');
	//canvas.setAttribute('width', 1000);
	//canvas.setAttribute('height', 1000);
	//canvas.setAttribute('z-index', 10000);
	result.appendChild(mapCanvas);
		
	return result;
}

function onUpdateClick(e) {
	syslog('War Room - version update activated', 1);
	// hides the update button next time the War Room is opened
	GM_deleteValue('xplorer.newVersion');
	// update check will be performed next time the War Room is opened
	// (in case user cancels the installation)
	GM_deleteValue('xplorer.lastUpdateCheck');
	location.href = scriptURL;
}


function onGridSurroundMouseDown(e) {
	if(!e) return;
	var currentX = mapContainer.scrollLeft + e.clientX;
	var currentY = mapContainer.scrollTop + e.clientY;
	dragPrevX = currentX;
	dragPrevY = currentY;
	return false;
}

function onGridSurroundMouseUp(e) {
	if(!e) return;
	var currentX = mapContainer.scrollLeft + e.clientX;
	var currentY = mapContainer.scrollTop + e.clientY;
	mapContainer.scrollLeft = mapContainer.scrollLeft + (dragPrevX-currentX);
	mapContainer.scrollTop = mapContainer.scrollTop + (dragPrevY-currentY);
	return false;
}

// Zoom with mousewheel (center on mouse cursor location)
function onGridSurroundMouseScroll(e) {
	var delta = 0;
	if (e.detail) { 
		delta = -e.detail/3;
	}
	// delta is positive on scroll up and vice versa
	if (delta > 0) mapScale += 1;
	else if (delta < 0) mapScale -= 1;
	if(mapScale < 1) mapScale = 1;
	
	// zoom on mouse cursor location (make it the new center)
	var xFactor = (mapContainer.scrollLeft + e.clientX) / mapContainer.scrollWidth;
	var yFactor = (mapContainer.scrollTop + e.clientY) / mapContainer.scrollHeight;
	drawGrid();
	drawMap();
	// re-position the scrollbars to get a real zoom effect
	mapContainer.scrollLeft = xFactor * mapContainer.scrollWidth - mapContainer.clientWidth/2;
	mapContainer.scrollTop = yFactor * mapContainer.scrollHeight - mapContainer.clientHeight/2;
	
	e.preventDefault();
	return false;
}

function onMapContainerDragStart(e) {
	e.preventDefault();
	return false;
}


function drawGrid () {
	// first remove previous grid elements
	removeElementsByClassname(windowRef.document, 'gridElem', mapContainer);
	removeElementsByClassname(windowRef.document, 'gridSurroundingBox', mapContainer);
	removeElementsByClassname(windowRef.document, 'hTick', mapContainer);
	removeElementsByClassname(windowRef.document, 'vTick', mapContainer);
	
	// vertical grid lines
	for (var i = 0; i < 5; i++) {
		var gridVert = htmlDiv();
		var top = mapYOffset;
		var left = mapXOffset + (i*mapScale*mapScaleRatio)*2;
		var width = mapScale*mapScaleRatio-1;
		var height = mapScale*mapScaleRatio*10-1;
		gridVert.setAttribute('class', 'gridElem');
		gridVert.setAttribute('style', 'z-index:5;width:'+width+'px; height:'+height+'px; top:'+top+'px; left:'+left+'px');
		//gridVert.addEventListener('selectstart', onMapContainerSelectStart, false);
		mapContainer.appendChild(gridVert);
	}
	
	// horizontal grid lines
	for (var i = 0; i < 5; i++) {
		var gridHoriz = htmlDiv();
		var top = mapYOffset + (i*mapScale*mapScaleRatio)*2;
		var left = mapXOffset;
		var width = mapScale*mapScaleRatio*10-1;
		var height = mapScale*mapScaleRatio-1;
		gridHoriz.setAttribute('class', 'gridElem');
		gridHoriz.setAttribute('style', 'z-index:5;width:'+width+'px; height:'+height+'px; top:'+top+'px; left:'+left+'px');
		//gridHoriz.addEventListener('selectstart', onMapContainerSelectStart, false);
		mapContainer.appendChild(gridHoriz);
	}
	
	// Surrounding box
	var gridSurr = htmlDiv();
	var top = mapYOffset;
	var left = mapXOffset;
	var width = mapScale*mapScaleRatio*10-2;
	var height = mapScale*mapScaleRatio*10-2;
	gridSurr.setAttribute('class', 'gridSurroundingBox');
	gridSurr.setAttribute('style', 'cursor:all-scroll;z-index:10;width:'+width+'px; height:'+height+'px; top:'+top+'px; left:'+left+'px');
	gridSurr.addEventListener('mousedown', onGridSurroundMouseDown, false);
	gridSurr.addEventListener('mouseup', onGridSurroundMouseUp, false);
	gridSurr.addEventListener('DOMMouseScroll', onGridSurroundMouseScroll, false);
	mapContainer.appendChild(gridSurr);	
	
	// h-ticks
	for (var i = 1; i < 11; i++) {
		var hTick = htmlDiv();
		var height = 15;
		var top = mapYOffset-height-3;
		var left = mapXOffset + i*mapScale*mapScaleRatio-(mapScale*mapScaleRatio/2);
		var width = mapScale*mapScaleRatio-1;
		hTick.setAttribute('class', 'hTick');
		hTick.textContent = i*10;
		hTick.setAttribute('style', 'z-index:5;width:'+width+'px; height:'+height+'px; top:'+top+'px; left:'+left+'px');
		//hTick.addEventListener('selectstart', onMapContainerSelectStart, false);
		mapContainer.appendChild(hTick);
	}
	
	// v-ticks
	for (var i = 1; i < 11; i++) {
		var vTick = htmlDiv();
		var height = 15;
		var top = mapYOffset + i*mapScale*mapScaleRatio-height/2;
		var left = mapXOffset + mapScale*mapScaleRatio*10+5;
		var width = 15;
		vTick.setAttribute('class', 'vTick');
		vTick.textContent = i*10;
		vTick.setAttribute('style', 'z-index:5;width:'+width+'px; height:'+height+'px; top:'+top+'px; left:'+left+'px');
		//vTick.addEventListener('selectstart', onMapContainerSelectStart, false);
		mapContainer.appendChild(vTick);
	}
}

function createIkaCoreAddContainer(parent) {
	var ikaCoreAddFrame = windowRef.document.createElement('iframe');
	ikaCoreAddFrame.setAttribute('id', 'ikaCoreAddContainer');
	ikaCoreAddFrame.setAttribute('scrolling', 'NO');
	//ikaCoreAddFrame.setAttribute('src', 'http://www.ika-core.org/ikariam.html?u=25');
	ikaCoreAddFrame.setAttribute('disabled', 'true');
	parent.appendChild(ikaCoreAddFrame);
	return ikaCoreAddFrame;
}

function refreshSizesAndPositions() {
	syslog('refreshSizesAndPositions started', 3);
	// calculate new map scale ratio
	xRatio = defaultMapScaleRatio * windowRef.outerWidth/defaultWindowWidth;
	yRatio = defaultMapScaleRatio * windowRef.outerHeight/defaultWindowHeight;
	mapScaleRatio = Math.min(xRatio, yRatio);
	// reposition and resize mapContainer
	mapContainer.style.top = 10 + 'px';
	mapContainer.style.left = 10 + 'px';
	mapContainer.style.width = mapScaleRatio*10 + mapXOffset*3 + 'px';
	mapContainer.style.height = mapContainer.style.width;
	mapContainer.style.display = 'block';
	// resize map canvas
	mapCanvas.style.position = 'absolute';
	mapCanvas.style.top = mapYOffset +'px';
	mapCanvas.style.left = mapXOffset + 'px';
	mapCanvas.width = mapScale*mapScaleRatio*10;
	mapCanvas.height = mapScale*mapScaleRatio*10;
	
	// #### testing
	var ctx = mapCanvas.getContext("2d");
	ctx.beginPath();
	ctx.moveTo(0, 0);  
	ctx.lineTo(mapCanvas.width, 0);
	ctx.lineTo(mapCanvas.width, mapCanvas.height);
	ctx.lineTo(0, mapCanvas.height);
	ctx.lineTo(0, 0);
	ctx.stroke();
	
	// reposition and resize previewContainer
	previewContainer.style.top = mapContainer.offsetTop + 'px';
	previewContainer.style.height = mapContainer.offsetHeight*0.5 + 'px';
	previewContainer.style.left = mapContainer.offsetLeft + mapContainer.offsetWidth + 5 + 'px';
	previewContainer.style.width = windowRef.outerWidth - mapContainer.offsetLeft - mapContainer.offsetWidth - 35 + 'px';
	previewContainer.style.display = 'block';
	// reposition and resize searchContainer
	searchContainer.style.top = previewContainer.offsetTop + previewContainer.offsetHeight + 0 + 'px';
	searchContainer.style.height = mapContainer.offsetHeight*0.5 - 7 + 'px';
	searchContainer.style.left = previewContainer.offsetLeft + 'px';
	searchContainer.style.width = previewContainer.style.width;
	searchContainer.style.display = 'block';
	// reposition and resize legendContainer
	legendContainer.style.top = mapContainer.offsetTop + mapContainer.offsetHeight + 0 + 'px';
	//legendContainer.style.height = '20px';
	legendContainer.style.left = mapContainer.offsetLeft + 'px';
	legendContainer.style.width = mapContainer.offsetWidth + searchContainer.offsetWidth + 2 + 'px';
	legendContainer.style.display = 'block';
	// reposition and resize statusContainer
	statusContainer.style.top = legendContainer.offsetTop + legendContainer.offsetHeight + 0 + 'px';
	//statusContainer.style.height = '20px';
	statusContainer.style.left = mapContainer.offsetLeft + 'px';
	statusContainer.style.width = legendContainer.style.width;
	statusContainer.style.display = 'block';
	// reposition (and resize) ikaCoreAddContainer
	//ikaCoreAddContainer.style.top = statusContainer.offsetTop + statusContainer.offsetHeight + 5 + 'px';
	//ikaCoreAddContainer.style.display = 'block';
	// re-draw grid and map items
	drawGrid();
	drawMap();
	syslog('refreshSizesAndPositions finished', 3);
}

function onWindowResize() {
   if(windowResizeTimeOut != null) clearTimeout(windowResizeTimeOut);
   windowResizeTimeOut = setTimeout(refreshSizesAndPositions, 300);
}

function onWindowKeypress(e) {
	switch (e.keyCode)	{
		case 13:
		  onSearchClick();
		  break;
		default:
		  return;
	}
}

function onAllianceInputKeyUp(e) {
	consoleTime('xplorer.onAllianceInputKeyUp');
	if (allianceInput.value != allianceInput.lastValue) {
		alliances.filterString(allianceInput.value, 'name', 'filterMatch', filterIndex, false, true);
		allianceInput.lastValue = allianceInput.value;
		liveFilter();
	}
	consoleTimeEnd('xplorer.onAllianceInputKeyUp');	
}
function onPlayerInputKeyUp(e) {
	consoleTime('xplorer.onPlayerInputKeyUp');
	if (playerInput.value != playerInput.lastValue) {
		players.filterString(playerInput.value, 'name', 'filterMatch', filterIndex, false, true);
		playerInput.lastValue = playerInput.value;
		liveFilter();
	}
	consoleTimeEnd('xplorer.onPlayerInputKeyUp');
}

function onCityInputKeyUp(e) {
	consoleTime('xplorer.onCityInputKeyUp');
	if (cityInput.value != cityInput.lastValue) {
		cities.filterString(cityInput.value, 'name', 'filterMatch', filterIndex, false, true);
		cityInput.lastValue = cityInput.value;
		liveFilter();
	}
	consoleTimeEnd('xplorer.onCityInputKeyUp');
}

function onSelectChange(e) {
	// #### TODO this must be broken to several event handlers for each item to be performance optimised
	// also now supports only player status which seems to be broken too
	var playerStatus = PLAYER_STATUS_SELECT_INDEX[playerStatusSelect.selectedIndex]
	// case-sensitive since otherwise Active would match also Inactive
	if (playerStatus == 'Any') playerStatus = '';
	players.filterString(playerStatus, 'status', 'filterMatch', filterIndex, false, false);
	liveFilter();
}


function checkVersion() {
	// get the stored last update check time
	var lastUpdateCheck = GM_getValue('xplorer.lastUpdateCheck', 0);
	var now = parseInt(new Date().getTime());
	
	// calculate time elapsed since last update check 
	var performCheck = ((now - parseInt(lastUpdateCheck)) > updateCheckInterval);
	
	// perform update check if necessary
	if (performCheck) {
		syslog('War Room performing update check', 1);
		// get the script source
		try	{
			GM_xmlhttpRequest({
				method: "GET",
				url: scriptMetaURL,
				onload: function(xhr) { checkVersionResponseHandler(xhr.responseText); }
			});
			GM_setValue('xplorer.lastUpdateCheck', '' + now);
		}
		catch(err) {
			syslog('checkVersion, GM_xmlhttpRequest failed: ' + err.description, 1);
		}
	}
	else {
		// also check if an update was found earlier on
		var newVersion = GM_getValue('xplorer.newVersion', '');
		if(newVersion != '') {
			updateButton.value = 'Update to ' + newVersion;
			updateButton.style.display = 'inline-block';
		}
	}
}

function checkVersionResponseHandler(responseText) {
	var lines = responseText.split('\n');
	var latestVersion = null;
	for(i=0; i < lines.length; i++ ) {
		if(lines[i].indexOf(versionTag) != -1) {
			// extract the version info from line (excluding any whitespace)
			latestVersion = lines[i].substr(versionTag.length).replace(/^\s*|\s*$/g,'');
		}
	}
	if(!latestVersion) {
		syslog('War Room - version information not found in the latest script source', 1);
		return;
	}
	if (version != latestVersion) {
		syslog('War Room, new version available (current: ' + version + ' new: ' + latestVersion + ')', 1);
		updateButton.value = 'Update to ' + latestVersion;
		updateButton.style.display = 'inline-block';
		GM_setValue('xplorer.newVersion', latestVersion);
	}
}

function browseToWithDelay(href, delay, status) {
	windowRef.setTimeout(function() {
		syslog('href=' + href + ', delay=' + delay);
		window.location = href;
		updateStatus(alliances, players, islands, cities, status);
	}, delay);
}

function displayExportWindow (cities, alliances, players) {
	// TODO modify to expect a sortedList instead of an array of items
    var windowName  = 'War Room Export';
	var windowWidth = 900;
	var windowHeight = 800;
    var windowOptions  = 'width='+windowWidth+',height='+windowHeight+',status=no,toolbar=0,copyhistory=no,';
    windowOptions += 'location=no,scrollbars=yes,menubar=no,directories=no';
    var windowRef = window.open('', windowName, windowOptions);

	windowRef.document.write('<html><head><title>Ikariam War Room Export (by bt_viper)</title>');
	windowRef.document.write(xplorerCSS());
	windowRef.document.write('</head><body></body></html>');
	windowRef.document.close();
	
	// Theta Atiens text area
	var thetaCaption = windowRef.document.createElement('h3');
	thetaCaption.textContent = "Theta Atien's Alliance Map - Embassy Input format";
	var thetaTextArea = windowRef.document.createElement('textarea');
	thetaTextArea.setAttribute('rows','10');
	thetaTextArea.setAttribute('cols','100');
	var thetaText = '';
	for (var i = 0; i < cities.length; i++) {
		thetaText += cities[i].toThetaAtiensString();
		if(i < cities.length-1) thetaText += '\n';
	}
	thetaTextArea.textContent = thetaText;
	
	// Players in CSV format
	var playersCaption = windowRef.document.createElement('h3');
	playersCaption.textContent = "Player data in CSV format (ready for Excel import)";
	var playersTextArea = windowRef.document.createElement('textarea');
	playersTextArea.setAttribute('rows','10');
	playersTextArea.setAttribute('cols','100');
	var playersText = '"Player name";"Alliance";"Status";"Score";"Generals";"Gold";\n';
	for (var i = 0; i < players.length; i++) {
		playersText += players[i].toCSVString();
		if(i < players.length-1) playersText += '\n';
	}
	playersTextArea.textContent = playersText;
	
	// Cities in CSV format
	var citiesCaption = windowRef.document.createElement('h3');
	citiesCaption.textContent = "City data in CSV format (ready for Excel import)";
	var citiesTextArea = windowRef.document.createElement('textarea');
	citiesTextArea.setAttribute('rows','10');
	citiesTextArea.setAttribute('cols','100');
	var citiesText = '"City";"TownHallSize";"Island";"X-coordinate";"Y-coordinate";"Luxury";"Player";"Alliance";"Status";"Score";"Generals";"Gold";\n';
	for (var i = 0; i < cities.length; i++) {
		citiesText += cities[i].toCSVString();
		if(i < cities.length-1) {
			citiesText += '\n';
		}
	}
	citiesTextArea.textContent = citiesText;

	var body = windowRef.document.body;
	body.appendChild(thetaCaption);
	body.appendChild(thetaTextArea);
	body.appendChild(playersCaption);
	body.appendChild(playersTextArea);
	body.appendChild(citiesCaption);
	body.appendChild(citiesTextArea);
	
	windowRef.document.close();
}


// this is run is page script context
function fireNewMapWindow () {
    var windowName  = 'xplorer';
    var newWindow = window.open ('about:blank', '_blank', "height=800,width=1250");
    newWindow.addEventListener (
        "load",
        function () {
 			onDisplayMapWindow (newWindow)
        },
        false
    );
}

function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
    var D                                   = document;
    var scriptNode                          = D.createElement ('script');
    if (runOnLoad) {
        scriptNode.addEventListener ("load", runOnLoad, false);
    }
    scriptNode.type                         = "text/javascript";
    if (text)       scriptNode.textContent  = text;
    if (s_URL)      scriptNode.src          = s_URL;
    if (funcToRun)  scriptNode.textContent  = '(' + funcToRun.toString() + ')()';

    var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
    targ.appendChild (scriptNode);
}

function displayMapWindow() {
	// first export onDisplayMapWindow function to the page script domain
	exportFunction(onDisplayMapWindow, unsafeWindow, {defineAs: "onDisplayMapWindow"});
	// then trigger the new window creation
	addJS_Node (null, null, fireNewMapWindow);
}

function onDisplayMapWindow (newWindow) {
	syslog('displayMapWindow started', 3);
	consoleTime('xplorer.displayMapWindow');
	windowRef = newWindow;
	doc = windowRef.document;
	doc.write('<html><head><title>Ikariam Xplorer</title>');
	doc.write(xplorerCSS());
	doc.write('</head><body></body></html>');
	doc.close();
		
	// create the containers
	searchContainer = createSearchContainer(windowRef.document.body);
	previewContainer = createPreviewContainer(windowRef.document.body);
	mapContainer = createMapContainer(windowRef.document.body);
	legendContainer = createLegendContainer(windowRef.document.body);
	statusContainer = createStatusContainer(windowRef.document.body);
		
	// add a listener for browser window resize
	windowRef.addEventListener('resize', onWindowResize, false);
	
	// add a listener for key presses
	windowRef.addEventListener('keypress', onWindowKeypress, false);
		
	// load the data
	xplorerDB.load();
	alliances = xplorerDB.alliances;
	players = xplorerDB.players;
	islands = xplorerDB.islands;
	cities = xplorerDB.cities;
	
	// get own cities and update city relationships
	var ownCities = ikariamGetOwnCities();
	for (var i = 0; i < ownCities.length; i++) {
		var ownCity = ownCities[i];
		var city = getArrayItemByID(cities.list, ownCity.id);
		if(city) {
			city.relationship = ownCity.relationship;
			if(ownCity.selected) {
					city.selected = true;
					selectedOwnCity = city;
			}
		}
	}

	// set the correct sizes and positions to the elements
	refreshSizesAndPositions();
	
	// update legend
	updateLegend(alliances);
	
	// update status
	updateStatus(alliances, players, islands, cities, '');
	
	// perform version update check
	// checkVersion();
	
	consoleTimeEnd('xplorer.displayMapWindow');
	
	syslog('displayMapWindow finished', 3);
}


function xplorerCSS() {
	var result = '														\
        <style type="text/css">											\
					table { border-collapse: collapse; }				\
																		\
					.xplorer_searchDiv {								\
						float: left;									\
						margin: 1px;									\
						//clear: right;									\
					}													\
																		\
					.xplorer_heading {									\
						color: #d9eef7;									\
						background: -moz-linear-gradient(top,  #00adee,  #0078a5);	\
						outline: none;									\
						text-align: center;								\
						text-decoration: none;							\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						font: 16px/100% Arial, Helvetica, sans-serif;	\
						font-weight:bold;								\
						height: 20px;									\
						padding: .3em .5em .3em;						\
						margin: 0px;									\
						-moz-border-radius: 7px 7px 0 0;				\
					}													\
																		\
					.xplorer_caption, .xplorer_label {					\
						color: black;									\
						background-color: white;						\
						text-align: left;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						font: 14px/100% Arial, Helvetica, sans-serif;	\
						padding: 3px;									\
						margin: 0px;									\
					}													\
																		\
					.xplorer_button {									\
						color: #e9e9e9;									\
						border: solid 1px #555;							\
						background: -moz-linear-gradient(top,  #888,  #575757);	\
						display: inline-block;							\
						outline: none;									\
						cursor: pointer;								\
						text-align: center;								\
						text-decoration: none;							\
						font: 14px/100% Arial, Helvetica, sans-serif;	\
						font-weight:bold;								\
						padding: .3em 1em .3em;							\
						margin: 5px;									\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						-moz-border-radius: .5em;						\
						-moz-box-shadow: 0 2px 4px rgba(0,0,0,.4);		\
					}													\
					.xplorer_button:hover {								\
						text-decoration: none;							\
						background: -moz-linear-gradient(top,  #757575,  #4b4b4b);\
					}													\
					.xplorer_button:active {							\
						color: #afafaf;									\
						background: -moz-linear-gradient(top,  #575757,  #888);	\
						position: relative;								\
						top: 1px;										\
					}													\
																		\
					.xplorer_Update_button {							\
						color: #e8f0de;									\
						border: solid 1px #538312;						\
						background: -moz-linear-gradient(top,  #7db72f,  #4e7d0e);	\
						display: inline-block;							\
						outline: none;									\
						cursor: pointer;								\
						vertical-align: middle;							\
						text-align: center;								\
						text-decoration: none;							\
						font: 12px/100% Arial, Helvetica, sans-serif;	\
						font-weight:bold;								\
						//padding: .3em 1em .3em;						\
						margin: 0px 0px 0px 20px;						\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						-moz-border-radius: .5em;						\
							-moz-box-shadow: 0 2px 4px rgba(0,0,0,.4);	\
					}													\
					.xplorer_Update_button:hover {						\
						text-decoration: none;							\
						background: -moz-linear-gradient(top,  #6b9d28,  #436b0c);	\
					}													\
					.xplorer_Update_button:active {						\
						color: #a9c08c;									\
						background: -moz-linear-gradient(top,  #4e7d0e,  #7db72f);	\
						position: relative;								\
						top: 1px;										\
					}													\
																		\
					.xplorer_input {									\
						background-color: white;						\
						color: black;									\
						padding: 2px;									\
						margin: 3px;									\
						font: 12px/100% Arial, Helvetica, sans-serif;	\
						//font: 12px/100% Cambria, Arial;				\
                        font-weight:normal;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						-moz-border-radius: 2px;						\
						-moz-box-shadow: 0 1px 2px rgba(0,0,0,.4);		\
					}													\
																		\
					.xplorer_select, .xplorer_checkbox {				\
						background-color: white;						\
						color: black;									\
						padding: 3px;									\
						margin: 5px;									\
						font: 12px/100% Arial, Helvetica, sans-serif;	\
                        font-weight:normal;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						-moz-border-radius: 4px;						\
						-moz-box-shadow: 0 2px 4px rgba(0,0,0,.4);		\
					}													\
																		\
					.xplorer_th {										\
						color: #e9e9e9;									\
						//background-color: lightgrey;					\
						background: -moz-linear-gradient(top,  #888,  #575757);	\
						//color: black;									\
						height: 15px;									\
						padding: 2px;									\
						margin: 1px;									\
						font: 12px/100% Arial, Helvetica, sans-serif;	\
						font-weight:normal;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
					}													\
					.xplorer_td {										\
						background-color: white;						\
						color: black;									\
						padding: 3px;									\
						margin: 1px;									\
						font: 12px/100% Arial, Helvetica, sans-serif;	\
						font-weight:normal;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
					}													\
																		\
					.mdbPlayer {										\
                        position: absolute;								\
                        width: 5px;										\
                        height: 5px;									\
                        border: 1px solid black;						\
                    }													\
					.mdbIsland, .ownmdbIsland {							\
                        position: absolute;								\
                        border: 1px solid LightGray;						\
                    }													\
					.searchContainer, .previewContainer {				\
                        position: absolute;								\
						padding: 0px;									\
						margin: 0px;									\
						border: 2px solid grey;							\
						-moz-border-radius: 6px;						\
						-moz-box-shadow: 0 2px 4px rgba(0,0,0,.4);		\
					}													\
					.statusContainer, .legendContainer {				\
						color: #e9e9e9;									\
						border: solid 1px #555;							\
						background: -moz-linear-gradient(top,  #888,  #575757);	\
						//background-color: lightgrey;					\
						font: 10px/100% Arial, Helvetica, sans-serif;	\
                        font-weight:normal;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
					    position: absolute;								\
						height: 15px;									\
						padding: 0px;									\
						margin: 0px;									\
						border: 1px solid grey;							\
						-moz-border-radius: 4px;						\
						-moz-box-shadow: 0 2px 4px rgba(0,0,0,.4);		\
					}													\
																		\
					.mapContainer {										\
                        position: absolute;								\
						padding: 0px;									\
						margin: 0px;									\
						border: 3px solid grey;							\
						-moz-border-radius: 10px;						\
						-moz-box-shadow: 0 2px 4px rgba(0,0,0,.4);		\
					}													\
																		\
					.gridSurroundingBox {								\
                        position: absolute;								\
						padding: 0px;									\
						margin: 0px;									\
						border: 2px solid grey;							\
					}													\
																		\
					.gridElem {											\
                        position: absolute;								\
						padding:0px;									\
						margin:0px;										\
						border: 1px dashed gray;						\
                    }													\
																		\
					.hTick {											\
                        position: absolute;								\
						padding:0px;									\
						margin:0px;										\
						font-size: 15px;								\
                        font-weight:bold;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						text-align: center;								\
						border: none;									\
                    }													\
					.vTick {											\
                        position: absolute;								\
						padding:0px;									\
						margin:0px;										\
						font-size: 15px;								\
                        font-weight:bold;								\
						text-shadow: 0 1px 1px rgba(0,0,0,.3);			\
						text-align: left;								\
						border: none;									\
                    }													\
	       </style>														\
    ';
	return result;
}


function processIslandView() {
	syslog('processIslandView started', 1);
	// load the existing content to the database
	xplorerDB.load();
		
	// get a reference to an existing island or create a new one
	var islandId = ikariamIslandViewGetIslandId();
	var island = xplorerDB.islands.find(islandId);
	if (!island) island = xplorerDB.addIsland(islandId);
	ikariamIslandViewGetIslandInfo(island);

	// get a reference to avatar(=player) scores
	var avatarScores = unsafeWindow.ikariam.backgroundView.screen.data.avatarScores;
	
	// get a list of city infos
	var cityInfs = ikariamIslandViewGetCityInfos();
	island.cityCount = 0;
	for (i = 0; i < cityInfs.length; i++ ) {
		var cityInfo = cityInfs[i];
		// city id (skip empty building places)
		var cityId  = ikariamIslandViewGetCityId(cityInfo);
		if (cityId == -1) continue;
		island.cityCount = island.cityCount + 1;
				
		// get a reference to an existing alliance or create a new one
		var allianceId = ikariamIslandViewGetAllianceId(cityInfo);
		var alliance = xplorerDB.alliances.find(allianceId);
		if (!alliance) alliance = xplorerDB.addAlliance(allianceId);
		ikariamIslandViewGetAllianceInfo(cityInfo, alliance);

		// get a reference to an existing player or create a new one
		var playerId = ikariamIslandViewGetPlayerId(cityInfo);
		var player = xplorerDB.players.find(playerId);
		if (!player) player = xplorerDB.addPlayer(playerId, alliance);
		ikariamIslandViewGetPlayerInfo(cityInfo, avatarScores, player);
		// Added 7.7.2015
		if(player.allianceId != alliance.id) xplorerDB.movePlayer(player, alliance);
		
		// get a reference to an existing city or create a new one
		var city = xplorerDB.cities.find(cityId);
		if (!city) city = xplorerDB.addCity(cityId, player, island);
		ikariamIslandViewGetCityInfo(cityInfo, city);
		// Added 5.7.2015
		// check if city has been moved from an island to another
		if(city.islandId != island.id) xplorerDB.moveCity(city, island);
	}
	
	// store permanently to GM cache
	xplorerDB.save();
	
	syslog('processIslandView finished', 1);
}


function processAllyList() {
	syslog('Process Ally list started', 1);
	// load the existing content to the database
	this.db.load();

	// get a reference to an existing alliance or create a new one
	var allianceId = ikariamAllyListViewGetAllianceId();
	var alliance = this.db.alliances.find(allianceId);
	if (!alliance) alliance = this.db.addAlliance(allianceId);
	ikariamAllyListViewGetAllianceInfo(alliance);
	
	// get the member entries (=players)
	var playerInfos = ikariamAllyListViewGetPlayerInfos();
	for (i = 0; i < playerInfos.length; i++ ) {
		var playerInfo = playerInfos[i];

		// get a reference to an existing player or create a new one
		var playerId = ikariamAllyListViewGetPlayerId(playerInfo);
		var player = this.db.players.find(playerId);
		if (!player) player = this.db.addPlayer(playerId, alliance);
		ikariamAllyListViewGetPlayerInfo(playerInfo, player);

		// player's city information
		var cityInfos = ikariamAllyListViewGetCityInfos(playerInfo);
		for(j=0; j < cityInfos.length; j++ ) {
			var cityInfo = cityInfos[j];

			// get a reference to an existing island or create a new one
			var islandId = ikariamAllyListViewGetIslandId(cityInfo);
			var island = this.db.islands.find(islandId);
			if (!island) island = this.db.addIsland(islandId);
			ikariamAllyListViewGetIslandInfo(cityInfo, island);

			// get a reference to an existing city or create a new one
			var cityId = ikariamAllyListViewGetCityId(cityInfo);
			var city = this.db.cities.find(cityId);
			if (!city) city = this.db.addCity(cityId, player, island);
			ikariamAllyListViewGetCityInfo(cityInfo, city);
		}
	}
	// store permanently to GM cache
	this.db.save();
	syslog('Process Ally list finished', 1);
}

function processMilitaryMovementsView() {
	// get the military movements
	var movementInfos = ikariamMilitaryMovementsViewGetMovementInfos();
	for (i = 1; i < movementInfos.length; i++ ) { 	// first item heading row
		var movementInfo = movementInfos[i];

		// ####
		consoleDir(movementInfo);
		
		// get a reference to an existing movement or create a new one
		var movementId = ikariamMilitaryMovementsViewGetMovementId(movementInfo);
		
		// ####
		syslog(movementId,1);
	}
}

function worldMapViewOnLoad(JSONResponse) {
	worldMapViewOriginalHandleMapData(JSONResponse);
	// GM prevents from directly calling GM_* function from unsafeWindow callbacks
	setTimeout(function() {processWorldMapView();}, 0);
}

function initWorldMapView() {
	syslog('InitWorldMapView started', 1);
	// hook into world map view's map data handler
	worldMapViewOriginalHandleMapData = unsafeWindow.worldmap.handleMapData;
	unsafeWindow.worldmap.handleMapData = worldMapViewOnLoad;
	syslog('InitWorldMapView finished', 1);
}

function processWorldMapView() {
	syslog('processWorldMapView started', 1);	
	xplorerDB.load();
	var islands = unsafeWindow.ikariam.backgroundView.screen.islands;
	var islandCount = 0;
	// #### DEBUG
	syslog('islands.length=' + islands.length);
	var minX=100;
	var minY=100;
	var maxX=0;
	var maxY=0;
	for (i = 1; i < islands.length; i++ ) { 
		if(!islands[i]) continue;
		for (j = 1; j < islands[i].length; j++ ) {
			if((!islands[i][j]) || (!islands[i][j][0]) || (islands[i][j] == 'ocean')) continue;
			var islandId = parseInt(islands[i][j][0]);
			var island = xplorerDB.islands.find(islandId);
			if (!island) island = xplorerDB.addIsland(islandId);
			ikariamWorldMapViewGetIslandInfo(islands[i][j], island);
			island.coords.x = i;
			island.coords.y = j;
			islandCount++;
			// #### DEBUG
			if(i<minX) minX=i;
			if(i>maxX) maxX=i;
			if(j<minY) minY=j;
			if(j>maxY) maxY=j;
		}
	}
	xplorerDB.save();
	
	syslog('coords=[' + minX + ':' + minY + '] -> [' + + maxX + ':' + maxY + ']');
	syslog('processWorldMapView finished (' + islandCount + ')', 1);
	
	/*
	
wonderText = new Array();
wonderText[1] = 'Hephaistoksen paja';
wonderText[2] = 'Hadesin pyhä lehto';
wonderText[3] = 'Demeterin puutarhat';
wonderText[4] = 'Athenen temppeli';
wonderText[5] = 'Hermeksen temppeli';
wonderText[6] = 'Aresin linnake';
wonderText[7] = 'Poseidonin temppeli';
wonderText[8] = 'Kolossus';
*/
	
}

/**************************************************** 
				Script initialisation
****************************************************/
// only init if GF_toolbar is present
if (document.getElementById('GF_toolbar')) {
	var xp = new xplorer();
	xp.init();
	xp.processViews();
}