function isElementInViewport( elm ) {
	var pos = document.viewport.getScrollOffsets();
	var viewportSize = document.viewport.getDimensions();
	var elmSize = elm.getDimensions();
	var top = pos.top;
	var left = pos.left;
	var right = left + viewportSize.width;
	var bottom = top + viewportSize.height;
	var elmLeft = parseFloat(elm.getStyle('left') || '0');
    var elmTop  = parseFloat(elm.getStyle('top')  || '0');
    var elmRight = elmLeft + elmSize.width;
    var elmBottom = elmTop + elmSize.height;
    
	return ( elmRight >= left && elmLeft < right && elmBottom >= top && elmTop < bottom );		
}

/*
 * Ordnet HTML-Elemente auf der Browserflaeche an
 */
var Positioner = Class.create({
	initialize: function( form, options, elements ) {
		this.opt = {
			scaleX: 100,
			scaleY: 100,
			area: 'fotoarea',
			animated: false,
			width: 'full', // 'full', 'viewport', Breite in Pixel (zB 30)
			elementsPerForm: 0, //
			arrangeOnlyNotPositioned: false,
			xOffset: 0
		};
		Object.extend(this.opt,options || {});
		this.elements = elements || $$('.moveable');
		if (this.opt.arrangeOnlyNotPositioned) {
			this.elements = this.elements.findAll( function(elm) { return elm.match('.notpositioned') });
		}

		//this.format = this.parent.getWidth() / this.parent.getHeight();
		this.area = $(this.opt.area);
		this.areaDimensions = this.area.getDimensions();
		this.setBounds();
		this.n = this.elements.length;
		this.form = form;
	},
	
	setBounds: function() {
		this.top = 0;
		this.left = 0;
		switch ( this.opt.width ) {
			case 'full':
				var dim = Object.clone(this.areaDimensions);
				dim.width -= this.opt.xOffset;
			break;
			case 'viewport':
				var dim = document.viewport.getDimensions();
			break;
			default:
				var dim = {
					height: this.areaDimensions.height,
					width: this.opt.width
				}
		}

		this.width = dim.width;
		this.height = dim.height;
		
		this.left += this.opt.xOffset;
		var neededAreaWidth = this.left + this.width;
		
		if ( this.areaDimensions.width < neededAreaWidth ) {
			this.area.setStyle( { width: neededAreaWidth + "px" } );
			this.areaDimensions.width = neededAreaWidth;
		}		
		
		/*this.left += this.opt.xOffset;
		if ((this.left + this.width) > this.areaDimensions.width) {	
			this.width = this.areaDimensions.width - this.left;
		}*/
	},
	
	moveToNextDisplaySection: function() {
		var left = this.left + this.width;
		if ( left + this.width <= this.areaDimensions.width) {
			this.left = left;
		}
	},
	
	getDisplaySectionCount: function() {
		return Math.floor( (this.areaDimensions.width - this.left) / this.width);
	},
	
	getScaledBounds: function() {
		var width = this.width * this.opt.scaleX / 100;
		var height = this.height * this.opt.scaleY / 100;
		var left = this.left + (this.width - width) / 2;
		var top = this.top + (this.height - height) / 2;
		return {
			width: width,
			height: height,
			left: left,
			top: top
		}
	},
	
	getPositions: function() {
		return this.form.get(this.n);
	},

	arrange: function() {
		var sectionCount = this.getDisplaySectionCount();
		var elementsPerGroup = Math.round(this.n / sectionCount);
		var elmGroups = this.elements.inGroupsOf(elementsPerGroup);
		elmGroups.each( function( group ) {
			var positions = this.form.get(elementsPerGroup);
			var bounds = this.getScaledBounds();			
			group.each( function( elm, index ) {
				if (elm == null || positions.length < index+1) return;
				elm.removeClassName("notpositioned");
				var w = elm.getWidth();
				var h = elm.getHeight();
				var x = Math.round(bounds.left + positions[index].x * bounds.width - w/2);
				var y = Math.round(bounds.top + positions[index].y * bounds.height - h/2);
				var left = parseInt(elm.getStyle('left') || '0');
			    var top  = parseInt(elm.getStyle('top')  || '0');		
				if ( x!=left || y!=top ) {
					if ( this.opt.animated )
						new Effect.Move(elm, { x: x, y: y, mode: 'absolute', duration: 0.2, queue: "end"});
					else
						elm.setStyle({
							left: x + "px",
							top: y + "px"
						});
				}
			}.bind(this));
			this.moveToNextDisplaySection();
		}.bind(this));
		this.area.setStyle({ width: (this.left + this.width) + "px" });
	}
});
 
