// Copyright (c) 2005 Just Objects B.V. <just@justobjects.nl>
// Distributable under LGPL license. See terms of license at gnu.org.

/*
 * Widgets for Tracks and Tracers
 *
 * $Id: gtwidget.js,v 1.15 2006-05-01 09:45:33 just Exp $
 */



// Tracer static functions
var GTW = {
// Array of N available colors, each in triples: N+0 (avail) N+1 (name) N+2 (colorcode)
	colors: [0, 'blue', '#FF9933', '#000000', 0, 'yellow', '#FFFF00', '#222222', 0, 'green','#00EE00', '#222222', 0,'red','#CC0000', '#ffffff', 0, 'blue','#0066FF', '#ffffff', 0,'purple','#FF66FF', '#ffffff'],
	randomColors: ['#FFCCFF',  '#88AACC',
			'#DDDDDD',  '#FFFFCC',  '#FFCCCC',  '#FF99CC', '#FF66CC',
			'#FF33CC',  '#FF00CC',  '#FFFF99',  '#FFCC99', '#FF9999',
			'#FF6699',  '#FF3399',  '#99FFFF',  '#99CCFF',
			'#99FF00',  '#99CC00',  '#6FFFF',  '#66CCFF', '#00FFFF',
			'#00CCFF', '#00FF66',  '#00CC66', '#00EE00', '#CC3333', '#00CCFF', '#666666' ],

	tracers: [],
	mediaViewer: null,
	poiViewer: null,
	trackPlayer: null,
	trackAutoPlayer: null,
	map: null,
	polyLineWidth: 3,
	polyLineOpacity: 0.80,
// Minimal distance between trackpoints (when reading tracks from server)
	minTrackPtDist: 15,

	startAutoPlay: function() {
		GTW.stopAutoPlay();
		GTW.trackAutoPlayer = new TrackAutoPlayer();
		GTW.trackAutoPlayer.start();
	},

	stopAutoPlay: function() {
		if (GTW.trackAutoPlayer != null) {
			GTW.trackAutoPlayer.stop();
			GTW.trackAutoPlayer = null;
		}
	},

	init: function(map) {
		// Init server.js
		SRV.init();
		GTW.map = map;
	},

	clearFeatures: function() {
		if (GTW.mediaViewer != null) {
			GTW.mediaViewer.clear();
			GTW.mediaViewer.hide();
		}
		if (GTW.poiViewer != null) {
			GTW.poiViewer.clear();
			GTW.poiViewer.hide();
		}
	},

	clearMap: function() {
		GTW.clearTracers();
		GTW.clearFeatures();
		GTW.clearTrackPlayer();
		GTW.stopAutoPlay();
	},

	clearTrackPlayer: function() {
		if (GTW.trackPlayer != null) {
			GTW.trackPlayer.hide();
		}
	},

// Delete all tracers from view
	clearTracers: function() {
		for (tracer in GTW.tracers) {
			var t = GTW.getTracer(tracer);
			t.clear();
			t.hide();
		}
	},

/** Lazy creation of TrackPlayer */
	getTrackPlayer: function() {
		if (GTW.trackPlayer == null) {
			GTW.trackPlayer = new TrackPlayer('#6666FF', '#FFFFFF');
		}

		return GTW.trackPlayer;
	},

// Create new Tracer object and put in tracers array
	createTracer: function(name, lon, lat) {
		// See if tracer already exists
		var tracer = GTW.getTracer(name);
		if (tracer) {
			if (lon && lat) {
				tracer.setLocation(new GPoint(lon, lat));
			}
			tracer.show();
			return tracer;
		}

		// Find color
		// Always try username to find color first
		var colorIndex = 0;
		var lastFreeIndex = -1;
		while (true) {
			if (GTW.colors[colorIndex] == 0) {
				// remember last free index
				// lastFreeIndex = colorIndex;

				// Check if name matches
				if (GTW.colors[colorIndex + 1] == name) {
					// OK found, reserve color slot
					GTW.colors[colorIndex] = 1;
					break;
				}
			}

			// next quadruple
			colorIndex += 4;
			if (colorIndex >= GTW.colors.length) {
				// not found name: use random bgcolor
				colorIndex = 0;
				// take random
				GTW.colors[2] = GTW.randomColors[Math.floor(Math.random() * (GTW.randomColors.length - 1))];
				break;
			}
		}

		var point;
		if (lon && lat) {
			point = new GPoint(lon, lat);
		}

		var iconURL = 'img/' + GTW.colors[colorIndex + 1] + 'ball.gif';
		tracer = new Tracer(name, GTW.colors[colorIndex + 2], GTW.colors[colorIndex + 3], iconURL, point);
		GTW.tracers[name] = tracer;
		return tracer;
	},

// Display media
	displayMedia: function(records) {
		if (GTW.mediaViewer == null) {
			GTW.mediaViewer = new MediaViewer('#FF9900', '#000000');
		}

		GTW.mediaViewer.show();
		GTW.mediaViewer.addMedia(records);
	},

// Display POIs
	displayPOIs: function(records) {
		if (GTW.poiViewer == null) {
			GTW.poiViewer = new POIViewer('#FFFF33', '#000000');
		}

		GTW.poiViewer.show();
		GTW.poiViewer.addPOIs(records);
	},

// Display media
	displayTrackPlayer: function() {

		GTW.getTrackPlayer().show();
	},

// Get tracer by name
	getTracer: function(name) {
		return GTW.tracers[name];
	},

	formatDate: function(time) {
		var date = new Date(time);
		return date.format("DD-MM-YY");
	},

	formatTime: function(time) {
		var date = new Date(time);
		return date.format("HH:mm:ss");
	},

	formatDateAndTime: function(time) {
		var date = new Date(time);
		return date.format("DD-MM-YY HH:mm:ss");
	},

// Get tracers array
	getTracers: function() {
		return GTW.tracers;
	}
}


