
function ImageHolder (imgurl, n, prio)
{
	this.idx = n;
	this.priority = prio;
	this.imgurl = imgurl;   // image url
	this.loaded = false;
	this.loading = false;
	this.img_obj = null;
}


ImageHolder.prototype.start_loading = function (loader)
{
	if (this.loaded || this.loading) {
		debug_msg("Unexpeded call to start_loading: idx = " + this.idx + " loaded = " + this.loaded + " loading = " + this.loaded);
		return;
	}
	this.loading = true;
	this.img_obj = document.createElement('IMG');
	var n = this.idx;
	this.img_obj.onload = function () { // Closure
		loader.image_loaded(n); 
	}
	this.img_obj.src = this.imgurl;
}

ImageHolder.prototype.done_loading = function (updloading)
{
	if (!this.loading) {
		debug_msg("Unexpeded call to done_loading: idx = " + str(this.idx) + " loaded = " + str(this.loaded) + " loading = " + str(this.loaded));
		return;
	}
	this.loaded = true;
	if (updloading) {
		this.loading = false;
	}
	this.width = this.img_obj.width;
	this.height = this.img_obj.height;
}

function ImgPreloader ()
{
	this.destroyed = false;
	this.all_done = false;
	this.queue = new Array();
	this.cache = new Array();
	this.loading_cnt = 0;
	this.loaded_cnt  = 0;
	this.max_load = 1;
	this.fullcheck = 2000; /* msecond(s) */
	this.recheck = this.fullcheck;
	this.posmiss = new Array();
}

ImgPreloader.prototype.destroy = function ()
{
	/* 
	 * Dump cache to break possible dom-javascript cycles which
	 * can lead to memory-leaks in some browsers 
	 * Should be called on page close.
	*/
	this.destroyed = true;
	this.all_done = true;
	this.cache = null;
	this.queue = null;
	this.posmiss = null;
}

ImgPreloader.prototype.preload = function (imgurl, priority)
{
	var img = new ImageHolder(imgurl, this.cache.length, priority);
	
	// FIXME: linear search!
	this.cache[img.idx] = img;
	for (i = 0; i < this.queue.length; ++i) {
		if (this.cache[this.queue[i]].priority <= priority) {
			break;
		}
	}
	this.queue.splice(i, 0, img.idx);
	this.all_done = false;
	return img.idx;
}


ImgPreloader.prototype.image_loaded = function (idx) // called by onload event
{
	if (!this.destroyed && this.cache[idx].loading) {
		this.cache[idx].done_loading(true);
		this.loaded_cnt++;
	}
}

ImgPreloader.prototype.dofullcheck = function ()
{
	var i, k;
	var missed = 0;
	for (var i = 0; i < this.posmiss.length; ++i) {
		k = this.posmiss[i];
		if (this.cache[k].loading && this.cache[k].img_obj.width > 0) {
			// second time, assume missed onload event
			this.cache[k].done_loading(true);
			this.loaded_cnt++; // Danger, danger Will Robinson
			missed++;
		}
	}
	if (missed > 0) {
		debug_msg("PreLoader: Missed " + missed + " onload event(s)");
		this.fullcheck /= 2; /* keep halving */
	}

	this.posmiss = new Array();
	var real_cnt = 0;
	for (var i = 0; i < this.cache.length; ++i) {
		img = this.cache[i];
		if (img.loaded && !img.loading) {
			real_cnt++;
		}
		else if (img.loading && img.img_obj.width > 0) {
			this.posmiss.push(i);
		}
	}
	
	if (real_cnt == this.cache.length) { // ALL DONE
		this.all_done = true;
		if (real_cnt != this.loaded_cnt) {
			debug_msg("Preloader: Loaded count is wrong : " + this.loaded_cnt + " should be " + real_cnt);
			this.loaded_cnt = real_cnt;
			this.all_done = false; // recheck next time
		}
		if (real_cnt != this.loading_cnt) {
			debug_msg("Preloader: Loaded count is wrong : " + this.loaded_cnt + " should be " + real_cnt);
			this.loading_cnt = real_cnt;
			this.all_done = false; // recheck next time
		}
		if (this.queue.length > 0) {
			debug_msg("Preloader: queue not empty : " + this.queue.length);
			this.all_done = false; // recheck next time
		}
	}
}

ImgPreloader.prototype.schedule = function (ticks)
{
	if (this.destroyed || this.all_done) {
		return;
	}

	if (this.posmiss.length > 0 || this.recheck <= ticks) {
		this.dofullcheck();
		this.recheck = this.fullcheck;
	}
	else {
		this.recheck -= ticks;
	}

	var img;
	while (this.queue.length > 0 && this.loaded_cnt + this.max_load > this.loading_cnt) {
		img = this.cache[this.queue.pop()];
		if (!img.loading && !img.loaded) {
			img.start_loading(this);
			this.loading_cnt++;
		}
	}
}

ImgPreloader.prototype.set_maxload = function (maxl)
{
	this.max_load = maxl;
}

ImgPreloader.prototype.bump = function (idx)
{
	if (this.destroyed) {
		return;
	}
	var img = this.cache[idx];
	if (img && !img.loading && !img.loaded) {
		img.start_loading(this);
		this.loading_cnt++;
	}
	// just leave on queue
}

ImgPreloader.prototype.is_loaded = function (idx)
{
	if (this.destroyed) {
		return false; // unknown actually
	}
	var img = this.cache[idx];
	if (img.loaded) {
		return true;
	}
	else if (img.loading && img.img_obj.width > 0) {
		img.done_loading(false);
		return true;
	}
	return false;
}