var MultiForm = Class.create({
	initialize: function( forms ) {
		this.forms = forms;
		this.opt = {
			gap: 0.35
		}
	},
	get: function(n) {
		var result = new Array();
		var forms = new Array();

		var npf = Math.floor(n / this.forms.length);
		this.forms.each( function(f) {
			var positions = f.get(npf);
			positions = positions.inGroupsOf(npf).first();
			forms.push( positions );
		});
		result = this.setFormsParallel( forms );

		return result;		
	},
	
	setFormsParallel: function( forms ) {
		var result = new Array();
		var n = forms.length;
		var totalWidth = this.getTotalWidth( forms ) + n * this.opt.gap;
		var usedWidth = 0;
		forms.each( function( f ) {
			var offset = usedWidth / totalWidth;
			var ranges = this.getFormRanges(f);
			var width = Math.abs( ranges.max - ranges.min ) + this.opt.gap;
			f.each( function( pos )  {
				var x = offset + (pos.x - ranges.min + this.opt.gap / 2) / totalWidth;
				var y = pos.y;
				result.push( { x: x, y: y } );
			}.bind(this));
			usedWidth += width;
		}.bind(this));
		return result;
	},
	
	getTotalWidth: function( forms ) {
		var width = 0;
		forms.each( function(f) {
			width += this.getFormWidth(f);
		}.bind(this));
		return width;
	},
	
	getFormRanges: function(form) {
		if (form.length == 0) return 0;
		var min = form[0].x; var max = form[0].x;
		form.each( function(pos) {
			if (pos.x < min) min = pos.x;
			if (pos.x > max) max = pos.x;
		});
		return { max: max, min: min };
	},
	getFormWidth: function(form) {
		var ranges = this.getFormRanges(form);
		return Math.abs( ranges.max - ranges.min );
	}
});

var RelativeMultiForm = Class.create(MultiForm,{
	get: function(n) {
		var result = new Array();
		var forms = new Array();
	
		var total = this.getTotalSize();
		
		this.forms.each( function(f) {
			var npf = Math.round(n * f.getAbsoluteSize() / total);
			var positions = f.get(npf);
			positions = positions.inGroupsOf(npf).first();
			forms.push( positions );
		});
		result = this.setFormsParallel( forms );
	
		return result;		
	},

	getTotalSize: function() {
		var lengths = this.forms.collect( function(f) {
			return f.getAbsoluteSize();
		});
		return lengths.inject(0, function(acc, i) { return acc + i; });
	}
	
});

var TextPositioner = Class.create({
	initialize: function( text, options ) {
		this.text = text;
		this.opt = Object.extend({
			maxLetters: 10,
			widthPerLetter: null
		}, options || {});
		this.letters = this.getLetters();
		var form = new RelativeMultiForm( this.letters );

		if (this.opt.widthPerLetter != null) {
			this.opt.width = this.letters.length * this.opt.widthPerLetter; 
		}
		this.positioner = new Positioner( form, this.opt );
	},
	getLetters: function() {
		var result = new Array();
		for ( var i=0; i<this.text.length; i++ ) {
			var c = this.text.charAt(i).toUpperCase();
			if ( c.match( /[A-Z]/ ) ) {
				result.push( new LetterPositionerForm(c) );
				if (result.length >= this.opt.maxLetters) break;
			}
		}
		return result;
	},
	arrange: function() {
		this.positioner.arrange();
	}
});

