if (Prototype) {
	var Columnator = Class.create({
		initialize: function(container, columnWidth, columnGutter, columnHeight, options) {
			this.container = $(container);
			this.columnWidth = columnWidth;
			this.columnGutter = columnGutter;
			this.columnHeight = columnHeight;
			this.options = Object.extend({ resizeImages: true, specials: [] }, options);
			this.columns = $A();
			this.splitPoints = $A();
			
			Element.addMethods({ // This can probably be removed to the initialize function, where it will only get called once. // Moved.
				testSplitPoint: function (element, desiredHeight) {
					var offsetTop = element.cumulativeOffset().top - this.container.cumulativeOffset().top;
					var offsetBottom = offsetTop + element.getHeight();
					
					if (offsetTop < desiredHeight && offsetBottom > desiredHeight) {
						if (this.isElementSplitable(element)) {
							return this.findSplitPoint(element, desiredHeight);
						} else { // The element sitting on our split point is big and unsplittable.
							return { forceBreakAfter: element };
						}
					} else if (offsetTop == desiredHeight) { // Testing this out. // Turns out it was a good idea. Keeping it.
						return { forceBreakBefore: element };
					} else {
						return false;
					}
				}.bind(this)
			});
			
			// Create the first column and dump everything up into it.
			this.columns.push($(document.createElement('div')).addClassName('column').setStyle({ width: this.columnWidth, cssFloat: 'left' }));
			this.columnNumber = 0;
			
			var specials = this.processSpecials();
			
			if (specials[0]) {
				specials[0].each(function(special, index) {
					this.columns.first().appendChild(special.element.addClassName('column-layout-special').show());
				});
			}
			
			while (this.container.firstChild) {
				this.columns.first().appendChild(this.container.firstChild);
			}
			
			this.container.appendChild(this.columns.first());
			
			// Before we start calculating split points, there are a few things we need to cleanup.
			if (this.options.resizeImages) {
				this.container.select('img').invoke('setStyle', { maxWidth: this.columnWidth });
			}
			
			this.splitPoints.push(this.findSplitPoint(this.columns.first(), this.columnHeight));
			
			// Give ourselves plenty of room to work in.
			this.container.setStyle({ width: '100000px' });
			
			// Other columns now.
			while (this.splitPoints[this.columnNumber]) {
				// Create new column and increment columnNumber.
				this.columns.push($(document.createElement('div').addClassName('column').setStyle({ width: this.columnWidth, marginLeft: this.columnGutter, cssFloat: 'left' })));
				this.columnNumber++;
				
				// Add elements in reverse to the new column, stopping before we add the element to be split.
				var children = $A(this.columns[this.columnNumber - 1].childNodes);
				
				if (this.splitPoints[this.columnNumber - 1].forceBreakAfter) {
					while (children.last() && children.last() != this.splitPoints[this.columnNumber - 1].forceBreakAfter && !this.splitPoints[this.columnNumber - 1].forceBreakAfter.descendantOf(children.last())) { // We may be able to discard the descendant check, but I'm leaving it in, because it shouldn't slow us much, and I'm not entirely sure we don't need it.
						this.columns[this.columnNumber].insertBefore(children.pop(), this.columns[this.columnNumber].firstChild);
					}
				} else if (this.splitPoints[this.columnNumber - 1].forceBreakBefore) {
					while (children.last() && children.last() != this.splitPoints[this.columnNumber - 1].forceBreakBefore && !this.splitPoints[this.columnNumber - 1].forceBreakBefore.descendantOf(children.last())) { // See above.
						this.columns[this.columnNumber].insertBefore(children.pop(), this.columns[this.columnNumber].firstChild);
					}
					
					this.columns[this.columnNumber].insertBefore(children.pop(), this.columns[this.columnNumber].firstChild); // Once more, to add the element we wanted to break before.
				} else { // What will happen most often.
					while (children.last() && children.last() != this.splitPoints[this.columnNumber - 1] && !this.splitPoints[this.columnNumber - 1].descendantOf(children.last())) {
						this.columns[this.columnNumber].insertBefore(children.pop(), this.columns[this.columnNumber].firstChild);
					}
				}
				
				// Go ahead and add the new column to the container.
				this.container.appendChild(this.columns[this.columnNumber]);
				
				// Split the element into the new column.
				if (!this.splitPoints[this.columnNumber - 1].forceBreakAfter && !this.splitPoints[this.columnNumber - 1].forceBreakBefore && children.last()) {
					this.splitElement(children.last(), this.columns[this.columnNumber], this.splitPoints[this.columnNumber - 1], this.columnHeight);
					
					// Test for widows and orphans.
					var splitChild = children.last();
					var splitPiece = this.columns[this.columnNumber].firstDescendant();
					
					// Gather information
					var lineHeight = splitChild.getStyle('line-height');
					var lineHeightUnit = /[^\d\.]{2}/.exec(lineHeight) || [''];
					if (typeof(lineHeight) == 'string' && typeof(lineHeightUnit.index) != 'undefined') { lineHeight = lineHeight.slice(0, lineHeightUnit.index) * 1; }
					if (lineHeightUnit == 'em') {
						var unitTest = this.columns[this.columnNumber - 1].appendChild($(document.createElement('div')).setStyle({ width: '1em', height: '1em', display: 'none', position: 'absolute' }));
						var unitInPixels = unitTest.getWidth();
						this.container.removeChild(unitTest);
						
						lineHeight *= unitInPixels;
					}
					
					// First, test for widows.
					if (splitPiece.getHeight() < lineHeight * 2) {
						// Copy the contents of the last line of the previous column into our new column. It's tricky for the same reasons outlined above.
						// For now, we'll just run the column-splitting code with the desired column height = requested column height - height of one line.
						var newSplitPoint = this.findSplitPoint(this.columns[this.columnNumber - 1], this.columnHeight - lineHeight);
						
						this.splitElement(newSplitPoint, this.columns[this.columnNumber], newSplitPoint, this.columnHeight - lineHeight); // This call to this function makes it evident that perhaps, given the way this function is used, the third parameter is a bit redundant? It only gets used in one line of the routine, and must be the same as the first parameter in order for the function to run as expected... it may be slated for the trash.
						
						// Now, mend our mess.
						var mendChildren = this.columns[this.columnNumber].childElements();
						this.mendElement($(mendChildren[0]), $(mendChildren[1]), splitChild);
					}
					
					// Now, test for orphans. There can be an orphan _and_ a widow if we had a three-line paragraph, fixed the widow, and now have an orphan. We test for widows first so that, if fixing a widow creates an orphan, the orphan gets fixed too.
					if (splitChild.getHeight() < lineHeight * 2) { // Sometimes lines get padded out a little more than they should be. For instance, when different fonts are displayed inline.
						// Move the contents of the orphan over into the other part of the splitChild in the next column. It's a bit tricky, because any grandchildren that we split should be put back together again.
						// For now, we'll just dump everything in. We'll need to add 'naming' to our splitting function before we can actually merge split grandchildren.
						var orphanChildren = $A(splitChild.childNodes);
						
						while (orphanChildren.last()) {
							splitPiece.insertBefore(orphanChildren.pop(), splitPiece.firstChild);
						}
						
						// Again, mend our mess.
						var mendChildren = this.columns[this.columnNumber].childElements();
						this.mendElement($(mendChildren[0]), $(mendChildren[1]), splitChild);
					}
				}
				
				// Add specials to the top of the new column.
				if (specials[this.columnNumber]) {
					specials[this.columnNumber].reverse();
					specials[this.columnNumber].each(function(special, index) {
						this.columns[this.columnNumber].insertBefore(special.element.addClassName('column-layout-special').show(), this.columns[this.columnNumber].firstChild);
					}, this);
				}
				
				// Find split point in the new column.
				this.splitPoints[this.columnNumber] = this.findSplitPoint(this.columns[this.columnNumber], this.columnHeight);
			}
			
			this.setContainerDimensions();
			document.body.addClassName('has-columnated-content');
			//this.splitPoints.invoke('setStyle', { border: '1px solid red' });
			
			//document.body.appendChild($(document.createElement('div')).setStyle({ background: 'rgba(0, 255, 255, 0.5)', position: 'fixed', width: '100%', height: '600px', left: '0' }));
		},
		findSplitPoint: function(element, desiredHeight) {
			var splitPoint = element.childElements().invoke('testSplitPoint', desiredHeight).find(Prototype.K);
			
			//console.log(splitPoint);
			
			var backup = undefined;
			var offsetTop = element.cumulativeOffset().top - this.container.cumulativeOffset().top;
			var offsetBottom = offsetTop + element.getHeight();
			
			if (offsetTop < desiredHeight && offsetBottom > desiredHeight && this.isElementSplitable(element) && !element.hasClassName('column')) {
				backup = element;
			}
			
			return (splitPoint != undefined && typeof(splitPoint.forceBreakAfter) == "undefined") ? splitPoint : ((backup != undefined) ? backup : splitPoint); // If we fall back on backup and it's undefined, we use splitPoint: if it's forceBreakAfter, then we want that, and failing that, we have to return undefined anyway.
		},
		isElementSplitable: function(element) {
			if (!$(element).hasClassName('columnator-unsplittable') && element.tagName && $A(['p', 'ol', 'ul', 'dl', 'div', 'blockquote', 'span', 'strong', 'em']).include(element.tagName.toLowerCase()) && element.getStyle('position') == 'static' && (element.getStyle('float') == 'none' || element.hasClassName('column'))) {
				// Check to see if height changes when width significantly increases.
				var height = element.getHeight();
				var width = element.getWidth();
				var oldStyle = element.readAttribute('style'); // Cache old style, in case width was defined there.
				element.setStyle({ width: '99999px', display: 'block' });
				var multiline = (height > element.getHeight()); // Since it will scarcely have grown!
				
				element.writeAttribute({ 'style': (oldStyle) ? oldStyle : '' });
				
				return multiline;
			} else {
				return false;
			}
		},
		splitElement: function(element, newContainer, splitPoint, desiredHeight) {
			if (!$(element).hasChildNodes()) { return; } // Something's wrong, since this element obviously isn't splittable.
			
			var children = $A(element.childNodes);
			var height = desiredHeight - (element.cumulativeOffset().top - this.container.cumulativeOffset().top);
			
			var buffer = '', pointer = 0;
			
			if (height <= 0) {
				$(newContainer).insertBefore(element, newContainer.firstChild);
				
				return;
			}
			
			if (element.getHeight()) { // And it ought to be, or else why are we splitting it?
				var newElement = $(newContainer).insertBefore($(document.createElement(element.tagName)).writeAttribute({ 'class': element.readAttribute('class'), 'style': element.readAttribute('style') }).addClassName('split-element').addClassName('continuation-of-' + element.identify()), newContainer.firstChild);
			} else {
				return;
			}
			
			while (element.getHeight() > height || element.parentNode.getHeight() > (desiredHeight - (element.parentNode.cumulativeOffset().top - this.container.cumulativeOffset().top))) { // Not only do we need to reuce our element, but sometimes nested elements, even when reduced to their own appropriate size, do not sufficiently reduce that of their parents. It is possible that one needs to check grandparents, great-grandparents, etc., but we'll cross that bridge later with a separate checkig function which goes all the way back to the container = the ancestor.
				if (children.last().nodeType == Node.TEXT_NODE) {
					pointer = ((pointer = children.last().nodeValue.lastIndexOf(' ')) != -1) ? pointer : 0;
					buffer = children.last().nodeValue.substr(pointer) + ' ' + buffer;
					children.last().nodeValue = children.last().nodeValue.substring(0, pointer);
					
					if (!children.last().nodeValue) { // Write the buffer and flush, it's all been copied.
						newElement.insertBefore(document.createTextNode(buffer), newElement.firstChild);
						
						buffer = '';
						
						children.pop();
					}
				} else { // If it's not the splitting element or the splitting element's ancestor, just copy it over. Otherwise, hand it off to the splitter.
					if (splitPoint != children.last() && !splitPoint.descendantOf(children.last())) {
						newElement.insertBefore(children.last(), newElement.firstChild);
					} else {
						this.splitElement(children.last(), newElement, splitPoint, desiredHeight);
					}
					
					children.pop();
				}
			}
			
			// If we still have data in the buffer at this point, we need to write it (no need to flush, since it's about to be garbage-collected anyway).
			newElement.insertBefore(document.createTextNode(buffer), newElement.firstChild);
		},
		mendElement: function(element, sibling, splitChild) {
			if (!Object.isElement(element) || !Object.isElement(sibling)) { return; }
			
			var splitId = splitChild.identify();
			
			if ((element.hasClassName("continuation-of-" + splitId) && sibling.hasClassName("continuation-of-" + splitId)) || sibling.hasClassName("continuation-of-" + element.identify())) {
				while (sibling.firstChild) {
					element.appendChild(sibling.firstChild);
				}
				
				sibling.parentNode.removeChild(sibling); // Won't be removed simply because it is empty, but we don't need it hanging around.
				
				element.childElements().each(function(element, index) {
					this.mendElement(element, element.next(), splitChild.childElements().last());
				}, this);
			}
		},
		setContainerDimensions: function() {
			var columnWidth = this.columnWidth;
			var columnGutter = this.columnGutter;
			var columnHeight = this.columnHeight;
			var rightPadding = (typeof(this.options.extraRightPadding) != 'undefined') ? this.options.extraRightPadding : 0;
			
			widthUnit = /[^\d\.]{2}/.exec(columnWidth) || [''];
			gutterUnit = /[^\d\.]{2}/.exec(columnGutter) || [''];
			heightUnit = /[^\d\.]{2}/.exec(columnHeight) || [''];
			rightPaddingUnit = /[^\d\.]{2}/.exec(rightPadding) || [''];
			
			if (typeof(columnWidth) == 'string' && typeof(widthUnit.index) != 'undefined') { columnWidth = columnWidth.slice(0, widthUnit.index) * 1; }
			if (typeof(columnGutter) == 'string' && typeof(gutterUnit.index) != 'undefined') { columnGutter = columnGutter.slice(0, gutterUnit.index) * 1; }
			if (typeof(columnHeight) == 'string' && typeof(heightUnit.index) != 'undefined') { columnHeight = columnHeight.slice(0, heightUnit.index) * 1; }
			if (typeof(rightPadding) == 'string' && typeof(rightPaddingUnit.index) != 'undefined') { rightPadding = rightPadding.slice(0, rightPaddingUnit.index) * 1; }
			
			if ($A([widthUnit[0], gutterUnit[0], heightUnit[0], rightPaddingUnit[0]]).member('em')) {
				var unitTest = this.container.appendChild($(document.createElement('div')).setStyle({ width: '1em', height: '1em', display: 'none', position: 'absolute' }));
				var unitInPixels = unitTest.getWidth();
				this.container.removeChild(unitTest);
				
				if (widthUnit[0] == 'em') {
					columnWidth *= unitInPixels;
				}
				
				if (gutterUnit[0] == 'em') {
					columnGutter *= unitInPixels;
				}
				
				if (heightUnit[0] == 'em') {
					columnHeight *= unitInPixels;
				}
				
				if (rightPaddingUnit[0] == 'em') {
					rightPadding *= unitInPixels;
				}
			}
			
			var containerWidth = (columnWidth * (this.columnNumber + 1)) + (columnGutter * this.columnNumber) + rightPadding;
			
			this.container.setStyle({ width: containerWidth + 'px', height: columnHeight + 'px' });
		},
		processSpecials: function() {
			var specials = $A();
			
			this.options.specials.each(function(special, index) {
				if (!specials[special.column - 1]) { specials[special.column - 1] = $A(); }
				
				special.element = $(special.element);
				
				special.element.hide();
				
				specials[special.column - 1].push(special);
			});
			
			return specials;
		}
	});
}