function VliegerShow (n, maxShow, callbackfunc, loading_kite, loading_fly, loading_txt)
{
	if (n > kite_space.nkites) {
		debug_msg("Not enough positions for " + n + " kites (max " + kite_space.nkites + ")");
		n = kite_space.nkites;
	}
	this.destroyed = false;
	this.showing = false;
	this.max_showing = maxShow;
	this.preloader = new ImgPreloader();
	this.loading_kiteimg = loading_kite;
	this.loading_flyimg = loading_fly;
	this.loading_txtimg = loading_txt;
	this.num_vliegers = n;
	this.callbackfunc = callbackfunc;
	this.canvas = null;
	this.vliegers = new Array(n+1); // index starts with 1 
	this.kites_waiting = 0;
	this.null_vlieger = new NullVlieger();
	this.clock = 0;
	this.interval = new MyInterval();
	this.interval.init(this, 100);
	this.positionmap = new Array(n+1);
	this.swap = new Object();
	this.swap.n   = kite_space.first_n;
	this.swap.off = this.swap.n+1;
	this.swap.m   = parseInt(Math.ceil(this.num_vliegers / this.swap.n));

	// mouse tracking for generating over and out events
	this.mouse = new Object();
	this.mouse.in_vlieger = 0;
	this.mouse.x  = 0;
	this.mouse.y  = 0;
	this.mouse.changed = false;
	
	this.preloader.preload(this.loading_txtimg, 0);
	this.preloader.preload(this.loading_flyimg, 0);
	this.preloader.preload(this.loading_kiteimg, 0);

	var i;
	for (i = this.num_vliegers; i >= 0; --i) {
		this.positionmap[i] = i;
		this.vliegers[i] = this.null_vlieger;
	}
/*
	// "randomly" map the kites on the first n positions
	for (i = this.num_vliegers; i > 0; --i) {
		ni = 1 + parseInt(Math.round(Math.random() * (this.num_vliegers-1)));
		swpi = this.positionmap[i];
		this.positionmap[i] = this.positionmap[ni];
		this.positionmap[ni] = swpi;
	}
*/
	this.curr_attention_id = 0;
	this.curr_show_id = 0;
}

VliegerShow.prototype.destroy = function ()
{
//	this.stop_show();
	this.showing = false;
	this.destroyed = true;
	this.n = 0;
	this.vliegers = null;
	if (this.interval) {
		this.interval.clear();
		this.interval = null;
	}
	if (this.preloader) {
		this.preloader.destroy();
		this.preloader = null;
	}
	if (this.canvas && this.canvas.div) {
		this.canvas.div.innerHTML = '';
		this.canvas.div = null;
	}
}


VliegerShow.prototype.load_vlieger = function (vliegerobj)
{
	if (this.destroyed) return false;
	
	if (!vliegerobj) {
		debug_msg('load_vlieger: null object');
		return false;
	}
	var idx = vliegerobj.get_id();
	if (idx <= 0 || idx > this.num_vliegers) {
		debug_msg('load_vlieger: bad index ' + idx);
		return false;
	}
	if (this.vliegers[idx] != this.null_vlieger) { // FIXME: werkt deze test???
		debug_msg('load_vlieger: vlieger already loaded ' + idx);
		return false;
	}
	this.vliegers[idx] = vliegerobj;
	var i = this.positionmap[idx];
	var m_max = (i <= kite_space.first_n ? kite_space.first_m : kite_space.m_max);
	var posobj = kite_space.positions[i];
	vliegerobj.set_positions(posobj.fly, posobj.start, posobj.start, posobj.height/kite_space.z_max, m_max);
	vliegerobj.set_size(kite_space.kite_w, kite_space.kite_h, kite_space.kite_d, kite_space.fly_w, kite_space.fly_h, kite_space.fly_d, kite_space.fly_w, kite_space.fly_h, kite_space.fly_d);
	this.kites_waiting++;
	return true;
}

VliegerShow.prototype.ready_vlieger = function (vidx)
{
	this.kites_waiting--;
}

VliegerShow.prototype.do_swap_next = function ()
{
	if (this.destroyed || !this.showing) return false;

	if (this.swap.m == 1) {
		return;
	}

	var n = this.swap.n;
	if (2*n > this.num_vliegers) {
		n = this.num_vliegers - n;
	}

	var i, j, k, posobj;
	var swaparr = new Array(n);
	var down_cnt = 0;
	for (i = 1; i <= n; ++i) {
		j = this.swap.off;
		k = this.positionmap[j];
		swaparr[i-1] = k;
		if (this.vliegers[k].get_status() == 'D') {
			this.vliegers[k].set_status('F');
			k = this.positionmap[i];
			this.vliegers[k].set_status('D');
		}
		this.positionmap[j] = this.positionmap[i];
		posobj = kite_space.positions[j];
		this.vliegers[this.positionmap[j]].set_positions(posobj.fly, posobj.start, posobj.start, posobj.height, kite_space.m_max);
		if (++this.swap.off > this.num_vliegers) {
			this.swap.off = this.swap.n+1;
		}
	}
	for (i = 1; i <= n; ++i) {
		this.positionmap[i] = swaparr[n-i]; // reverse swap
		posobj = kite_space.positions[i];
		this.vliegers[this.positionmap[i]].set_positions(posobj.fly, posobj.start, posobj.start, posobj.height, kite_space.first_m);
	}
}

VliegerShow.prototype.do_swap_prev = function ()
{
	if (this.destroyed || !this.showing) return false;
	
	if (this.swap.m == 1) {
		return;
	}
	
	var n = this.swap.n;
	if (2*n > this.num_vliegers) {
		n = this.num_vliegers - n;
	}

	var i, j, posobj;
	var swaparr = new Array();
	for (i = 1; i <= n; ++i) {
		if (--this.swap.off == this.swap.n) {
			this.swap.off = this.num_vliegers;
		}
		j = this.swap.off;
		k = this.positionmap[j];
		swaparr[i-1] = k;
		if (this.vliegers[k].get_status() == 'D') {
			this.vliegers[k].set_status('F');
			k = this.positionmap[i];
			this.vliegers[k].set_status('D');
		}
		this.positionmap[j] = this.positionmap[i];
		posobj = kite_space.positions[j];
		this.vliegers[this.positionmap[j]].set_positions(posobj.fly, posobj.start, posobj.start, posobj.height/kite_space.z_max, kite_space.m_max);
	}
	for (i = 1; i <= n; ++i) {
		this.positionmap[i] = swaparr[n-i]; // reverse swap
		posobj = kite_space.positions[i];
		this.vliegers[this.positionmap[i]].set_positions(posobj.fly, posobj.start, posobj.start, posobj.height/kite_space.z_max, kite_space.first_m);
	}
}