var SequentialPositioner = Class.create(Positioner,{
	/*initialize: function( $super, forms, options, elements ) {
		
	}*/
	arrange: function() {
	
	}
});

var PositionerForm = Class.create({
	/*
	 * Abstrakte Methode
	 * Gibt die Positionierung der n Elemente im Einheitsquadrat an
	 * @return [{x: x1,y: y1}, {x: x2,y: y2} ...]
	 */
	get: function(n) {}
});

var GridPositionerForm = Class.create(PositionerForm,{
	get: function(n) {
		var result = new Array();
		var a = Math.ceil(Math.sqrt(n));
		var step = 1/a;
		var start = step/2;
		for (var y=start; y<=1; y+=step) {
			for (var x=start; x<=1; x+=step) {
				result.push( { x: x, y: y } );
			}
		}
		return result;
	}
});

var CirclePositionerForm = Class.create(PositionerForm,{
	get: function(n) {
		var result = new Array();
		var max = 2 * Math.PI;
		var step = max / n;
		for (var i=0; i<max; i+=step) {
			var x = 0.5 + Math.cos(i) * 0.45;
			var y = 0.5 + Math.sin(i) * 0.45;
			result.push( { x: x, y: y } );
		}
		return result;
	}
});

var SinePositionerForm = Class.create(PositionerForm,{
	get: function(n) {
		var result = new Array();
		var max = 2 * Math.PI;
		var step = max / n;
		for (var i=0; i<=max; i+=step) {
			var x = i / max;
			var y = 0.5 - Math.sin(i) * 0.45;
			result.push( { x: x, y: y } );
		}
		return result;
	}
});

var DoubleSinePositionerForm = Class.create(PositionerForm,{
	get: function(n) {
		var result = new Array();
		var max = 2 * Math.PI;
		var step = max / n * 2;
		for (var i=0; i<=max; i+=step) {
			var x = i / max;
			var dy = Math.sin(i) * 0.45;
			
			var y = 0.5 - dy;
			result.push( { x: x, y: y } );
			
			y = 0.5 + dy;
			result.push( { x: x, y: y } );
		}
		return result;
	}
});

var XPositionerForm = Class.create(PositionerForm,{
	get: function(n) {
		var result = new Array();
		var step = 1 / (n / 2);
		for (var j=0; j<=1; j++) {
			for (var i=0; i<=1; i+=step) {
				var x = i;
				var y = Math.abs(j-i);
				result.push( { x: x, y: y } );
			}
		}
		return result;
	}
});

var SpiralPositionerForm = Class.create(PositionerForm,{
	get: function(n) {
		var result = new Array();
		var max = 4 * Math.PI;
		var step = max / n;
		var radius = 0;
		var max_radius = 0.45;
		var radius_step = max_radius / n;
		for (var i=0; i<=max; i+=step) {
			radius += radius_step;
			var x = 0.5 + Math.cos(i) * radius;
			var y = 0.5 + Math.sin(i) * radius;
			result.push( { x: x, y: y } );
		}
		return result;
	}
});

var RandomPositionerForm = Class.create(PositionerForm,{
	get: function(n) {
		var result = new Array();
		for (var i=1; i<=n; i++) {
			var x = Math.random();
			var y = Math.random();
			result.push( { x: x, y: y } );
		}
		return result;
	}
});

var LetterPositionerForm = Class.create(PositionerForm,{
	initialize: function( letter ) {
		this.letter = getAlphabet().getLetter(letter);
	},
	getAbsoluteSize: function() {
		return this.letter.getLength();
	},
	get: function(n) {
		return this.letter.getPoints(n);
	}
});