// Manage multiple media for viewing.
function MediaViewer(bgcolor, fgcolor) {
	this.panel = new Panel('- MediaViewer -', bgcolor, fgcolor);
	this.media = [];

	var mediaViewer = this;
	this.panel.onClose = function () {
		mediaViewer.clear();
	}

}

// Add a media from (server) Records
MediaViewer.prototype.addMedia = function (records) {
	var medium;
	for (var i = 0; i < records.length; i++) {
		// Create and draw location-based medium
		medium = new Medium(records[i].getField('id'),
				records[i].getField('name'),
				records[i].getField('kind'),
				records[i].getField('mime'),
				records[i].getField('creationdate'),
				this.panel,
				records[i].getField('lon'),
				records[i].getField('lat'));
		medium.show();
		this.media.push(medium);
	}
	this.panel.setContent('<p class="pn-text-content">move your mouse over the icons to view media within this window<br/><br/>clicking an image will enlarge it to full scale</p>');
}

MediaViewer.prototype.clear = function () {
	for (var i = 0; i < this.media.length; i++) {
		if (this.media[i]) {
			this.media[i].remove();
			delete this.media[i];
		}
	}
	this.media = [];
}

MediaViewer.prototype.close = function () {
	this.clear();
	this.panel.close();
}

// Setting the visibility to visible
MediaViewer.prototype.hide = function() {
	this.panel.hide();
}

// Setting the visibility to visible
MediaViewer.prototype.show = function() {
	this.panel.show();
}


// Location-based medium
function Medium(id, name, kind, mime, time, panel, lon, lat) {
	this.id = id;
	this.name = name;
	this.kind = kind;
	this.mime = mime;
	this.time = new Number(time);
	// somehow needed
	this.lon = lon;
	this.lat = lat;
	this.panel = panel;
	if (!panel.full) {
		panel.full = false;
	}

	this.url = 'media.srv?id=' + this.id;
	this.tlabel = null;
	this.divId = 'medium' + id;
	this.iconId = null;
}

Medium.prototype.blink = function(cnt) {
	if (cnt <= 0) {
		DH.show(this.iconId);
		return;
	}

	DH.toggleVisibility(this.iconId);
	var medium = this;
	var n = cnt;
	setTimeout(function() {
		medium.blink(n - 1)
	}, 150);
}

// Shows icon on map
Medium.prototype.show = function() {
	var mediumId = this.id;
	if (!this.lon && !this.lat) {
		return;
	}

	var tl = new TLabel(true);
	tl.id = 'med' + mediumId;
	var pt = new GPoint(this.lon, this.lat);
	tl.anchorLatLng = pt;
	tl.anchorPoint = 'center';
	this.iconId = 'medicon' + mediumId;

	tl.content = '<div class="medicon" id="' + this.iconId + '" style="background-color:' + this.panel.bgcolor + ';" >&nbsp;&nbsp;&nbsp;&nbsp;</div>';

	// Add Tlabel to map
	GTW.map.addTLabel(tl);

	this.tlabel = tl;

	// Add extra attributes so callback can figure out the medium and tracer
	var obj = DH.getObject(this.iconId);

	var medium = this;

	// Define callback for mouse over medium icon
	this.onMouseOverIcon = function (e) {
		medium.display();
		DH.cancelEvent(e);
	}

	DH.addEvent(obj, 'mouseover', this.onMouseOverIcon, false);
}

Medium.prototype.getDate = function() {
	var date = new Date(this.time);
	return date.format("DDD D MMMM YYYY HH:mm:ss");
}

Medium.prototype.getTitle = function() {
	var date = new Date(this.time);
	return this.name + ' [' + this.kind + '/' + this.id + '] - ' + this.getDate();
}

// Displays medium in Panel
Medium.prototype.display = function() {
	var src = this.url;
	var div = DH.getObject(this.divId);
	if (div == null) {
		//div = document.createElement('div');
		//div.className = 'medidisp';
		//div.id = this.divId;
		this.panel.setContent('<div class="medidisp" id="' + this.divId + '"></div>');
	}

	if (this.kind == 'video') {
		this._displayVideo();
	} else if (this.kind == 'image') {
		this._displayImage();
	} else if (this.kind == 'audio') {
		this._displayAudio();
	}
}

Medium.prototype._displayAudio = function() {
	var div = DH.getObject(this.divId);

	//this.tracer.panel.setContent('<img id="' + this.divId + '" title="' + this.getTitle() + '" src="img/videoicon.gif" border="0"  />');
	div.innerHTML = '<img title="' + this.getTitle() + '" src="img/audioicon.jpg" border="0"  />';

	var medium = this;
	this.onClick = function(e) {
		DH.cancelEvent(e);

		// medium.tracer.full = medium.tracer.full == true ? false : true;
		// if (medium.tracer.full == false) {
		//   content = '<img title="' + medium.getTitle() + '" src="img/videoicon.gif" border="0"  />';
		// } else {
		var url = medium.url;
		content = '<embed src="' + medium.url + '"/>';
		// (navigator.userAgent.toLowerCase().indexOf('mac') != -1) ?
		//'<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"  codebase="http://www.apple.com/qtactivex/qtplugin.cab" align="left"><param name="src" value="' + medium.url + '"><param name="AUTOPLAY" value="true"><param name="type" value="video/quicktime"><param name="CONTROLLER" value="true"><embed src="' + medium.url + '" width="176" height="154" autoplay="true" controller="true" loop="false" type="video/quicktime" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>' :
		//'<embed src="' + medium.url + '"/>';
		//   '<object classid="clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95"> <param name="FileName" value="'+medium.url+'"><param name="ShowControls" value="true"><param name="bgcolor" value="#ffffff"><param name="loop" value="true"></object>';
		// }

		//var d = DH.getObject(medium.divId);
		// d.innerHTML = content;
		medium.panel.setContent(content);
	}

	DH.addEvent(div, 'click', this.onClick, false);
}