VliegerShow.prototype.swap_prev = function ()
{
	this.pending.swapnext = false;
	this.pending.swapprev = true;
}

VliegerShow.prototype.swap_next = function ()
{
	this.pending.swapnext = true;
	this.pending.swapprev = false;
}

VliegerShow.prototype.set_show_vlieger = function (vliegerid)
{
	this.pending.show_id = vliegerid;
}

VliegerShow.prototype.set_attention_vlieger = function (vliegerid)
{
	this.pending.atten_id = vliegerid;
}

VliegerShow.prototype.do_show_vlieger = function (vliegerid)
{
	if (this.destroyed || !this.showing) return false;
	
	if (!vliegerid || vliegerid > this.num_vliegers) {
		debug_msg('do_show_vlieger: bad index ' + vliegerid);
		return false;
	}
	if (this.vliegers[vliegerid].get_status() == 'N') {
		debug_msg("do_show_vlieger: this one doesn't fly index " + vliegerid);
		return false;
	}
	if (this.curr_show_id) {
		this.vliegers[this.curr_show_id].set_status('F'); // FIXME: ??? old status?
	}
	if (this.curr_show_id != vliegerid) {
		this.vliegers[vliegerid].set_status('S');
		this.curr_show_id = vliegerid;
	}
	else {
		this.curr_show_id = 0;
	}
	if (this.curr_attention_id == vliegerid) {
		this.curr_attention_id = 0;
	}
	return true;
}

VliegerShow.prototype.do_attention_vlieger = function (vliegerid)
{
	if (this.destroyed) return false;
	
	if (!vliegerid || vliegerid > this.num_vliegers) {
		debug_msg('do_attention_vlieger: bad index ' + vliegerid);
		return false;
	}
	if (this.vliegers[vliegerid].get_status() == 'N') {
		debug_msg("do_attention_vlieger: this one doesn't fly index " + vliegerid);
		return false;
	}
	if (this.curr_show_id == vliegerid) {
		return false;
	}
	if (this.curr_attention_id) {
		this.vliegers[this.curr_attention_id].set_status('F'); // FIXME: ??? old status?
	}
	if (this.curr_attention_id != vliegerid) {
		this.vliegers[vliegerid].set_status('A');
		this.curr_attention_id = vliegerid;
	}
	else {
		this.curr_attention_id = 0;
	}
	return true;
}

VliegerShow.prototype.get_drift_vector = function (alldir, max_m)
{
	var a = 1.570796327; // 0.5*pi
	if (alldir) {
		a *= 4; // 2*pi
	}
	var t1 = Math.random() * a;
	var t2 = Math.random() * a;
	var dn = Math.random() * max_m;
	var du = Math.sin(t1) * dn;
	var dv = Math.sin(t2) * du;
	du *= Math.cos(t2);
	dn *= Math.cos(t1);
	return new function() {
		this.u = du;
		this.v = dv;
		this.n = dn;
	}
}

VliegerShow.prototype.space2canvas = function (u, v, n, w, h)
{
	/* 
	 * transforms space coord u, v, n  and size (w, h) to projection on canvas 
	 *
	 * Note when when w and h != 0 then the (u,v,n) are coordinates of the image center and
	 * x,y,z of the top-left corner.
	 *
	 * Using canvas in UV-plane and the viewpoint eye on the N-axis on coordinate (0, 0, E)
	 * The origin (0, 0, 0) is mapped on canvas offsetLeft cnv_left and offsetTop cnv_top (note V-axis and 
	 * offsetTop are reversed).
	*/
	if (n < 0) {
		n = 0; // safety first 
	}
	var factor = this.canvas.resolution / (1.0 - (n / this.E));
	var x = parseInt(Math.round(u * factor)) + this.canvas.left;
	var y = this.canvas.top - parseInt(Math.round(v * factor));
	var cw = parseInt(Math.round(w * factor));
	var ch = parseInt(Math.round(h * factor));
	var cz = parseInt(Math.round((1.0 - (n / kite_space.d_max)) * 255)); // z-index 

	return new function () {
		this.x = x - parseInt(cw/2);
		this.y = y - parseInt(ch/2);
		this.z = cz;
		this.w = cw;
		this.h = ch;
	};
}

VliegerShow.prototype.canvas2space = function (x, y, n, w, h)
{
	/* 
	 * transforms canvas position t, l and size for given n to u, v, n and size in space 
	 *
	 * Note when when w and h != 0 then the (u,v,n) are coordinates of the image center and
	 * x,y,z of the top-left corner.
	 *
	 * Using canvas in UV-plane and the viewpoint eye on the N-axis on coordinate (0, 0, E)
	 * The origin (0, 0, 0) is mapped on canvas offsetLeft cnv_left and offsetTop cnv_top (note V-axis and 
	 * offsetTop are reversed).
	*/
	if (n < 0) {
		n = 0; // safety first
	}
	var factor = (1.0 - (n / this.E)) / this.canvas.resolution;
	var u = (x + parseInt(w/2) - this.canvas.left) * factor;
	var v = (this.canvas.top - (y+parseInt(h/2))) * factor;

	return new function() {
		this.u = u;
		this.v = v;
		this.n = n;
		this.w = w * factor;
		this.h = h * factor;
	};
}


VliegerShow.prototype.get_showpos = function (d)
{
	return this.canvas2space(this.canvas.width/2, this.canvas.height/2, d, 0, 0);
}

VliegerShow.prototype.check_loaded = function (idx)
{
	return this.preloader.is_loaded(idx);
}

VliegerShow.prototype.force_load = function (idx)
{
	return this.preloader.bump(idx);
}

