// DOM "renderer" for Verlet physics engine
// by Andreas Blixt <andreasblixt@msn.com> 2007
// Free for use. Please include this header.

DOMRenderer = function (world) {
	// Make sure a world was passed.
	if (!(world instanceof World)) throw "First argument must be an instance of World.";

	// Store a reference to the specified world.
	this.World = world;

	// Get the body element.
	var body = document.getElementsByTagName("body")[0];

	// Update boundaries when window is resized.
	var e = window.onresize, thiz = this;
	window.onresize = typeof e == "function" ? function () { e(); thiz.UpdateBoundaries.call(thiz); } : function () { thiz.UpdateBoundaries.call(thiz); };

	// Update boundaries now.
	this.UpdateBoundaries();

	// Create an array which will hold a list of elements that have been attached.
	this.Attached = [];

	// Create an array which will hold a list of elements that are obstacles.
	this.Obstacles = [];

	// Hijack the update function of the world.
	var update = world.Update;
	world.Update = function () { thiz.PreUpdate.call(thiz); update.call(world); thiz.Render.call(thiz); };
};

// Static function which returns the position of an element.
DOMRenderer.ElementPosition = function (e) {
	// Calculate the position of the element.
	var x = 0, y = 0;
	if (e.offsetParent) {
		x = e.offsetLeft;
		y = e.offsetTop;

		while (e = e.offsetParent) {
			x += e.offsetLeft;
			y += e.offsetTop;
		}
	}

	// Return the position.
	return { X: x, Y: y };
};

DOMRenderer.prototype = {
	// A reference to the world which will be rendered.
	World: null,

	// A list of elements that have been attached.
	Attached: null,

	// A list of elements that are obstacles.
	Obstacles: null,

	// Makes a new atom at the position of the specified element and attaches the element to it.
	AtomizeElement: function (element, group) {
		// Make sure a group was passed.
		if (!(group instanceof Group)) throw "Second argument must be an instance of Group.";

		// Get the position of the element.
		var pos = DOMRenderer.ElementPosition(element);

		// Create a new atom.
		var atom = group.CreateAtom(pos.X + element.offsetWidth / 2, pos.Y + element.offsetHeight / 2, Math.ceil(element.offsetHeight / 2));

		// Attach the element to the atom.
		this.AttachElement(element, atom);

		// Return the atom.
		return atom;
	},

	// Attaches a DOM element to an atom.
	AttachElement: function (element, atom) {
		// Make sure an atom was passed.
		if (!(atom instanceof Atom)) throw "Second argument must be an instance of Atom.";

		// Add element to list of attached elements.
		this.Attached.push({ atom: atom, element: element });
	},

	// Makes an obstacle from an element.
	MakeElementObstacle: function (element) {
		// Get the position of the element.
		var pos = DOMRenderer.ElementPosition(element);

		// Create the obstacle.
		var obstacle = this.World.CreateObstacle(pos.X, pos.Y, element.offsetWidth, element.offsetHeight);

		// Add element to list of obstacle elements.
		this.Obstacles.push({ element: element, obstacle: obstacle });

		// Return the obstacle.
		return obstacle;
	},

	// Prepares the world before it is updated.
	PreUpdate: function () {
		// Loop through all the elements which have been turned into obstacles.
		for (var i in this.Obstacles) {
			var o = this.Obstacles[i];

			// Get the position of the element.
			var pos = DOMRenderer.ElementPosition(o.element);

			// Update the obstacle.
			o.obstacle.X = pos.X;
			o.obstacle.Y = pos.Y;
			o.obstacle.Width = o.element.offsetWidth;
			o.obstacle.Height = o.element.offsetHeight;
		}
	},

	// Renders the world.
	Render: function () {
		// Loop through all the elements which have been attached.
		for (var i in this.Attached) {
			var atom = this.Attached[i].atom;
			var element = this.Attached[i].element;

			// Position the element.
			with (element.style) {
				position = "absolute";
				left = Math.floor(atom.CurrentX - element.offsetWidth / 2) + "px";
				top = Math.floor(atom.CurrentY - element.offsetHeight / 2) + "px";
			}
		}
	},

	// Updates the boundaries of the world this renderer is attached to.
	UpdateBoundaries: function () {
		// Get the window dimensions.
		var winWidth, winHeight;
		if (self.innerHeight) {
			winWidth = self.innerWidth;
			winHeight = self.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight) {
			winWidth = document.documentElement.clientWidth;
			winHeight = document.documentElement.clientHeight;
		} else if (document.body) {
			winWidth = document.body.clientWidth;
			winHeight = document.body.clientHeight;
		} else {
			winWidth = 0;
			winHeight = 0;
		}

		// Get the page dimensions.
		var pageWidth, pageHeight;
		if (document.body.scrollHeight > document.body.offsetHeight) {
			pageWidth = document.body.scrollWidth;
			pageHeight = document.body.scrollHeight;
		} else {
			pageWidth = document.body.offsetWidth;
			pageHeight = document.body.offsetHeight;
		}

		// Set the world boundaries.
		this.World.BoundaryMinX = 0;
		this.World.BoundaryMaxX = Math.max(pageWidth, winWidth);
		this.World.BoundaryMinY = 0;
		this.World.BoundaryMaxY = Math.max(pageHeight, winHeight) - 4;
	}
};