Medium.prototype._displayImage = function() {
	var src = this.url;
	if (this.panel.full == false) {
		src += '&resize=158x118';
	}

	var div = DH.getObject(this.divId);

	div.innerHTML = '<img title="' + this.getTitle() + '" src="' + src + '" border="0"  />';

	var medium = this;
	this.onClick = function(e) {
		DH.cancelEvent(e);

		var url = medium.url;

		medium.panel.full = medium.panel.full == true ? false : true;
		if (medium.panel.full == false) {
			url += '&resize=158x118';
		}
		var d = DH.getObject(medium.divId);

		d.innerHTML = '<img title="' + medium.getTitle() + '" src="' + url + '" border="0"  />';
		DH.cancelEvent(e);
	}

	DH.addEvent(div, 'click', this.onClick, false);

}

Medium.prototype._displayVideo = function() {
	var div = DH.getObject(this.divId);

	//this.tracer.panel.setContent('<img id="' + this.divId + '" title="' + this.getTitle() + '" src="img/videoicon.gif" border="0"  />');
	div.innerHTML = '<img title="' + this.getTitle() + '" src="img/videoicon.gif" border="0"  />';

	var medium = this;
	this.onClick = function(e) {
		DH.cancelEvent(e);

		// medium.tracer.full = medium.tracer.full == true ? false : true;
		// if (medium.tracer.full == false) {
		//   content = '<img title="' + medium.getTitle() + '" src="img/videoicon.gif" border="0"  />';
		// } else {
		var url = medium.url;
		content = (navigator.userAgent.toLowerCase().indexOf('mac') != -1) ?
				  '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="176" height="154" codebase="http://www.apple.com/qtactivex/qtplugin.cab" align="left"><param name="src" value="' + medium.url + '"><param name="AUTOPLAY" value="true"><param name="type" value="video/quicktime"><param name="CONTROLLER" value="true"><embed src="' + medium.url + '" width="176" height="154" autoplay="true" controller="true" loop="true" type="video/quicktime" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>' :
				  '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" align="left"><param name="movie" value="' + medium.url + '&format=swf"><param name="quality" value="high"><param name="bgcolor" value="#ffffff"><param name="loop" value="true"><embed src="' + medium.url + '&format=swf" quality="high" bgcolor="#ffffff" swliveconnect="true" loop="true" type="application/x-shockwave-flash"pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?p1_prod_version=shockwaveflash"></embed></object>';
		// }

		//var d = DH.getObject(medium.divId);

		// d.innerHTML = content;
		medium.panel.setContent(content);
	}

	DH.addEvent(div, 'click', this.onClick, false);
}

Medium.prototype.remove = function() {
	if (this.tlabel == null) {
		return;
	}
	// remove Tlabel from map
	GTW.map.removeTLabel(this.tlabel);

	this.tlabel = null;
}


// Location-based Point Of Interest (e.g. bomb)
function POI(id, name, type, time, panel, lon, lat) {
	this.id = id;
	this.name = name;
	this.type = type;
	this.time = new Number(time);
	// somehow needed
	this.lon = lon;
	this.lat = lat;
	this.panel = panel;
	this.tlabel = null;
	this.iconId = 'poiicon' + id;
}

POI.prototype.blink = function(cnt) {
	if (cnt <= 0) {
		DH.show(this.iconId);
		return;
	}

	DH.toggleVisibility(this.iconId);
	var poi = this;
	var n = cnt;
	setTimeout(function() {
		poi.blink(n - 1)
	}, 150);
}

// Show icon on map
POI.prototype.show = function() {
	var id = this.id;

	var tl = new TLabel();
	tl.id = 'poi' + id;
	var pt = new GPoint(this.lon, this.lat);
	tl.anchorLatLng = pt;
	tl.anchorPoint = 'center';

	var src = 'img/poi.gif';
	var img = '<img title="' + this.getTitle() + '" src="' + src + '" border="0"  />';

	tl.content = '<div class="poiicon" id="' + this.iconId + '" style="border: 1px solid ' + this.panel.bgcolor + ';" >' + img + '</div>';

	// Add Tlabel to map
	GTW.map.addTLabel(tl);

	this.tlabel = tl;
	var poi = this;

	// Define callback for mouse over medium icon
	this.onMouseOverIcon = function (e) {
		poi.display();
		DH.cancelEvent(e);
	}

	DH.addEvent(DH.getObject(this.iconId), 'mouseover', this.onMouseOverIcon, false);
}

POI.prototype.getTitle = function() {
	var date = new Date(this.time);
	return '<b>' + this.name + ' </b><br/>type: ' + this.type + '<br/><br/>at ' + this.lon + ',' + this.lat + '<br/>' + date.format("DDD D MMMM YYYY HH:mm:ss");
}

// Display in Panel
POI.prototype.display = function() {
	this.panel.setContent('<p class="pn-text-content" >' + this.getTitle() + '</p>');
}

POI.prototype.remove = function() {
	if (this.tlabel == null) {
		return;
	}
	// remove Tlabel from map
	GTW.map.removeTLabel(this.tlabel);

	this.tlabel = null;
}

// Class representing GeoTracing user on Map.
function POIViewer(bgcolor, fgcolor) {
	this.panel = new Panel('- POIViewer -', bgcolor, fgcolor);
	this.pois = [];

	var poiViewer = this;
	this.panel.onClose = function () {
		poiViewer.clear();
	}

}

// Add from server Records
POIViewer.prototype.addPOIs = function (records) {
	var poi;
	for (var i = 0; i < records.length; i++) {
		// Create and draw location-based poi
		poi = new POI(records[i].getField('id'),
				records[i].getField('name'),
				records[i].getField('type'),
				records[i].getField('time'),
				this.panel,
				records[i].getField('lon'),
				records[i].getField('lat'));
		poi.show();
		this.pois.push(poi);
	}
	this.panel.setContent('<p class="pn-text-content" >move your mouse over the icons to view each POI in this window</p>');
}

POIViewer.prototype.clear = function () {
	for (var i = 0; i < this.pois.length; i++) {
		if (this.pois[i]) {
			this.pois[i].remove();
			delete this.pois[i];
		}
	}
	this.pois = [];
}