VliegerShow.prototype.start_show = function (canvasnm)
{
	if (this.destroyed) return;
	
	// FIXME: test error
	this.canvas = new Object();
	this.canvas.div = document.getElementById(canvasnm);
	this.canvas.div.onmousemove = this.callback(0, 'mousemove');
	this.canvas.width = 0;
	this.canvas.height = 0;

	if (this.canvas.div.offsetHeight) {
		this.canvas.width  = this.canvas.div.offsetWidth;
		this.canvas.height = this.canvas.div.offsetHeight;
	}
	else if (document.all && this.canvas.div.clientHeight) {
		this.canvas.width  = this.canvas.div.clientWidth;
		this.canvas.height = this.canvas.div.clientHeight;
	}
	var du = kite_space.view_u[1] - kite_space.view_u[0];
	var dv = kite_space.view_v[1] - kite_space.view_v[0];
	var test_ratio =  du / dv;
	var test_width = parseInt(Math.round(test_ratio * this.canvas.height));
	

	if (this.canvas.width != test_width) {
		debug_msg("Background image ratio doesn't match: " + (this.canvas.width/this.canvas.height) + " should be " + test_ratio);
	}
	// If the ratio doesn't match just fit it inside the image and center it horizontal 
	// or place it at the bottom
	if (test_width <= this.canvas.width) {
		var dw = 0.5*(this.canvas.width - test_width);
		this.canvas.left = parseInt(Math.round(dw - (kite_space.view_u[0]/du) * (test_width-1)));
		this.canvas.top  = parseInt(Math.round((kite_space.view_v[1]/dv) * (this.canvas.height-1)));
		this.canvas.resolution = this.canvas.height / dv;
	}
	else {
		var test_height = parseInt(Math.round(this.canvas.width / test_ratio));
		var dh = this.canvas.height - test_height;
		this.canvas.left = parseInt(Math.round(-1 * (kite_space.view_u[0]/du) * (this.canvas.width-1)));
		this.canvas.top  = parseInt(Math.round(dh + (kite_space.view_v[1]/dv) * (test_height-1)));
		this.canvas.resolution = this.canvas.width / du;
	}
	this.E = kite_space.E;

	// preload images in the order of distance
	for (i = 1; i <= this.num_vliegers; ++i) {
		this.vliegers[this.positionmap[i]].load_images(this.preloader);
	}

	var i, start_pos, fly_pos;
	var imgobj, cnt_down;
	if (this.max_showing > 0) {
		cnt_down = this.max_showing;
		if (this.swap.n > cnt_down) {
			cnt_down = this.swap.n;
		}
	}
	else {
		cnt_down = this.num_vliegers;
	}
	for (i = 1; i <= this.num_vliegers; ++i) {
		imgobj = this.vliegers[i].start_flying(this, this.loading_txtimg, (cnt_down > 0));
		if (imgobj) {
			this.canvas.div.appendChild(imgobj);
			cnt_down--;
		}
	}
	this.showing = true;
	this.preloader.set_maxload(20); // FIXME: initialy high, big kite images one or two at the time?
	this.preloader.schedule(0);
	
	this.pending = new Object();
	this.pending.swapnext = false;
	this.pending.swapprev = false;
	this.pending.show_id = null;
	this.pending.atten_id = null;
	this.pending.click_vid = null;
	
	this.interval.start();
}

VliegerShow.prototype.stop_show = function ()
{
	if (this.destroyed || !this.showing) {
		return;
	}
	this.interval.stop();
	this.showing = false;
	var i;
	for (i = this.num_vliegers; i > 0; --i) {
		this.vliegers[i].release();
	}
}


VliegerShow.prototype.run = function (ticks)
{
	if (this.destroyed || !this.showing) {
		return;
	}

	// first perform pending actions
	var tmpid;
	if (this.pending.click_vid != null) {
		tmpid = this.pending.click_vid;
		this.pending.click_vid = null; // set to null first
		tmpid = this.find_hit(this.pending.click_x, this.pending.click_y, tmpid);
		if (tmpid != null) {
			// call it now so that we do the "pending" actions immediatly
			this.callbackfunc(tmpid, "click");
		}
	}
	if (this.pending.show_id != null) {
		tmpid = this.pending.show_id;
		this.pending.show_id = null;
		this.do_show_vlieger(tmpid);
	}
	if (this.pending.atten_id != null) {
		tmpid = this.pending.atten_id;
		this.pending.atten_id = null;
		this.do_attention_vlieger(tmpid);
	}
	if (this.pending.swapnext) {
		this.pending.swapnext = false;
		this.do_swap_next();
	}
	else if (this.pending.swapprev) {
		this.pending.swapprev = false;
		this.do_swap_prev();
	}

	var mc = false;
	if (this.mouse.changed) {
		this.mouse.changed = false;
		mc = true;
	}
	var mx = this.mouse.x;
	var my = this.mouse.y;
		
	this.clock += ticks;
	this.preloader.schedule(ticks);

	var i, tmp_d;
	var best_d = -1;
	var best_id = 0;
	for (i = this.num_vliegers; i > 0; --i) {
		if (this.vliegers[i].update(ticks) || mc || i == this.mouse.in_vlieger) {
			tmp_d = this.vliegers[i].check_hit(mx, my, best_d);
			if (tmp_d >= 0 && (best_d < 0 || tmp_d < best_d)) {
				best_d = tmp_d;
				best_id = i;
			}
		}
	}
	if (this.mouse.in_vlieger != best_id) {
		if (this.mouse.in_vlieger != 0) {
			this.callbackfunc(this.mouse.in_vlieger, "out");
		}
		if (best_id != 0) {
			this.callbackfunc(best_id, "over");
		}
	}
	this.mouse.in_vlieger = best_id;

	// preload big images at slower rate
	if (this.kites_waiting <= 2) {  // FIXME: hardcoded
		this.preloader.set_maxload(2);
	}

	// FIXME: compare clock with real clock and adjust interval time
}


