// the general strategy is to compute all positions on startup, 
// so we only have to do the animation when a state is changed
// of course positions have to be recomputed when the window size changes
//
// one problem is the handling of elements that are positioned relative to others
//		the problem here is we don't know the order at which we pull objects from the database  ->  well, then we just have to add an additional sort clause...!?
//
// another issue is the alignment of images vs. text and radial vs. cartesian
//		when positioning elements radially, it is important to use their centers as reference points, otherwise elements might "hang off" to the right bottom, in particular large images
//		this could also be adjusted manually of course, but still looks weird this way when moving elements. It's also counter-intuitive when adding new elements or doing regular grids
//		but when aligning elements to the borders (i.e. cartesian) this means that we first have to subtract half their widths and heights, then add them again
//		another problem occurs when loading HTML dynamically into elements so we can't know the width/height upon constructing the positions
		
function arrangeAllSpirals () {
//	alert ("arrange all");
	for (iSpiral in spirals) {
		spirals[iSpiral].arrangeAll ();
	}
}

function drawAllSpirals () {
//	alert ("draw all");
	for (iSpiral in spirals) {
		spirals[iSpiral].draw (currentState, centerX, centerY);
	}
}



//-----------------------------------------------------------------------------------------
function cartPos () {
	this.x = 0;
	this.y = 0;
	this.w = 0;
	this.h = 0;
}

//-----------------------------------------------------------------------------------------
function spiralPos 	(minR, maxR, startPhi, phiInc, incFactor, w, h, reference, x, y) {
	if (minR != null) 		this.minR = minR;
	if (maxR != null) 		this.maxR = maxR;
	if (startPhi != null)	this.startPhi = startPhi;
	if (phiInc != null)		this.phiInc = phiInc;
	if (incFactor != null)	this.incFactor = incFactor;
	if (reference != null)	this.reference = reference;		else	this.reference = "radial";

	this.absCartPos = new cartPos ();
	this.relCartPos = new cartPos ();
	if (x < 1)	{		this.absCartPos.x = 0;	this.relCartPos.x = x;		}	else 	{	this.absCartPos.x = x;	this.relCartPos.x = 0;	}
	if (y < 1)	{		this.absCartPos.y = 0;	this.relCartPos.y = y;		}	else 	{	this.absCartPos.y = y;	this.relCartPos.y = 0;	}
	if (w < 1)	{		this.absCartPos.w = 0;	this.relCartPos.w = w;		}	else 	{	this.absCartPos.w = w;	this.relCartPos.w = 0;	}
	if (h < 1)	{		this.absCartPos.h = 0;	this.relCartPos.h = h;		}	else 	{	this.absCartPos.h = h;	this.relCartPos.h = 0;	}
}

//-----------------------------------------------------------------------------------------
function spiral (num, nDots, handle) {
	this.positions = new Array();
	this.num = num;
	this.handle = handle;
	this.nDots = nDots;
	
	this.centerX = 0;
	this.centerY = 0;
	this.startPos = 0;
	this.animPos = 0;
	this.endPos = 0;
	this.e = $(handle+'_0');
	this.image = $('i'+handle+'_0')
	if (this.e.style.visibility=='hidden') {	this.hide();	}	else 	{	this.show();	}
	
	this.d_minR = 0; 
	this.d_maxR = 0; 
	this.d_startPhi = 0; 
	this.d_phiInc = 0; 
	this.d_incFactor = 0;
}