POIViewer.prototype.close = function () {
	this.clear();
	this.panel.close();
}

// Setting the visibility to visible
POIViewer.prototype.hide = function() {
	this.panel.hide();
}

// Setting the visibility to visible
POIViewer.prototype.show = function() {
	this.panel.show();
}

// Class representing GeoTracing user on Map.
function Tracer(name, bgcolor, fgcolor, iconURL, point) {
	this.name = name;
	this.fgcolor = fgcolor;
	this.color = bgcolor;
	this.panel = new Panel(name, bgcolor, fgcolor);
	this.activeTrack = null;
	this.point = point;
	this.iconURL = iconURL;
	this.tlabel = null;
	this.hidden = false;
	this.live = false;
	this.full = false;

	if (this.point) {
		this.setLocation(point);
	}

	// Need this for JS local scope in callback
	var tracer = this;
	this.panel.onActivate = function () {
		tracer.activate();
	}

	this.panel.onClose = function () {
		tracer.clear();
	}
}

// Tracer window activated
Tracer.prototype.activate = function () {
	if (this.activeTrack != null && GTW.trackPlayer != null) {
		GTW.trackPlayer.setTrack(this.activeTrack);
	}

	if (this.point != null) {
		GTW.map.centerAndZoom(this.point, GTW.map.getZoomLevel());
	}
}

// Add a medium
Tracer.prototype.addMedium = function (medium) {
	if (this.activeTrack != null) {
		this.activeTrack.addMedium(medium);
	}
}

// Add a POI
Tracer.prototype.addPOI = function (poi) {
	if (this.activeTrack != null) {
		this.activeTrack.addPOI(poi);
	}
}

// Delete a Track for id and name
Tracer.prototype.clear = function () {
	this.hide();
	this.deleteTrack();
	this.panel.clear();
}

// Delete a Track for id and name
Tracer.prototype.deleteTrack = function () {
	if (this.activeTrack != null) {
		this.activeTrack.remove();
	}
	this.activeTrack = null;
}

// Do we have a track ?
Tracer.prototype.hasTrack = function () {
	return this.activeTrack != null;
}

// get the active track
Tracer.prototype.getActiveTrack = function () {
	return this.activeTrack;
}

// Delete a Track for id and name
Tracer.prototype.newTrack = function (id, name) {
	this.deleteTrack(id, name);
	this.activeTrack = new Track(id, name, this.panel, this);
}


// Read a Track by getting GPX file by (record) id
Tracer.prototype.readTrack = function (id, name, doDraw) {
	// Need this for JS local scope in callback
	var tracer = this;

	// Define callback for async response
	this.onGetTrackRsp = function (gtx) {

		tracer.deleteTrack();

		var track = new Track(id, name, tracer.panel, tracer);

		track.setGTX(gtx);

		if (doDraw == true) {
			track.draw();
		}

		tracer.panel.setTitle(tracer.name + '/' + track.name);

		// Show Tracer at last point of Track
		tracer.setLocation(track.getLastPoint());

		tracer.activeTrack = track;

		tracer.activate();

	}

	// Get GTX document by id from server
	tracer.panel.setTitle('drawing track...');

	SRV.get('get-track', this.onGetTrackRsp, 'id', id, 'format', 'gtx', 'attrs', 'lon,lat,t', 'mindist', GTW.minTrackPtDist);
}

// Set Tracer at lon/lat location
Tracer.prototype.getLocation = function() {
	return this.point;
}

// Set Tracer at lon/lat location
Tracer.prototype.setLocation = function(point) {
	if (point == null) {
		return;
	}

	// Always last location
	this.point = point;

	if (this.tlabel == null) {

		this.iconId = 'icon' + this.name;

		var html = '<span class="tracer"><img id="' + this.iconId + '" src="' + this.iconURL + '" border="0" />&nbsp;<span class="tracername" >' + this.name + '</span></span>';

		// Setup TLabel object
		tl = new TLabel();
		tl.id = 'tlab' + this.name;
		tl.anchorLatLng = this.point;
		tl.anchorPoint = 'topLeft';
		tl.content = html;

		// To shift icon on exact lat/lon location (half size of icon)
		tl.markerOffset = new GSize(5, 5);

		// Add Tlabel to map
		GTW.map.addTLabel(tl);
		this.tlabel = tl;
	} else {
		// Only Move TLabel
		this.tlabel.setPosition(this.point);
	}
}

// Move Tracer to lon/lat location
Tracer.prototype.move = function(lon, lat) {

	// replace point
	var point = new GPoint(lon, lat);

	// Move TLabel
	this.setLocation(point);

	if (this.activeTrack != null) {
		this.activeTrack.addLivePoint(point);
	}
}

// Setting the visibility to visible
Tracer.prototype.show = function() {
	if (this.tlabel != null) {
		DH.show(this.tlabel.id);
	}
	this.panel.show();
	this.hidden = false;
}

// Setting the visibility to hidden
Tracer.prototype.hide = function() {
	if (this.tlabel != null) {
		DH.hide(this.tlabel.id);
	}
	this.panel.hide();
	this.hidden = true;
}

// Is Tracer live
Tracer.prototype.isLive = function() {
	return this.live;
}

// Is Tracer visible ?
Tracer.prototype.resumeTrack = function() {

}

// Set Tracer live
Tracer.prototype.setLive = function() {
	if (this.live == true) {
		return;
	}
	this.live = true;
	this.blink();
}

// Is Tracer visible ?
Tracer.prototype.suspendTrack = function() {

}

// Is Tracer visible ?
Tracer.prototype.isVisible = function() {
	return this.hidden == false;
}

// The continuous blinking
Tracer.prototype.blink = function() {
	DH.toggleVisibility(this.iconId);

	// JS Trick to have setTimeout() call our object method
	var tracer = this;
	setTimeout(function() {
		tracer.blink();
	}, 250);
	//  DH.isVisible(this.iconId) ? 3000 : 400
}