VliegerShow.prototype.mouse_event = function (vid, eaction, e)
{
	if (!e) {
		var e = window.event;
	}
	var x, y;
	if (e.pageX || e.pageY)	{ 
		x = e.pageX;
		y = e.pageY;
	}
	else if (e.clientX || e.clientY) { // FIXME: should we check for undefined?
		x = e.clientX + document.body.scrollLeft + document.documentElement.scrollTop;
		y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
	}
	else {
		x = 0;
		y = 0;
	}

	// x,y are page coordinates, transform them to canvas coordinates.
	var objref = this.canvas.div;
	var o_left = 0;
	var o_top  = 0;

	if (objref.offsetParent) {
		if (objref.scrollLeft) {
			o_left += objref.scrollLeft;
		}
		if (objref.scrollTop) {
			o_top += objref.scrollTop;
		}

		while (objref.offsetParent) {
			o_top  += objref.offsetTop;
			o_left += objref.offsetLeft;
			if (objref.scrollLeft) {
				o_left -= objref.scrollLeft;
			}
			if (objref.scrollTop) {
				o_top -= objref.scrollTop;
			}
			objref = objref.offsetParent;
		}
	}
	else if (objref.x) {
		o_left = objref.x;
		o_top  = objref.y;
	}

	x -= o_left;
	y -= o_top;

	if (eaction == "mousemove") {
		// store for next interval run
		this.mouse.x = x;
		this.mouse.y = y;
		this.mouse.changed = true;
		return false; // FIXME: or true (bubbling?)
	}
	else if (eaction == "click") {
		this.mouse.x = x;
		this.mouse.y = y;
		this.pending.click_vid = vid;
		this.pending.click_x = x;
		this.pending.click_y = y;
	}
	else {
		debug_msg("Unexpected event " + eaction);
	}
	return false;
}


VliegerShow.prototype.callback = function (vliegerid, eaction)
{
	// return closure
	var self = this;
	return function (e) {
		return self.mouse_event(vliegerid, eaction, e);
	}
}

VliegerShow.prototype.find_hit = function (x, y, vid)
{
	if (vid != null) {
		if (this.vliegers[vid].check_hit(x, y, -1) >= 0) {
			return vid; // browser has done the depth check
		}
	}

	var best_d = -1;
	var best_vid = null;
	var tmp_d = 0;
	var i;

	// FIXME: linear search!
	for (i = 1; i <= this.num_vliegers; ++i) {
		if (i != vid) {
			tmp_d = this.vliegers[i].check_hit(x, y, best_d);
			if (tmp_d >= 0 && (best_d < 0 || tmp_d < best_d)) {
				best_d = tmp_d;
				best_vid = i;
			}
		}
	}
	return best_vid;
}

VliegerShow.prototype.inside_test = function (u, v, spritenm)
{
	var edges;
	if (spritenm == "kite") {
		edges = kite_space.kite_edges;
	}
	else {
		edges = kite_space.fly_edges; // FIXME: no fall edges!!!
	}
	for (i = 0; i < 4; ++i) {
		if (u * edges[i][0] + v * edges[i][1] < edges[i][2]) {
			return false;
		}
	}
	return true;
}


VliegerShow.prototype.get_loading_img = function (spritenm)
{
	if (spritenm == "kite") {
		return this.loading_kiteimg;
	}
	else if (spritenm == "fly" || spritenm == "fall") {
		return this.loading_flyimg;
	}
	else {
		return this.loading_txtimg;	
	}
}

function NullVlieger ()
{
	this.get_id 	= function() { return 0; }
	this.start_flying = function(v, d) { return null; };
	this.release 	= function() { };
	this.update 	= function(t) { return false; };
	this.set_status = function(s) { };
	this.get_status = function() { return 'N'; };
	this.display 	= function() { };
	this.check_hit 	= function() { return -1; };
	this.set_positions = function(f, s, p, h) { };
	this.load_images = function(preloader) { };
	this.set_speed 	= function(tu, tv, tn, ku, kv, kn) { };
}


function Vlieger (id, fn_big, fn_fly, fn_fall)
{
	this.id = id;


	// big "frontal" image of kite
	this.kite = new Object();
	this.kite.image = fn_big;
	this.kite.loaded = false;
	this.kite.idx = null;

	// flying "perspective sprite" image of kite
	this.fly = new Object();
	this.fly.image = fn_fly;
	this.fly.loaded = false;
	this.fly.idx = null;
	
	// falling "perspective sprite" image of kite
	this.fall = new Object();
	if (fn_fall == null || fn_fall == "") {
		this.fall.image = fn_fly;
	}
	else {
		this.fall.image = fn_fall;
	
	}
	this.fall.loaded = false;
	this.fall.idx = null;
	
	this.vstatus = 'N';
	this.is_moving = false;

	this.vliegershow = null;
	this.vlieger_img = null;
	
	this.dest  = new Object();
	this.curr  = new Object();
	this.speed = new Object();
	this.accel = new Object();
}

Vlieger.prototype.get_id = function ()
{
	return this.id;
}

Vlieger.prototype.get_status = function ()
{
	return this.vstatus;
}

	
Vlieger.prototype.set_size = function (kite_w, kite_h, kite_d, fly_w, fly_h, fly_d, fall_w, fall_h, fall_d)
{
	this.kite.h = kite_h;
	this.kite.w = kite_w;
	this.kite.d = kite_d;

	this.fall.h = fall_h;
	this.fall.w = fall_w;
	this.fall.d = fall_d;

	this.fly.h  = fly_h;
	this.fly.w  = fly_w;
	this.fly.d  = fly_d;
	
}

Vlieger.prototype.load_images = function (preloader)
{
	this.fly.idx = preloader.preload(this.fly.image, 1);
	this.kite.idx = preloader.preload(this.kite.image, 2);
	if (this.fall.image == this.fly.image) {
		this.fall.idx = this.fly.idx;
	}
	else {
		this.fall.idx = preloader.preload(this.fall.image, 3);
	}
}

Vlieger.prototype.start_flying = function (vshow, txtimg, upyn)
{
	if (this.vstatus != 'N') {
		debug_msg("Vlieger " + this.id + " is already flying");
		return;
	}

	this.vliegershow = vshow;

	this.vlieger_img = document.createElement('IMG');
	this.vlieger_img.className = 'vlieger';
	this.vlieger_img.id = 'vlieger_' + this.id;

	this.vlieger_img.onclick     = this.vliegershow.callback(this.id, "click");
//	this.vlieger_img.onmousemove = this.vliegershow.callback(this.id, "mousemove");
//	this.vlieger_img.onmouseover = this.vliegershow.callback(this.id, "over");
//	this.vlieger_img.onmouseout  = this.vliegershow.callback(this.id, "out");

	this.vlieger_img.src = txtimg; // temp image indication loading or transparent pixel gif.

	// determine start position off screen
	this.cnvpos = null;
	this.curr.u = this.startpos.u;
	this.curr.v = this.startpos.v;
	this.curr.n = this.startpos.n;

	if (!upyn) {
		this.set_status('D');
	}
	else if (this.vliegershow.check_loaded(this.fly.idx)) {
		this.fly.loaded = true;
		this.vliegershow.ready_vlieger(this.id);
		this.set_status('F');
	}
	else {
		this.set_status('W');
	}

	return this.vlieger_img;
}