var LetterView = Class.create({
	initialize: function( letters ) {
		this.letters = letters;
		this.squareSize = 150;
	},
	show: function() {
		this.letters.each( function( letter ) {
			var elm = new Element( "div", { 'class': 'square', width: this.squareSize, height: this.squareSize } );
			var cv = new Element( "canvas", { width: this.squareSize, height: this.squareSize } );
			elm.appendChild( cv );
			document.body.appendChild( elm );
			var ctx = cv.getContext("2d");
			letter.draw( ctx, this.squareSize );
			this.drawPoints( ctx, letter.getPointsRelative( 10 ) );
		}.bind(this));
	},
	drawPoints: function( ctx, points ) {
		var size = this.squareSize;
		points.each( function(p) {
			ctx.beginPath();
			ctx.fillStyle = "rgb(255,0,0)";
			ctx.arc( p.x * size, p.y * size, 4, 0, 360, false );
			ctx.fill();
		}.bind(this));
	}
});

var alphabet = null;
function getAlphabet() {
	if ( alphabet == null ) alphabet = new Alphabet();
	return alphabet;
}
var Alphabet = Class.create({
	initialize: function() {
		var letters = new Array();
		
		letters.push( new Letter( "A", [
			new Line( { x: 0, y: 1 }, { x: 0.5, y: 0 } ),
			new Line( { x: 0.5, y: 0 }, { x: 1, y: 1 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.75, y: 0.5 } )
		] ) );

		letters.push( new Letter( "B", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.25, y: 0 }, { x: 0.5, y: 0 } ),
			new Line( { x: 0.25, y: 1 }, { x: 0.5, y: 1 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.5, y: 0.5 } ),
			new Arc( { x: 0.5, y: 0.25 }, 0.25, 270, 90 ),
			new Arc( { x: 0.5, y: 0.75 }, 0.25, 270, 90 )
		] ) );

		letters.push( new Letter( "C", [
			new Arc( { x: 0.5, y: 0.5 }, 0.5, 45, 315 )
		] ) );
		
		letters.push( new Letter( "D", [
			new Line( { x: 0, y: 0 }, { x: 0, y: 1 } ),
			new Line( { x: 0, y: 0 }, { x: 0.5, y: 0 } ),
			new Line( { x: 0, y: 1 }, { x: 0.5, y: 1 } ),
			new Arc( { x: 0.5, y: 0.5 }, 0.5, 270, 90 )
		] ) );

		letters.push( new Letter( "E", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.25, y: 0 }, { x: 0.75, y: 0 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.65, y: 0.5 } ),
			new Line( { x: 0.25, y: 1 }, { x: 0.75, y: 1 } )
		] ) );

		letters.push( new Letter( "F", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.25, y: 0 }, { x: 0.75, y: 0 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.65, y: 0.5 } ),
		] ) );
		
		letters.push( new Letter( "G", [
			new Arc( { x: 0.5, y: 0.5 }, 0.5, 180, 315 ),
			new Arc( { x: 0.45, y: 0.5 }, 0.45, 0, 180 ),
			new Line( { x: 0.9, y: 0.5 }, { x: 0.5, y: 0.5 } ),
			new Line( { x: 0.9, y: 0.5 }, { x: 0.9, y: 1 } ),
		] ) );
		
		letters.push( new Letter( "H", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.75, y: 0 }, { x: 0.75, y: 1 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.75, y: 0.5 } ),
		] ) );	
		
		letters.push( new Letter( "I", [
			new Line( { x: 0.5, y: 0 }, { x: 0.5, y: 1 } )
		] ) );
		
		letters.push( new Letter( "J", [
			new Line( { x: 0.75, y: 0 }, { x: 0.75, y: 0.75 } ),
			new Arc( { x: 0.5, y: 0.75 }, 0.25, 0, 180 ),
		] ) );
		
		letters.push( new Letter( "K", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.75, y: 0 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.75, y: 1 } )
		] ) );
		
		letters.push( new Letter( "L", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.25, y: 1 }, { x: 0.75, y: 1 } )
		] ) );
		
		letters.push( new Letter( "M", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.75, y: 0 }, { x: 0.75, y: 1 } ),
			new Line( { x: 0.75, y: 0 }, { x: 0.5, y: 0.5 } ),
			new Line( { x: 0.25, y: 0 }, { x: 0.5, y: 0.5 } )
		] ) );
		
		letters.push( new Letter( "N", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.75, y: 0 }, { x: 0.75, y: 1 } ),
			new Line( { x: 0.25, y: 0 }, { x: 0.75, y: 1 } )
		] ) );
		
		letters.push( new Letter( "O", [
			new Arc( { x: 0.5, y: 0.5 }, 0.5, 0, 360 ),
		] ) );
		
		letters.push( new Letter( "P", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.25, y: 0 }, { x: 0.5, y: 0 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.5, y: 0.5 } ),
			new Arc( { x: 0.5, y: 0.25 }, 0.25, 270, 90 )
		] ) );

		letters.push( new Letter( "Q", [
			new Arc( { x: 0.5, y: 0.5 }, 0.5, 0, 360 ),
			new Line( { x: 0.65, y: 0.65 }, { x: 1, y: 1 } )
		] ) );

		letters.push( new Letter( "R", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.25, y: 0 }, { x: 0.5, y: 0 } ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.5, y: 0.5 } ),
			new Arc( { x: 0.5, y: 0.25 }, 0.25, 270, 90 ),
			new Line( { x: 0.25, y: 0.5 }, { x: 0.75, y: 1 } )
		] ) );
		
		letters.push( new Letter( "S", [
			new Arc( { x: 0.6, y: 0.25 }, 0.25, 135, 315 ),
			new Arc( { x: 0.4, y: 0.75 }, 0.25, 315, 135 ),
			new Line( { x: 0.42322, y: 0.42678 }, { x: 0.57678, y: 0.57322 } )
		] ) );
		
		letters.push( new Letter( "T", [
			new Line( { x: 0.5, y: 0 }, { x: 0.5, y: 1 } ),
			new Line( { x: 0, y: 0 }, { x: 1, y: 0 } )
		] ) );
		
		letters.push( new Letter( "U", [
			new Line( { x: 0.25, y: 0 }, { x: 0.25, y: 0.75 } ),
			new Line( { x: 0.75, y: 0 }, { x: 0.75, y: 0.75 } ),
			new Arc( { x: 0.5, y: 0.75 }, 0.25, 0, 180 ),
		] ) );
		
		letters.push( new Letter( "V", [
			new Line( { x: 0.25, y: 0 }, { x: 0.5, y: 1 } ),
			new Line( { x: 0.5, y: 1 }, { x: 0.75, y: 0 } )
		] ) );
		
		letters.push( new Letter( "W", [
			new Line( { x: 0, y: 0 }, { x: 0.25, y: 1 } ),
			new Line( { x: 0.75, y: 1 }, { x: 1, y: 0 } ),
			new Line( { x: 0.75, y: 1 }, { x: 0.5, y: 0.5 } ),
			new Line( { x: 0.25, y: 1 }, { x: 0.5, y: 0.5 } )
		] ) );
		
		letters.push( new Letter( "X", [
			new Line( { x: 0.25, y: 0 }, { x: 0.75, y: 1 } ),
			new Line( { x: 0.25, y: 1 }, { x: 0.75, y: 0 } )
		] ) );	
		
		letters.push( new Letter( "Y", [
			new Line( { x: 0.25, y: 0 }, { x: 0.5, y: 0.5 } ),
			new Line( { x: 0.5, y: 0.5 }, { x: 0.75, y: 0 } ),
			new Line( { x: 0.5, y: 0.5 }, { x: 0.5, y: 1} )
		] ) );
		
		letters.push( new Letter( "Z", [
			new Line( { x: 0.25, y: 0 }, { x: 0.75, y: 0 } ),
			new Line( { x: 0.25, y: 1 }, { x: 0.75, y: 0 } ),
			new Line( { x: 0.25, y: 1 }, { x: 0.75, y: 1} )
		] ) );
		
		this.letters = letters;
	},
	getLetter: function( chr ) {
		return this.letters.find( function(letter) {
			return ( letter.name == chr );
		});
	}
});
var Letter = Class.create({
	initialize: function( name, shapes ) {
		this.name = name;
		this.shapes = shapes;
	},
	draw: function( ctx, size ) {
		this.shapes.each( function( shape ) {
			shape.draw( ctx, size );
		});
	},
	getPointsRelative: function( npp ) {
		var len = this.getLength();
		return this.getPoints( Math.round(npp * len) );
	},
	getPoints: function( n ) {
		var lengths = this.shapes.collect( function(shp) {
			return shp.getLength();
		});
		var total = lengths.inject(0, function(acc, i) { return acc + i; });
		var result = new Array();
		this.shapes.each( function( shp, i ) {
			var anz = Math.round(n * lengths[i] / total);
			if ( i == this.shapes.length-1 ) {
				anz = n - result.length;
			}
			result = result.concat( shp.getPoints( anz ) );
		}.bind(this));
		return result;
	},
	getLength: function() {
		var lengths = this.shapes.collect( function(shape) {
return shape.getLength();
		});
		return lengths.inject(0, function(acc, i) { return acc + i; });	
	}
});