// Track
function Track(id, name, panel, tracer) {
	this.id = id;
	this.name = name;
	this.startDate = 0;
	this.endDate = 0;
	this.panel = panel;
	this.tracer = tracer;
	this.color = panel.bgcolor;
	this.segments = [];
	this.media = [];
	this.pois = [];
	this.polyLines = [];

}

// Add point
Track.prototype.addPoint = function (aPoint) {
	if (this.segments.length == 0) {
		this.segments[0] = [];
	}

	this.segments[this.segments.length - 1].push(aPoint);
}

// Add live point
Track.prototype.addLivePoint = function (aPoint) {

	var lastPoint = this.getLastPoint();
	this.addPoint(aPoint);

	// Only draw line if we have a previous point
	if (lastPoint != null) {
		var ptArr = [];
		ptArr[0] = lastPoint;
		ptArr[1] = aPoint;
		this.drawPoints(ptArr);
	}
}

// Add medium to this tracer
Track.prototype.addMedium = function (medium) {
	this.media[medium.id] = medium;
	medium.show();
}

// Add medium to this tracer
Track.prototype.addPOI = function (poi) {
	this.pois[poi.id] = poi;
	poi.show();
}

Track.prototype.clear = function () {
	for (var i = 0; i < this.polyLines.length; i++) {
		GTW.map.removeOverlay(this.polyLines[i]);
	}

	this.polyLines = [];

	for (var i = 0; i < this.media.length; i++) {
		if (this.media[i]) {
			this.media[i].remove();
		}
	}

	for (var i = 0; i < this.pois.length; i++) {
		if (this.pois[i]) {
			this.pois[i].remove();
		}
	}

}

// Drawin view
Track.prototype.draw = function () {
	this.panel.setContent('<p class="pn-text-content" >track #' + this.id + '</p>');

	for (var i = 0; i < this.segments.length; i++) {
		// Draw the entire Track on map
		this.drawPoints(this.segments[i]);
	}

	for (var i = 0; i < this.media.length; i++) {
		this.media[i].show();
	}

	for (var i = 0; i < this.pois.length; i++) {
		this.pois[i].show();
	}
}

// Draw array of points
Track.prototype.drawPoints = function (ptArr) {
	// Draw slices to prevent browser problems with large polyliness
	if (ptArr.length > 200) {
		this.drawPoints(ptArr.slice(0, 200));
		this.drawPoints(ptArr.slice(200));
		return;
	}

	// Draw using GMap Polyline
	var pl = new GPolyline(ptArr, this.color, GTW.polyLineWidth, GTW.polyLineOpacity);
	GTW.map.addOverlay(pl);
	this.polyLines.push(pl);
}

// remove from view
Track.prototype.getLastPoint = function () {
	var segment = this.getSegment(this.segments.length - 1);
	if (segment == null) {
		return null;
	}

	return this.getPoint(this.segments.length - 1, segment.length - 1);
}

// Get Medium by index
Track.prototype.getMedium = function (index) {
	if (this.media.length == 0 || index < 0 || index >= this.media.length) {
		return null;
	}

	return this.media[index];
}

// Get POI by index
Track.prototype.getPOI = function (index) {
	if (this.pois.length == 0 || index < 0 || index >= this.pois.length) {
		return null;
	}

	return this.pois[index];
}

// remove from view
Track.prototype.getPoint = function (segIndex, ptIndex) {
	var segment = this.getSegment(segIndex);
	if (segment == null) {
		return null;
	}

	if (segment.length == 0 || ptIndex < 0 || ptIndex >= segment.length) {
		return null;
	}

	return segment[ptIndex];
}

// get track segment
Track.prototype.getSegment = function (segIndex) {
	if (this.segments.length <= 0 || segIndex < 0 || segIndex >= this.segments.length) {
		return null;
	}
	return this.segments[segIndex];
}

Track.prototype.getTracer = function() {
	return this.tracer;
}

// remove from view
Track.prototype.remove = function () {
	this.clear();
	this.polyLines = [];
	this.segments = [];
	this.media = [];
	this.pois = [];
}

// Populate from GTX (GPX-like) document
Track.prototype.setGTX = function (gtx) {
	// Sanity check for invalid XML (empty doc)
	if (!gtx.documentElement) {
		alert('empty track document (maybe invalid chars in XML)');
		return;
	}

	// Get general info
	var infoElm = gtx.documentElement.getElementsByTagName('info')[0];
	if (infoElm) {
		this.name = infoElm.getAttribute('name');
		this.startDate = new Number(infoElm.getAttribute('startdate'));
		this.endDate = new Number(infoElm.getAttribute('enddate'));
	}

	// Get segments
	var segElms = gtx.documentElement.getElementsByTagName('seg');
	this.segments = [];

	// Sick case
	if (segElms) {
		this.distance = 0;
		for (i = 0; i < segElms.length; i++) {

			// Get points in segment
			var ptElements = segElms[i].getElementsByTagName('pt');

			// Sick case
			if (!ptElements || ptElements.length == 0) {
				continue;
			}

			// Go through all Track points for this segment
			var ptArr = [];
			var nextPt;
			for (j = 0; j < ptElements.length; j++) {
				nextPt = new GPoint(ptElements[j].getAttribute('lon'), ptElements[j].getAttribute('lat'))
				nextPt.time = new Number(ptElements[j].getAttribute('t'));
				nextPt.distance = 0;
				nextPt.speed = 0;
				if (j > 0) {
					nextPt.distance = GMAP.distance(ptArr[j - 1], nextPt);
					nextPt.speed = nextPt.distance / ((nextPt.time - ptArr[j - 1].time) / 3600000);
					this.distance += nextPt.distance;
				}
				ptArr.push(nextPt);
			}

			this.segments.push(ptArr);
		}

	}

	// Get medium locations
	var mediumElements = gtx.documentElement.getElementsByTagName('medium');
	this.media = [];
	if (mediumElements) {
		var nextMedium;
		for (i = 0; i < mediumElements.length; i++) {
			nextMedium = mediumElements[i];

			// Create and draw location-based medium
			medium = new Medium(nextMedium.getAttribute('id'),
					nextMedium.getAttribute('name'),
					nextMedium.getAttribute('kind'),
					nextMedium.getAttribute('mime'),
					nextMedium.getAttribute('time'),
					this.panel,
					nextMedium.getAttribute('lon'),
					nextMedium.getAttribute('lat'));
			this.media.push(medium);
		}
	}


	// Get POI locations
	var poiElements = gtx.documentElement.getElementsByTagName('poi');
	this.pois = [];
	if (poiElements) {
		var nextPOI;
		for (i = 0; i < poiElements.length; i++) {
			nextPOI = poiElements[i];

			// Create and draw location-based medium
			poi = new POI(nextPOI.getAttribute('id'),
					nextPOI.getAttribute('name'),
					nextPOI.getAttribute('type'),
					nextPOI.getAttribute('time'),
					this.panel,
					nextPOI.getAttribute('lon'),
					nextPOI.getAttribute('lat'));
			this.pois.push(poi);
		}
	}
}