Vlieger.prototype.release = function ()
{
	this.vstatus = 'N';
	this.is_moving = false;
	if (this.vlieger_img) {
		this.vlieger_img.parentNode.removeChild(this.vlieger_img);
		this.vlieger_img = null;
	}
}


function uvn_pos(uvnarr)
{
	this.u = uvnarr[0];
	this.v = uvnarr[1];
	this.n = uvnarr[2];

}

Vlieger.prototype.set_positions = function (fly_pos, start_pos, stop_pos, fly_h, fly_max)
{
	this.startpos = new uvn_pos(start_pos);
	this.flypos   = new uvn_pos(fly_pos);
	this.stoppos  = new uvn_pos(stop_pos);

	this.flypos.z_height   = fly_h;
	this.flypos.max_wander = fly_max;

	var du = (this.flypos.u - this.startpos.u);
	var dv = (this.flypos.v - this.startpos.v);
	var dn = (this.flypos.n - this.startpos.n);
	var f = 1.0 / Math.sqrt(du*du + dv*dv + dn*dn);

	this.rise_vector = new Object();
	this.rise_vector.u = du * f;
	this.rise_vector.v = dv * f;
	this.rise_vector.n = dn * f;

	if (this.vstatus == 'N' || this.vstatus == 'S') { /* NOT or SHOWING */
		return;
	}

	if (this.vstatus == 'W' || (this.vstatus == 'D' && !this.is_moving)) { /* WAIT OR DOWN */
		this.curr.u = this.startpos.u;
		this.curr.v = this.startpos.v;
		this.curr.n = this.startpos.n;
	}

	var move_time, max_delay;
	
	if (this.vstatus == 'F' || this.vstatus == 'A') {
		this.dest.u = this.flypos.u;
		this.dest.v = this.flypos.v;
		this.dest.n = this.flypos.n;
	
		if (this.curr.n > this.dest.n) {
			move_time = 3.0;
			max_delay = 0.5;
		}
		else {
			move_time = 5.0;
			max_delay = 1.0;
		}
		
		this.set_speed(move_time, move_time, move_time, 1.0, 1.0, 1.0);
		if (max_delay >= 0) {
			this.delay = parseInt(Math.ceil(max_delay * 1000.0 * Math.random()));
		}
		else {
			this.delay = 0;
		}
	}
}

Vlieger.prototype.set_status = function (newstatus)
{
	var move_time, max_delay, new_sprite;
	var k_u = 1.0;
	var k_v = 1.0;
	var k_n = 1.0;
	
	if (newstatus == 'S') { /* SHOW */
		var showpos = this.vliegershow.get_showpos(this.kite.d);
		this.dest.u = showpos.u;
		this.dest.v = showpos.v;
		this.dest.n = showpos.n;
		move_time = 2.0;
		max_delay = 0.0;
		k_u = 0.1;
		k_v = 0.1;
		new_sprite = "fly";
		if (!this.kite.loaded) {
			this.vliegershow.force_load(this.kite.idx);
		}
		if (this.vstatus == 'W' && !this.fly.loaded) {
			this.vliegershow.force_load(this.fly.idx);
		}
	}
	else if (newstatus == 'A') { /* ATTENTION */
		this.dest.u = this.flypos.u;
		this.dest.v = this.flypos.v;
		this.dest.n = this.flypos.n;
		move_time = 0.5;
		max_delay = 0.0;
		new_sprite = "fly";
	}
	else if (newstatus == 'W') { /* WAIT FOR FLY IMAGE LOAD */
		var drift = this.vliegershow.get_drift_vector(true, this.flypos.max_wander);
		this.dest.u = this.flypos.u + drift.u;
		this.dest.v = this.flypos.v + drift.v;
		this.dest.n = this.flypos.n + drift.n;
		new_sprite = "";
		move_time = 3.0;
		max_delay = 10.0;
//		max_delay = 4.0 + this.num_vliegers / 10.0;
	}
	else if (newstatus == 'F') { /* FLY */
		var drift = this.vliegershow.get_drift_vector(true, this.flypos.max_wander);
		this.dest.u = this.flypos.u + drift.u;
		this.dest.v = this.flypos.v + drift.v;
		this.dest.n = this.flypos.n + drift.n;
		
		if (this.vstatus == 'D') {
			move_time = 3.0;
			max_delay = 2.0;
			if (!this.is_moving) {
				this.curr.u = this.startpos.u;
				this.curr.v = this.startpos.v;
				this.curr.n = this.startpos.n;
			}
		}
		else if (this.vstatus == 'N') {
			move_time = 3.0;
			max_delay = 10.0;
//			max_delay = 4.0 + this.num_vliegers / 10.0;
		}
		else if (this.vstatus == 'W') {
			move_time = 4.0 * this.flypos.z_height + 1.5;
			max_delay = -1;
		}
		else if (this.vstatus == 'S') {
			move_time = 2.0;
			max_delay = 0.0;
		}
		else {
			move_time = 1.5;
			max_delay = 0.0;
		}
		new_sprite = "fly";
		if (!this.fly.loaded) {
			this.vliegershow.force_load(this.fly.idx);
		}
	}
	else if (newstatus == 'D') { /* FALL DOWN */
		if (this.vstatus == 'N') {
			this.curr.u = this.startpos.u;
			this.curr.v = this.startpos.v;
			this.curr.n = this.startpos.n;

			this.dest.u = this.startpos.u;
			this.dest.v = this.startpos.v;
			this.dest.n = this.startpos.n;
			new_sprite = "fly";
			move_time = 1.0;
			max_delay = 0.0;
		}
		else {
			this.dest.u = this.stoppos.u;
			this.dest.v = this.stoppos.v;
			this.dest.n = this.stoppos.n;
			new_sprite = "fall";
			if (!this.fall.loaded) {
				this.vliegershow.force_load(this.fall.idx);
			}
			move_time = 2.0;
			max_delay = 1.0;
		}
	}
	else {
		debug_msg('unknown status : ' + newstatus);
		return;
	}

	this.set_speed(move_time, move_time, move_time, k_u, k_v, k_n);

	if (max_delay >= 0) {
		this.delay = parseInt(Math.ceil(max_delay * 1000.0 * Math.random()));
	}
	this.vstatus = newstatus;

	old_image = this.curr.image;
	if (new_sprite == "fly" && this.curr.spritenm != "kite") { // change kite -> fly is done in update()
		if (!this.fly.loaded) {
			this.curr.image = this.vliegershow.get_loading_img("fly");
		}
		else {
			this.curr.image = this.fly.image;
		}
		this.curr.h = this.fly.h;
		this.curr.w = this.fly.w;
		this.curr.spritenm = new_sprite;
	}
	else if (new_sprite == "fall") {
		if (!this.fall.loaded) {
			this.curr.image = this.vliegershow.get_loading_img("fall");
		}
		else {
			this.curr.image = this.fall.image;
		}
		this.curr.h = this.fall.h;
		this.curr.w = this.fall.w;
		this.curr.spritenm = new_sprite;
	}
	if (old_image != this.curr.image) {
		this.update_image = true;
	}
	this.is_moving = true;
}