var Shape = Class.create({
	getLength: function() { return 0 },
	
	// gibt n gleichmaessig auf der Form verteilte Punkte zurueck
	getPoints: function(n) { return new Array() }
});

var Line = Class.create(Shape,{
	initialize: function( p1, p2 ) {
		this.p1 = p1;
		this.p2 = p2;
		this.dx = this.p2.x - this.p1.x;
		this.dy = this.p2.y - this.p1.y;
	},
	draw: function( ctx, size ) {
		ctx.beginPath();
		ctx.moveTo( this.p1.x * size, this.p1.y * size );
		ctx.lineTo( this.p2.x * size, this.p2.y * size );
		ctx.stroke();
	},
	getPoints: function( n ) {
		var len = this.getLength();
		var result = new Array();
		var step = len / n;
		for ( var i=step/2; i<len; i+=step ) {
			var rl = i / len;
			var x = this.p1.x + this.dx * rl;
			var y = this.p1.y + this.dy * rl;
			result.push( { x: x, y: y } );
		}
		return result;
	},
	getLength: function() {
		return Math.sqrt( this.dx*this.dx + this.dy*this.dy );
	}
});

var Arc = Class.create(Shape,{
	initialize: function( p, r, startAngle, endAngle ) {
		this.p = p;
		this.r = r;
		this.startAngle = startAngle;
		this.endAngle = endAngle;
		this.startRad = this.getRad(startAngle);
		this.endRad = this.getRad(endAngle);
	},
	getPoints: function( n ) {
		var len = Math.abs( this.endRad - this.startRad );
		var step = len / n;
		var result = new Array();
		for ( var i=step/2; i<len; i+=step ) {
			var a = this.startRad + i;
			var x = this.p.x + this.r * Math.cos( a );
			var y = this.p.y + this.r * Math.sin( a );
			result.push( { x: x, y: y } );
		}
		return result;
	},
	getRad: function( deg ) {
		return (Math.PI/180) * deg;
	},
	draw: function( ctx, size ) {
		ctx.beginPath();
		ctx.arc( this.p.x * size, this.p.y * size, this.r * size, this.startRad, this.endRad, false );
		ctx.stroke();
	},
	getLength: function() {
		var angle = Math.abs( this.endRad - this.startRad );
		return this.r * angle;
	}
});