// Control playing back Tracks
function TrackPlayer(bgcolor, fgcolor) {

	this.track = null;
	this.active = false;
	this.playing = false;
	this.intervalId = null;
	this.segIndex = 0;
	this.pointIndex = 0;
	this.mediumIndex = 0;
	this.poiIndex = 0;
	this.nextMedium = null;
	this.nextPOI = null;
	this.playPoints = [];
	this.panel = new Panel('- TrackPlayer -', bgcolor, fgcolor);
	this.panel.setDimension(180, 180);
	this.FEATURE_DISPLAY_TIME = 2000;
	this.POINT_INTERVAL_TIME = 100;
	this.featureShowing = false;

	// Create GUI
	var container = document.createElement('div');
	container.className = 'trackplayercontainer';

	this.info = document.createElement('div');
	this.info.className = 'trackplayerinfo';
	container.appendChild(this.info);

	var controls = document.createElement('div');
	controls.className = 'trackplayercontrols';
	container.appendChild(controls);

	var prev = document.createElement('div');
	prev.className = 'trackplayerprev';
	controls.appendChild(prev);

	this.playPause = document.createElement('div');
	this.playPause.className = 'trackplayerplay';
	controls.appendChild(this.playPause);

	var stop = document.createElement('div');
	stop.className = 'trackplayerstop';
	controls.appendChild(stop);

	var next = document.createElement('div');
	next.className = 'trackplayernext';
	controls.appendChild(next);

	this.panel.setContent(container);

	// Setup controls-click callbacks
	var trackPlayer = this;

	this.panel.onClose = function () {
		trackPlayer.stop();
		GTW.trackPlayer = null;
	}

	this.onPlayPause = function(e) {
		DH.cancelEvent(e);

		// First time ?
		if (trackPlayer.isActive() == false) {
			trackPlayer.play();
			return;
		}

		// Pause/resume when already active
		if (trackPlayer.isPlaying()) {
			trackPlayer.pause();
		} else {
			trackPlayer.resume();
		}

	}

	this.onStop = function(e) {
		DH.cancelEvent(e);
		trackPlayer.stop();
	}

	this.onNext = function(e) {
		DH.cancelEvent(e);
		trackPlayer.next();
	}

	this.onPrev = function(e) {
		DH.cancelEvent(e);
		trackPlayer.prev();
	}

	DH.addEvent(this.playPause, 'click', this.onPlayPause, true);
	DH.addEvent(stop, 'click', this.onStop, true);
	DH.addEvent(next, 'click', this.onNext, true);
	DH.addEvent(prev, 'click', this.onPrev, true);
	this.showInfo('ready');
}


// Setting the visibility to visible
TrackPlayer.prototype.hide = function() {
	this.panel.hide();
}

// Are we initialized ?
TrackPlayer.prototype.isActive = function() {
	return this.active;
}

// Are we playing a Track ?
TrackPlayer.prototype.isPlaying = function() {
	return this.active == true && this.playing == true;
}

// Play next point
TrackPlayer.prototype.next = function() {
	if (this.isPlaying() == true) {
		return;
	}

	if (this.isActive() == false) {
		this._initPlay();
	}

	this._playNext();
}

// Play previous point
TrackPlayer.prototype.prev = function() {
	this.showInfo('not yet implemented');
}

// Show message in window
TrackPlayer.prototype.showInfo = function(msg) {
	this.info.innerHTML = msg;
}

// Show general track info
TrackPlayer.prototype.showTrackInfo = function() {
	this.showInfo('track: ' + this.track.name + '<br/>id: ' + this.track.id + '<br/>s: ' + GTW.formatDateAndTime(this.track.startDate) + '<br/>e: ' + GTW.formatDateAndTime(this.track.endDate) + '<br/>distance: ' + this.track.distance.toFixed(2) + ' km<br/><b>READY</b>');
}

// Suspend playing
TrackPlayer.prototype.pause = function() {
	this.playing = false;
	this.playPause.className = 'trackplayerplay';

	// Draw remaining points
	if (this.playPoints.length > 0) {
		this.track.drawPoints(this.playPoints);
		var lastPt = this.playPoints[this.playPoints.length - 1]
		this.playPoints = [];
		this.playPoints.push(lastPt);
	}

	this.showInfo('paused');
	this._stopLoop();

}


// Go playing
TrackPlayer.prototype.play = function() {
	if (this.isActive() == false) {
		this._initPlay();
	}

	this.playPause.className = 'trackplayerpause';

	this.playing = true;
	this.showInfo('playing');

	this._startLoop(this.POINT_INTERVAL_TIME);
}

// Resume playing
TrackPlayer.prototype.resume = function() {
	this.showInfo('playing resumed');

	this.playing = true;
	this.playPause.className = 'trackplayerpause';
	var player = this;
	this._startLoop(this.POINT_INTERVAL_TIME);
}