Vlieger.prototype.set_speed = function (t_u, t_v, t_n, k_u, k_v, k_n)
{
	t_u *= 1000.0;
	t_v *= 1000.0;
	t_n *= 1000.0;

	/*
	 *  v(t) = v0 + a*t
	 *  x(t) = x0 + v0*t + 0.5*a*t^2
	 *  ve = v(e) = k*v0
	 *  xe = x(e)
	 *
	 *  a = v0 * (k-1)/e
	 *  v0 = (xe-x0) / (0.5*e*(k+1))
	*/
	var deler = 0.5 * t_u * (k_u + 1);
	this.speed.u = Math.abs(this.dest.u - this.curr.u) / deler;
	this.accel.u = this.speed.u * (k_u - 1) / t_u;

	deler = 0.5 * t_v * (k_v + 1);
	this.speed.v = Math.abs(this.dest.v - this.curr.v) / deler;
	this.accel.v = this.speed.v * (k_v - 1) / t_v;

	deler = 0.5 * t_n * (k_n + 1);
	this.speed.n = Math.abs(this.dest.n - this.curr.n) / deler;
	this.accel.n = this.speed.n * (k_n - 1) / t_n;
}


Vlieger.prototype.display = function ()
{
	if (!this.vliegershow) {
		return;
	}
	var cnvpos = this.vliegershow.space2canvas(this.curr.u, this.curr.v, this.curr.n, this.curr.w, this.curr.h);

	var changed = false;
	if (!this.cnvpos || this.cnvpos.x != cnvpos.x || this.cnvpos.y != cnvpos.y || this.cnvpos.z != cnvpos.z) {
		this.vlieger_img.style.left = cnvpos.x + 'px';
		this.vlieger_img.style.top  = cnvpos.y + 'px';
		this.vlieger_img.style.zIndex = cnvpos.z;
		changed = true;
	}
	if (!this.cnvpos || this.cnvpos.w != cnvpos.w || this.cnvpos.h != cnvpos.h) {
		this.vlieger_img.width  = cnvpos.w;
		this.vlieger_img.height = cnvpos.h;
		changed = true;
	}
	if (changed) {
		this.cnvpos = cnvpos;
	}

	if (this.update_image) { // FIXME: before or after resize?
		this.vlieger_img.src = this.curr.image;
		this.update_image = false;
		changed = true;
	}
	return changed;
}