// ----- add new entry to position array
spiral.prototype.newPos = function (iPos, minR, maxR, startPhi, phiInc, incFactor, w, h, reference, x, y) {
	this.positions[iPos] = new spiralPos (minR, maxR, startPhi, phiInc, incFactor, w, h, reference, x, y);
}
// ----- update entry in position array
spiral.prototype.updatePos = function (iPos, minR, maxR, startPhi, phiInc, incFactor, x, y) {
	this.positions[iPos].minR = minR;
	this.positions[iPos].maxR = maxR;
	this.positions[iPos].startPhi = startPhi;
	this.positions[iPos].phiInc = phiInc;
	this.positions[iPos].incFactor = incFactor;
}
// ----- make visible
spiral.prototype.show = function () {
	this.visibility = 1;
}
// ----- hide it
spiral.prototype.hide = function () {
	this.visibility = 0;
}
// ----- update the display (with array index)
spiral.prototype.draw = function (iPos) {
	pos = this.positions [iPos];
	this.drawPos (pos, 0);
}
// ----- update the display (with explicit position)
spiral.prototype.drawPos = function (pos, animFlag) {	// flag just for selective debugging
	if (pos == null) {
		pos = this.positions[STATE_OFFS];
	}	
		
	phi = pos.startPhi;
	phiInc = pos.phiInc;

	for (iSpiral=0; iSpiral<this.num; iSpiral++) {
		r = this.spiralRadius (iSpiral / this.num, pos.minR, pos.maxR);

		if ((this.handle == 'P') && ((currentState == STATE_PROJ_POSTS) || (targetState == STATE_PROJ_POSTS)))	{
			index = projMap [iSpiral];
		//	alert (index);
		}
		else {
			index = iSpiral;
		}
		
		this.setRadialPos (index, phi, r); 
		
		// set dots
	/*	if (drawDotsFlag) {
			for (iDot=0, phiDot = phi; iDot<this.nDots; iDot++) {
				phiDot += (phiInc / this.nDots);
				r = spiralRadius ((iSpiral + iDot/this.nDots) / this.num, pos.minR, pos.maxR);
				setRadialPos ('d'+this.handle+'_'+iSpiral+'_'+iDot, phiDot, r, 1);
			}
		}				
	*/
		phi += phiInc;
		phiInc *= pos.incFactor;
	}
	this.setSize (pos.absCartPos.w, pos.absCartPos.h);
}
// ----- set new position based on polar coordinates
spiral.prototype.setRadialPos = function (index, phi, r) {
	Pt = polar4cart (phi, r, centerX, centerY);

	Pt[0] -= (this.getWidth  (index, 0)  / 2);
	Pt[1] -= (this.getHeight (index, 0) / 2);

	e=$(this.handle+'_'+index);
	e.style.left = Pt[0]+'px';	
	e.style.top  = Pt[1]+'px';
}
// ----- set new size of element linked to spiral
spiral.prototype.setSize = function (w, h, iPos) {	// if negative parameter skip that part
	if (w > 0) {
		if (this.image)		this.image.width = w;
		this.e.style.width  = w + 'px';		
	}
	if (h > 0) {
		if (this.image)		this.image.height = h;
		this.e.style.height = h + 'px';
	}
}

// ===================== GETTERS
// ----- get height
spiral.prototype.getHeight = function (index, iPos) {
	if (this.image) 					return this.image.height;
	
	if (iPos == 0)	iPos = targetState;
	pos = this.positions[iPos];

	if ((pos) && (pos.absCartPos.h))	return pos.absCartPos.h;

	e=$("h"+this.handle+"_"+index);
	if (e)								return e.offsetTop;

	return 0;
}
// ----- get width
spiral.prototype.getWidth = function (index, iPos) {
	if (this.image) 					return this.image.width;
	
	if (iPos == 0)	iPos = targetState;
	pos = this.positions[iPos];

	if ((pos) && (pos.absCartPos.w))	return pos.absCartPos.w;

	e=$("w"+this.handle+"_"+index);
	if (e)								return e.offsetLeft;
}
// ----- get radius
spiral.prototype.spiralRadius = function (index, minR, maxR) {
	r = minR + index * (maxR-minR);
	return (r);
}
// ------ get phi and radius 
spiral.prototype.getPhi = function (state) {
	return (this.positions[state].startPhi);
}
spiral.prototype.getRadius = function (state) {
	return (this.positions[state].minR);
}
// ----- get cartesian coordinates
spiral.prototype.getCart = function (state) {
	var p,r;
	p = this.getPhi (state);				
	r = this.getRadius (state);
	return (polar4cart (p, r, centerX, centerY));
}