// Setting the visibility to visible
TrackPlayer.prototype.show = function() {
	this.panel.show();
}

// Stop playing
TrackPlayer.prototype.stop = function() {
	this.pause();
	this.active = false;
	this.intervalId = null;
	this.segIndex = 0;
	this.pointIndex = 0;
	this.curPoint = null;
	this.mediumIndex = 0;
	this.nextMedium = null;
	this.nextPOI = null;
	this.poiIndex = 0;
	this.playPoints = [];
	this.showTrackInfo();
}

// Set track to control
TrackPlayer.prototype.setTrack = function(track) {
	if (this.track == track) {
		return;
	}
	if (this.isActive() == true) {
		this.stop();
	}
	this.track = track;
	this.panel.setBGColor(track.panel.bgcolor);
	this.panel.setFGColor(track.panel.fgcolor);
	this.showTrackInfo();
}

// Initalize play-state
TrackPlayer.prototype._initPlay = function() {
	if (this.track == null) {
		return;
	}

	this.track.clear();
	this.segIndex = 0;
	this.pointIndex = 0;
	this.mediumIndex = 0;
	this.poiIndex = 0;
	this.distance = 0;
	this.nextMedium = this.track.getMedium(this.mediumIndex++);
	this.nextPOI = this.track.getPOI(this.poiIndex++);

	this.curPoint = this.track.getPoint(0, this.pointIndex++);
	if (this.curPoint == null) {
		return;
	}
	GTW.map.recenterOrPanToLatLng(this.curPoint);

	this.active = true;

}

// Start interval loop
TrackPlayer.prototype._startLoop = function(interval) {
	this._stopLoop();
	var player = this;
	this.intervalId = setInterval(function() {
		player._playNext()
	}, interval);
}

// Stop interval loop
TrackPlayer.prototype._stopLoop = function() {
	if (this.intervalId == null) {
		return;
	}
	clearInterval(this.intervalId);
	this.intervalId = null;
}

// Play next points
TrackPlayer.prototype._playNext = function() {
	try {
		if (this.isPlaying() == true && this.featureShowing == true) {
			this.featureShowing == false;
			this._startLoop(this.POINT_INTERVAL_TIME);
		}

		var segment = this.track.getSegment(this.segIndex);

		if (this.curPoint != null) {
			// Recenter when map edge reached
			if (!GMAP.isInBox(this.curPoint, GTW.map.getBoundsLatLng())) {
				GTW.map.recenterOrPanToLatLng(this.curPoint);

				// Wait a while
				if (this.isPlaying() == true) {
					this.featureShowing = true;
					this._startLoop(this.FEATURE_DISPLAY_TIME);
				}
			}

			var endOfSeg = this.pointIndex >= segment.length - 1;
			var endOfTrack = endOfSeg && (this.segIndex >= this.track.segments.length - 1);
			if (this.nextMedium != null && (this.curPoint.time >= this.nextMedium.time || endOfTrack == true)) {
				this._playMedium(this.nextMedium);
				this.nextMedium = this.track.getMedium(this.mediumIndex++);
				return;
			}

			if (this.nextPOI != null && (this.curPoint.time >= this.nextPOI.time || endOfTrack == true)) {
				this._playPOI(this.nextPOI);
				this.nextPOI = this.track.getPOI(this.poiIndex++);
				return;
			}

			this.track.getTracer().setLocation(this.curPoint);
			this.playPoints.push(this.curPoint);
			this.distance += this.curPoint.distance;

			if (this.playPoints.length % 5 == 0 || this.isPlaying() == false) {
				this.showInfo(GTW.formatDateAndTime(this.curPoint.time) + '<br/>dist: ' + this.distance.toFixed(2) + '<br/>speed: ' + this.curPoint.speed.toFixed(2) + ' km/h<br/>lon: ' + this.curPoint.x + '<br/>lat: ' + this.curPoint.y);
			}

			// Draw every N points or if end of segment is reached
			if (this.playPoints.length % 5 == 0 || endOfSeg == true) {
				this.track.drawPoints(this.playPoints);
				this.playPoints = [];
				if (endOfSeg == false) {
					this.playPoints.push(this.curPoint);
				}
			}
		}

		// Advance point  and test for end of segment
		if (segment != null && ++this.pointIndex >= segment.length) {
			this.playPoints = [];
			this.showInfo('end of segment');
			this.pointIndex = 0;
			++this.segIndex;
		}

		// Stop if end of track and no features remaining
		if (this.segIndex >= this.track.segments.length && this.nextMedium == null && this.nextPOI == null) {
			this.stop();
		} else {
			this.curPoint = this.track.getPoint(this.segIndex, this.pointIndex);
		}

	} catch(e) {
		// panic
		this.stop();
	}
}

// Play Medium in Panel
TrackPlayer.prototype._playMedium = function(medium) {
	medium.show();
	medium.blink(6);
	this.showInfo('showing ' + medium.kind + '<br/>name: ' + medium.name + '<br/>' + GTW.formatDateAndTime(medium.time));

	medium.display();

	this.featureShowing = true;
	if (this.isPlaying() == true) {
		this._startLoop(this.FEATURE_DISPLAY_TIME);
	}

}

// Play POI in Panel
TrackPlayer.prototype._playPOI = function(poi) {
	poi.show();
	poi.blink(6);
	this.showInfo('showing POI');
	poi.display();
	this.featureShowing = true;

	if (this.isPlaying() == true) {
		this._startLoop(this.FEATURE_DISPLAY_TIME);
	}
}

// Control automatic playing back Tracks
function TrackAutoPlayer() {
	this.INTERVAL_MILLIS = 2000;
	this.intervalId = null;
	this.tracer = null;
	this.lastTracer = null;
	this.state = 'IDLE';
	this.n = 0;
}

// Go playing
TrackAutoPlayer.prototype.start = function() {
	this.stop();
	var player = this;
	this.intervalId = setInterval(function() {
		player._doWork()
	}, this.INTERVAL_MILLIS);
}