Vlieger.prototype.update = function (ticks)
{
	if (!this.kite.loaded) {
		if (this.vliegershow.check_loaded(this.kite.idx)) {
			this.kite.loaded = true;
		}
	}
	if (!this.fly.loaded) {
		if (this.vliegershow.check_loaded(this.fly.idx)) {
			this.fly.loaded = true;
			this.vliegershow.ready_vlieger(this.id);
		}
	}
	if (!this.fall.loaded) {
		if (this.vliegershow.check_loaded(this.fall.idx)) {
			this.fall.loaded = true;
		}
	}

	if (!this.is_moving) { 
		// FIXME: quick fix , check this
		if (this.vstatus == 'S' && this.curr.image != this.kite.image) { 
			if (this.kite.loaded) {
				this.curr.image = this.kite.image;
				this.update_image = true;
				this.display();
			}
		}
		return false; // Nothing or Motionless
	}	


	if (this.delay > ticks) {
		this.delay -= ticks;
	}
	else if (this.delay != 0) {
		this.delay = 0;
	}

	if (this.vstatus == 'W') { /* WAITING */
		if (!this.fly.loaded) {
			return false;
		}
		this.set_status('F'); /* FLYING */
		if (this.delay > 0) {
			this.display(); // remove "laden...." image 
		}
	}

	if (this.delay > 0) {
		return false;
	}

	var changed = false;

	if (this.curr.u != this.dest.u || this.curr.v != this.dest.v || this.curr.n != this.dest.n) {
		// FIXME: lineair movement only
		var m_u = this.speed.u * ticks;
		if (m_u <= 0.0 || m_u >= Math.abs(this.dest.u - this.curr.u)) {
			this.curr.u = this.dest.u;
		}
		else if (this.dest.u > this.curr.u) {
			this.curr.u += m_u;
		}
		else {
			this.curr.u -= m_u;
		}
		this.speed.u += this.accel.u * ticks;
		var m_v = this.speed.v * ticks;
		if (m_v <= 0.0 || m_v >= Math.abs(this.dest.v - this.curr.v)) {
			this.curr.v = this.dest.v;
		}
		else if (this.dest.v > this.curr.v) {
			this.curr.v += m_v;
		}
		else {
			this.curr.v -= m_v;
		}
		this.speed.v += this.accel.v * ticks;
		var m_n = this.speed.n * ticks;
		if (m_n <= 0.0 || m_n >= Math.abs(this.dest.n - this.curr.n)) {
			this.curr.n = this.dest.n;
		}
		else if (this.dest.n > this.curr.n) {
			this.curr.n += m_n;
		}
		else {
			this.curr.n -= m_n;
		}
		this.speed.n += this.accel.n * ticks;
		changed= true;
	}
	
	if (this.curr.u == this.dest.u && this.curr.v == this.dest.v && this.curr.n == this.dest.n) {
		if (this.vstatus == 'F') { /* FLYING */
			// Assign new drift motion
			var drift = this.vliegershow.get_drift_vector(false, this.flypos.max_wander);

			this.dest.u += (this.dest.u < this.flypos.u ? 1 : -1) * drift.u;
			this.dest.v += (this.dest.v < this.flypos.v ? 1 : -1) * drift.v;
			this.dest.n += (this.dest.n < this.flypos.n ? 1 : -1) * drift.n;
			var k_u = (Math.random() * 1.5) + 0.5;
			var k_v = (Math.random() * 1.5) + 0.5;
			var k_n = (Math.random() * 1.5) + 0.5;
			this.set_speed(2.0, 2.0, 2.0, k_u, k_v, k_n);
			this.delay = parseInt(Math.ceil((20*Math.random() + 1)*1000)); // FIXME: hardcode delay
			this.is_moving = true;
		}
		else if (this.vstatus == 'A') { /* ATTENTION */
			var sp = (this.dest.u == this.flypos.u) ? 0.5: 1.0;
			var f = this.flypos.m_max;
			if (this.curr.v > this.flypos.v) {
				f *= -1;
			}
			this.dest.u = this.flypos.u + this.rise_vector.u * f;
			this.dest.v = this.flypos.v + this.rise_vector.v * f;
			this.dest.n = this.flypos.n + this.rise_vector.n * f;
			this.set_speed(sp, sp, sp, 0.9, 0.9, 0.9);
		}
		else {
			this.is_moving = false;
		}
	}
	
	// update sprite image
	if (this.vstatus == 'S') { /* SHOW */
		if (this.curr.n < this.fly.d && this.curr.image != this.kite.image) { 
			if (this.kite.loaded) {
				this.curr.image = this.kite.image;
				this.update_image = true;
			}
			else if (this.curr.spritenm != "kite") {
				this.curr.image = this.vliegershow.get_loading_img("kite");
				this.update_image = true;
			}
			if (this.spritenm != "kite") {
				this.curr.spritenm = "kite";
				this.curr.h = this.kite.h;
				this.curr.w = this.kite.w;
				changed = true;
			}
		}
	}
	else if (this.curr.spritenm == "kite" && this.curr.n > this.fly.d) {
		this.curr.spritenm = "fly";
		this.curr.image = this.fly.image;
		this.curr.h = this.fly.h;
		this.curr.w = this.fly.w;
		this.update_image = true;
		changed = true;
	}
	else if (this.curr.spritenm == "fly" && this.curr.image != this.fly.image && this.fly.loaded) {
		this.curr.image = this.fly.image;
		this.update_image = true;
	}
	// FIXME: do the same check for fall?
	
	if (changed || this.update_image) {
		this.display();
	}
	return changed; // FIXME: what if screen position/size didn't change (far objects) (note sprite change!)
}

Vlieger.prototype.check_hit = function (x,y,d)
{
	// check active
	if (this.vstatus == 'N' || this.vstatus == 'W' || this.cnvpos == null) {
		return -1;
	}

	// check extend
	if ((d >= 0 && d <=  this.curr.n) || x < this.cnvpos.x || x >= this.cnvpos.x + this.cnvpos.w || y < this.cnvpos.y || y >= this.cnvpos.y + this.cnvpos.h) {
		return -1;
	}

	// do inside edge test (works only for "normal" kites
	var uvn = this.vliegershow.canvas2space(x, y, this.curr.n, 0, 0);
	var du = uvn.u - this.curr.u;
	var dv = uvn.v - this.curr.v;
	if (this.vliegershow.inside_test(du, dv, this.curr.spritenm)) {
		return this.curr.n;
	}
	return -1;
}


function MyInterval ()
{
	this.call_obj = null;
	this.sleep_ms = 1000;
	this.timer = null;
	this.active = false;
	
	var self;
	
	function run () { 
		// closure
		if (self) {
			self.update();
		}
	}

	this.init = function (callobj, sleepms) { 
		// seperate to keep the parameters out of the run closure
		this.call_obj = callobj;
		this.sleep_ms = sleepms;
	}
	
	this.clear = function () {
		this.call_obj = null; // close closures :-)
		this.stop();
		this.sleep_ms = 1000;
	}

	this.start = function () {
		if (this.active) {
			return;
		}
		self = this;
		this.active = true;
		this.timer = setTimeout(run, this.sleep_ms);
	}

	this.stop = function () {
		this.active = false;
		self = null;
		if (this.timer) {
			clearTimeout(this.timer);
			this.timer = null;
		}
	}
	
	this.set_sleep = function (newsleep) {
		this.sleep_ms = newsleep;
	}

	this.update = function () {
		if (this.timer && this.call_obj) {
			this.call_obj.run(this.sleep_ms);
			this.timer = null;
		}
		if (this.active) {
			this.timer = setTimeout(run, this.sleep_ms); // FIXME: determine real sleeptime
		}
	}
}

var debug_active = false;
var debug_countdown = 5;

function debug_msg (msgstr)
{
	if (debug_active) {
		if (typeof(console) == "object") {
			console.log(msgstr);
		}
		else {
			alert(msgstr);
			debug_countdown -= 1;
			if (debug_countdown <= 0) {
				if (confirm("Disable debug messages?")) {
					debug_active = false;
				}
				else {
					debug_countdown = 5;
				}
			}
		}
	}
}