// =============================================== arrange all elements
spiral.prototype.arrangeAll = function () {
	var v, xd, yd, Pt;
	
	for (iPos = firstState; iPos <= lastState; iPos++) {
		pos = this.positions[iPos];
		if (pos != null) {
			if (pos.reference != null) {
				if (pos.reference == "radial")	
					continue;

				mainRef = pos.reference.substring (0,2);
				extraRef = pos.reference.substring (2);
							
				// ------------- adjust widths of elements - mainly applying relative sizes to new/initial window size to get absolute sizes
				w = pos.relCartPos.w;
				if (w > 0) {
					pos.absCartPos.w = windowWidth * w;
				}
				else if (w < 0) {
					pos.absCartPos.w = windowWidth + w;
				}
				h = pos.relCartPos.h;
				if (h > 0) {
					if (extraRef == "aspectH")
						pos.absCartPos.h = windowWidth * w * h;
					else
						pos.absCartPos.h = windowHeight * h;
				}
				else if (h < 0) {
					pos.absCartPos.h = windowHeight + h;
				}
				this.setSize (pos.absCartPos.w, pos.absCartPos.h, iPos);


				// ----- adapt relative positions to new window width
				x = pos.relCartPos.x;
				if (x > 0) {
					pos.absCartPos.x = windowWidth * x;
				}
				y = pos.relCartPos.y;
				if (y != 0) {
					pos.absCartPos.y = windowHeight * y;
				}
				
				// ----- start from absolute positions
				xd = pos.absCartPos.x;
				yd = pos.absCartPos.y;

				// ----- reference to center by adding half its size
				xd += this.getWidth  (0, iPos) / 2;
				yd += this.getHeight (0, iPos) / 2;


				// check if there's additional offsets related to the reference
				if ((extraRef != '') && (extraRef != 'aspectH')) {
					xyRef = extraRef.split(";");
					xRef = xyRef[0];	yRef = xyRef[1];
					
					if (xRef != '') {
						eRefImage	= $('i'+xRef+'_0');	// assuming element is an image
						PtRefImage	= spirals[xRef].getCart(iPos);		// have to get the cartesian position this way, because the referenced element might have been defined radially
						
						switch (mainRef) {
							case "TL": 			// top left
							case "BL": 			// bottom left
							case "TR": 			// top right
							case "BR": 			// bottom right
								xd += eRefImage.width;
								break;
							case "RB": 			// right of the reference element, alligned at bottom
								xd += PtRefImage[0];
								xd += eRefImage.width / 2;
								break;
						}
					}
					if (yRef != '') {
						eRefImage	= $('i'+yRef+'_0');	// assuming element is an image
						PtRefImage	= spirals[yRef].getCart(iPos);		// have to get the cartesian position this way, because the referenced element might have been defined radially
						
						switch (mainRef) {
							case "TL":
							case "BL":
							case "TR":
							case "BR":
								yd += eRefImage.height;
								break;
							case "RB":
								yd += PtRefImage[1];
								yd += eRefImage.height / 2;
								yd -= this.getHeight (0, iPos);// / 2;
								break;
						}
					}
				}
				
				
				switch (mainRef) {
					case "TL":
						v = cart4polar (globalMargin + xd, globalMargin + yd, centerX, centerY);
						break;
					case "BL":
						v = cart4polar (globalMargin + xd, windowHeight - yd - globalMargin, centerX, centerY);
						break;
					case "TR":
						v = cart4polar (windowWidth - globalMargin - xd, globalMargin + yd, centerX, centerY);
						break;
					case "BR":
						v = cart4polar (windowWidth - globalMargin - xd, windowHeight - yd - globalMargin, centerX, centerY);
						break;
						
					case "RB":
						v = cart4polar (xd, yd, centerX, centerY);
						break;
						
					default:
						alert ("danger danger");
						return;
				}

				this.updatePos (iPos,	v[1], v[1], v[0],	0.0, 1.0);
			}
		}
	}
}