// Go playing
TrackAutoPlayer.prototype.stop = function() {
	if (this.intervalId == null) {
		return;
	}
	clearInterval(this.intervalId);
	this.intervalId = null;
}

// Timer callback
TrackAutoPlayer.prototype._doWork = function() {
	if (this.intervalId == null) {
		return;
	}

	// Do action based on state
	if (this.state == 'IDLE') {
		if (this.tracer != null) {
			this.lastTracer = this.tracer;
			this.tracer = null;
		}

		// read random track info
		this.state = 'READING';

		this._getRandomTrack();
	} else if (this.state == 'READING') {
		GTAPP.showStatus('reading next track... ' + (this.n++));

		if (this.tracer != null && this.tracer.getActiveTrack() != null) {
			this.state = 'READY';
		}


	} else if (this.state == 'READY') {
		GTAPP.showStatus('ready');

		var track = this.tracer.getActiveTrack();
		var trackPlayer = GTW.getTrackPlayer();
		track.panel.setXY(2, 140);
		track.panel.setDimension(320, 600);
		this.tracer.show();
		track.panel.full = true;
		// track.panel.setOpacity(0.95);
		trackPlayer.setTrack(track);
		trackPlayer.hide();
		trackPlayer.play();
		this.state = 'PLAYING';
	} else if (this.state == 'PLAYING') {

		var trackPlayer = GTW.getTrackPlayer();
		if (trackPlayer.isPlaying() == false) {
			this.state = 'IDLE';
		} else if (this.tracer.getActiveTrack() != null) {
			GTAPP.showStatus('playing ' + this.tracer.name + '/' + this.tracer.getActiveTrack().name);
		}
	}
}

// Timer callback
TrackAutoPlayer.prototype._getRandomTrack = function() {

	// Need this for JS local scope in callback
	var player = this;
	// Define callback for async response
	this.onQueryTrackRsp = function (records) {
		//  id, name,state,loginname,lon,lat
		GTAPP.showStatus('read ' + records[0].getField('name'));
		player.tracer = GTW.createTracer(records[0].getField('loginname'), records[0].getField('lon'), records[0].getField('lat'));

		player.tracer.readTrack(records[0].getField('id'), records[0].getField('name'), false);

		if (player.lastTracer != null) {
			player.lastTracer.clear();
			player.lastTracer = null;
		}
	}
	SRV.get('q-random-track', this.onQueryTrackRsp);
}

/*
 * DateFormat.js
 * Formats a Date object into a human-readable string
 * See http://www.gazingus.org/html/Date_Formatting_Function.html
 *
 * Copyright (C) 2001 David A. Lindquist (http://www.gazingus.org)
 */

Date.MONTHS = [
		'January', 'February', 'March', 'April', 'May', 'June', 'July',
		'August', 'September', 'October', 'November', 'December'
		];

Date.DAYS = [
		'Sunday', 'Monday', 'Tuesday', 'Wednesday',
		'Thursday', 'Friday', 'Saturday'
		];

Date.SUFFIXES = [
		'st','nd','rd','th','th','th','th','th','th','th',
		'th','th','th','th','th','th','th','th','th','th',
		'st','nd','rd','th','th','th','th','th','th','th',
		'st'
		];

Date.prototype.format = function(mask) {
	var formatted = ( mask != null ) ? mask : 'DD-MMM-YY';
	var letters = 'DMYHdhmst'.split('');
	var temp = new Array();
	var count = 0;
	var regexA;
	var regexB = /\[(\d+)\]/;

	var day = this.getDay();
	var date = this.getDate();
	var month = this.getMonth();
	var year = this.getFullYear().toString();
	var hours = this.getHours();
	var minutes = this.getMinutes();
	var seconds = this.getSeconds();
	var formats = new Object();
	formats[ 'D' ] = date;
	formats[ 'd' ] = date + Date.SUFFIXES[ date - 1 ];
	formats[ 'DD' ] = ( date < 10 ) ? '0' + date : date;
	formats[ 'DDD' ] = Date.DAYS[ day ].substring(0, 3);
	formats[ 'DDDD' ] = Date.DAYS[ day ];
	formats[ 'M' ] = month + 1;
	formats[ 'MM' ] = ( month + 1 < 10 ) ? '0' + ( month + 1 ) : month + 1;
	formats[ 'MMM' ] = Date.MONTHS[ month ].substring(0, 3);
	formats[ 'MMMM' ] = Date.MONTHS[ month ];
	formats[ 'Y' ] = ( year.charAt(2) == '0' ) ? year.charAt(3) : year.substring(2, 4);
	formats[ 'YY' ] = year.substring(2, 4);
	formats[ 'YYYY' ] = year;
	formats[ 'H' ] = hours;
	formats[ 'HH' ] = ( hours < 10 ) ? '0' + hours : hours;
	formats[ 'h' ] = ( hours > 12 || hours == 0 ) ? Math.abs(hours - 12) : hours;
	formats[ 'hh' ] = ( formats[ 'h' ] < 10 ) ? '0' + formats[ 'h' ] : formats[ 'h' ];
	formats[ 'm' ] = minutes;
	formats[ 'mm' ] = ( minutes < 10 ) ? '0' + minutes : minutes;
	formats[ 's' ] = seconds;
	formats[ 'ss' ] = ( seconds < 10 ) ? '0' + seconds : seconds;
	formats[ 't' ] = ( hours < 12 ) ?  'A' : 'P';
	formats[ 'tt' ] = ( hours < 12 ) ?  'AM' : 'PM';

	for (var i = 0; i < letters.length; i++) {
		regexA = new RegExp('(' + letters[ i ] + '+)');
		while (regexA.test(formatted)) {
			temp[ count ] = RegExp.$1;
			formatted = formatted.replace(RegExp.$1, '[' + count + ']');
			count++;
		}
	}

	while (regexB.test(formatted)) {
		formatted = formatted.replace(regexB, formats[ temp[ RegExp.$1 ] ]);
	}

	return formatted;
}


