function createCodeMirror(place, options, source) {
	return new CodeMirror(place, Hash.create({}, {
		lineNumbers: true,
		matchBrackets: true,
		indentUnit: 4,
		tabMode: 'shift',
		value: source.getText().replace(/\t/gi, '    ').match(
			// Remove first & last empty line
			/^\s*?[\n\r]?([\u0000-\uffff]*?)[\n\r]?\s*?$/)[1]
	}, options));
}

Code = HtmlElement.extend({
	_class: 'code',

	initialize: function() {
		// Only format this element if it is visible, otherwise wait until
		// it is made visible and then call format() manually.
		if (this.getBounds().height != 0) {
			this.format();
		}
	},

	format: function() {
		var that = this;
		var start = this.getProperty('start');
		var highlight = this.getProperty('highlight');
		var editor = createCodeMirror(function(el) {
			that.replaceWith(el);
		}, {
			lineNumbers: !this.hasParent('.resource-text'),
			firstLineNumber: (start || 1).toInt(),
			readOnly: true
		}, this);
		if (highlight) {
			var highlights = highlight.split(',');
			for (var i = 0, l = highlights.length; i < l; i++) {
				var highlight = highlights[i].split('-');
				var hlStart = highlight[0].toInt() - 1;
				var hlEnd = highlight.length == 2
						? highlight[1].toInt() - 1 : hlStart;
				if (start) {
					hlStart -= start - 1;
					hlEnd -= start - 1;
				}
				for (var j = hlStart; j <= hlEnd; j++) {
					editor.setLineClass(j, 'highlight');
				}
			}
		}
		return editor;
	}
});


PaperScript = HtmlElement.extend({
	_class: 'paperscript',

	initialize: function() {
		var script = $('script', this);
		if (script) {
			var canvas = $('canvas', this);
			var button = $('.button', this);
			if (!button)
				return;
			var source = this.injectBottom('div', {
				className: 'source hidden',
			});
			var width, height;
			var editor = null;
			var hasBorders = true;
			function resize() {
				if (canvas.hasClass('hidden')) {
					// Can't get correct dimensions from hidden canvas,
					// so calculate again.
					var size = $window.getScrollSize();
					var offset = source.getOffset();
					width = size.width - offset.x;
					height = size.height - offset.y;
				} else {
					width = canvas.getWidth();
					height = canvas.getHeight();
				}
				source.set({
					width: width - (hasBorders ? 2 : 1),
					height: height - (hasBorders ? 2 : 0)
				});
			}
			if (canvas.getProperty('resize')) {
				$window.addEvents({
					resize: resize
				});
				hasBorders = false;
				source.setStyles({
					borderWidth: '0 0 0 1px'
				});
			} else {
				this.addClass('small');
			}
			button.addEvents({
				click: function(event) {
					var show = source.hasClass('hidden');
					resize();
					canvas.modifyClass('hidden', show);
					source.modifyClass('hidden', !show);
					this.setText(show ? 'Run' : 'Source');
					if (show && !editor) {
						editor = createCodeMirror(source.$, {
							/*
							onKeyEvent: function(editor, event) {
								event = new DomEvent(event);
								if (event.type == 'keydown') {
									var pos = editor.getCursor();
									pos.ch += 4;
									editor.setCursor(pos);
									event.stop();
								}
							},
							*/
						}, script);
					} else if (!show) {
						var scope = paper.PaperScope.get(script.$);
						if (scope) {
							// Update script to edited version
							script.setText(editor.getValue());
							// Clear scope first, then evaluate a new script.
							scope.clear();
							scope.evaluate(script.$);
						}
					}
					this.setStyle('right',
						$('.CodeMirror', source).getScrollSize().height > height // Remove padding
							? 24 : 8);
					event.stop();
				},

				mousedown: function(event) {
					event.stop();
				}
			})
		}
	}
});

$document.addEvent('domready', function() {
	var h = unescape(document.location.hash);
	if (h) scrollToElement(h.substring(1));
});

var lastMemberId = null;
function toggleMember(id, scrollTo) {
	if (lastMemberId && lastMemberId != id) {
		var prevId = lastMemberId;
		lastMemberId = null;
		toggleMember(prevId);
	}
	var link = $('#' + id + '-link');
	if (link) {
		var desc = $('#' + id + '-description');
		var v = !link.hasClass('hidden');
		lastMemberId = v && id;
		link.modifyClass('hidden', v);
		desc.modifyClass('hidden', !v);
		if (!desc.editor && v) {
			desc.editor = $$('pre.code', desc).each(function(code) {
				code.format();
			});
		}
		if (scrollTo)
			scrollToMember(id);
		return false;
	}
	return true;
}

function scrollToElement(id) {
	var e = $('#' + id);
	if (e) {
		var offs = e.getOffset();
		$window.setScrollOffset(offs);
		if (e.hasClass('member'))
			toggleMember(id);
	} else {
		document.location.hash = id;
	}
}

function togglePackage(id, def) {
	var e = $('#package-' + id);
	if (e) {
		var v = !e.hasClass('hidden');
		e.modifyClass('hidden', v);
		var img = document.images['arrow-' + id];
		if (img) img.src = img.src.replace(/open|close/, v ? 'close' : 'open');
	}
	return false;
}

function toggleThumbnail(id, over) {
	$('#' + id).modifyClass('hidden', over);
	$('#' + id + '_over').modifyClass('hidden', !over);
}