// ================================== animation
// ----- animate the spirals
spiral.prototype.animate = function (iStartPos, iEndPos, steps, centerX, centerY) {
	if (this.positions[iEndPos] == null) 	iEndPos = STATE_OFFS;
	if (this.positions[iStartPos] == null) 	iStartPos = STATE_OFFS;
	
	this.startPos = this.positions[iStartPos];
	this.endPos   = this.positions[iEndPos];
	
	this.d_minR = (this.endPos.minR - this.startPos.minR) / steps;
	this.d_maxR = (this.endPos.maxR - this.startPos.maxR) / steps;
	this.d_startPhi = (this.endPos.startPhi - this.startPos.startPhi) / steps;
	this.d_phiInc = Math.pow ((this.endPos.phiInc / this.startPos.phiInc), (1 / steps));
	this.d_incFactor = (this.endPos.incFactor - this.startPos.incFactor) / steps;
	
	animSteps = steps;			// handled globally
	this.centerX = centerX;
	this.centerY = centerY;
	this.animPos = new spiralPos (	this.startPos.minR, 		this.startPos.maxR, 		this.startPos.startPhi, 	// !!! too many new positions, allocate ONCE (or maybe not?)
									this.startPos.phiInc, 		this.startPos.incFactor);
}
// ----- perform next animation step
spiral.prototype.animStep = function () {
	this.animPos.minR 		+= this.d_minR;
	this.animPos.maxR 		+= this.d_maxR;	
	this.animPos.startPhi 	+= this.d_startPhi;
	this.animPos.phiInc 	*= this.d_phiInc;
	this.animPos.incFactor	+= this.d_incFactor;
	this.drawPos (this.animPos, 1);
}

//  global callback (don't know how to call schedule an object function)
var animSteps = 0;
var animFunc = 0;
var animEndFunc = 0;
var animTime = 0;
var animSpeed = 50;
var movingFlag = 0;

function animate (func, endFunc, time) {
	animFunc = func;
	animEndFunc = endFunc;
	animTime = time;
	window.setTimeout ("animStep ();", time);
}

function animSpArray () {
	for (var iSpiral in spirals) {
		spirals[iSpiral].animStep ();
	}
}	
function animateAll (endFunc, time) {
	animFunc = "animSpArray();";
	animEndFunc = endFunc;
	animTime = time;
	window.setTimeout ("animStep ();", time);
}

function animStep () {
	window.setTimeout (animFunc, 1);
	animSteps--;
	if (animSteps > 0) {
		window.setTimeout ("animStep ();", animTime);
	}
	else {
		window.setTimeout (animEndFunc, animTime); 
		movingFlag = 0;		// a tad too early, but can do it at a central location
	}
}

function animateArray (state, callback, endStepSpeed) {
	movingFlag = 1;
	
	targetState = state;
	showDots (0);	// animating dots is too slow			   
	for (var iSpiral in spirals) {
		spirals[iSpiral].animate	(currentState, targetState, defaultSteps, centerX, centerY);
	}
	
	mc = "midCall(\"" + callback + "\"," + endStepSpeed + ");";
	animateAll (mc, animSpeed);//		iteration over spArray[i].animStep doesn't work
	currentState = targetState;
	showState ();
}
function midCall (callback, endStepSpeed) {
	window.setTimeout (callback, endStepSpeed);		// extra call to the scheduler to force screen redraw (?)
}	


// ############################################### math #################################################
function polar4cart (phi, r, cx, cy) {
	p = phi/180.0 * Math.PI;
	return [cx + Math.sin(p)*r*cx,   cy + Math.cos(p)*r*cy - 40];
//	return [cx + Math.sin(p)*r*cx,   cy + Math.cos(p)*r*cy];
}
function cart4polar (x, y, cx, cy) {
	var lx = (x-cx) / cx;
	var ly = (y-cy+40) / cy;	// what's this 40?
//	var ly = (y-cy) / cy;
	return [Math.atan2(lx,ly) / Math.PI * 180.0, 
			 Math.sqrt(lx*lx+ly*ly)]; 
}

