App = new function() {
	Document.addEvents({
		domready: function() {
			App.menu = $('#menu');
			App.search = $('#search-query');
			App.menus = new Hash();
			App.entries = $$('div.entry');
			if (App.first) {
				// select the right main button:
				var button = $('#' + App.list + '-button')
				if (button) button.select();
				var entry = $('#' + App.entry);
				if (entry) {
					entry.open(true);
					var offset = entry.getOffset();
					Window.scrollTo(0, offset.y - 68);
				}
				App.first = false;
			}
		},

		beforeupdate: function() {
			// Store old states of entries and menus:
			App.opened = App.entries.each(function(entry) {
				this[entry.getId()] = entry.opened;
			}, {});
			App.selected = App.menus.each(function(menu) {
				if (menu.current)
					this.push(menu.current.getId());
			}, []);
		},

		afterupdate: function() {
			// Now restore states
			App.entries.each(function(entry) {
				entry.open(App.opened[entry.getId()]);
			});
			App.selected.each(function(id) {
				$('#' + id).select();
			});
		},

		beginedit: function() {
			App.editing = true;
		},

		endedit: function() {
			App.editing = false;
		},

		mousedown: function(event) {
			// Do not allow dragging of images
			if (event.target.match('img'))
				event.stop();
		}
	});

	return {
		menu: null,
		search: null,
		menus: null,
		entries: null,
		first: true,
		editing: false
	};
};

MenuButton = HtmlElement.extend({
	_class: 'menu-button',

	initialize: function() {
		var id = this.id = this.getId().match(/^(.*)-button$/)[1];
		var match = id.match(/^(.*)-(pictures|texts|films)$/);
		if (match) {
			var entry = this.entry = $('#' + match[1]);
			this.menu = this.entry;
			this.index = 0;
			this.resourceType = match[2];
		} else {
			var parent = this.getParent();
			if (parent.match('#menu')) {
				this.entries = $$('div.entry', '#' + id);
				this.menu = parent;
				this.type = 'menu';
			} else {
				this.type = 'entry';
			}
		}
		if (this.menu)
			App.menus[this.menu.getId()] = this.menu;
		this.first = true;
		if (!this.resourceType)
			this.setup();
	},

	setup: function() {
		if (this.first) {
			this.first = false;
			if (this.resourceType) {
				// entries are fetched before everything else, beause App#domready 
				// is installed first. So entry.resourceTabs is set here:
				// Hide menu buttons if there is only one resource tab:
				this.modifyClass('hidden', this.entry.resourceTabs.length == 1);
				this.resourceTab = $('div.' + this.resourceType, this.entry);
				this.resourceTab.groupTabs = $$('div.group-tab', this.resourceTab);
			}
			this.over = $('#' + this.id + '-over');
			this.out = $('#' + this.id + '-out');
			this.addEvents({
				mouseover: function() {
					if (!this.menu || this.menu.current != this)
						this.toggle(true);
				},

				mouseout: function() {
					if (!this.menu || this.menu.current != this)
						this.toggle(false);
				},

				click: function(event) {
					if (this.menu) {
						this.select();
						event.stop();
					}
				}
			});
		}
	},

	select: function() {
		if (this.first)
			this.setup();
		if (this.menu) {
			// Execute this even when current is already set, to make sure
			// key events are handled again
			if (this.resourceTab) {
				this.resourceTab.groupTabs.each(function(tab) {
					tab.open(tab.index == this.index);
				}, this);
			}
			if (this.menu.current != this) {
				if (this.menu.current) {
					// If it's a main menu, store the current scroll offset,
					// so it can be restored when switching back.
					if (!this.resourceTab)
						this.menu.current.offset = Window.getScrollOffset();
					this.menu.current.toggle(false);
				}
				this.menu.current = this;
				this.toggle(true);
				if (this.resourceTab) {
					this.entry.resourceTabs.addClass('hidden');
					this.resourceTab.removeClass('hidden');
				} else {
					// Main menu button.
					// Hide all entries except current ones
					App.entries.addClass('hidden');
					this.entries.removeClass('hidden');
					// Restore scroll offset
					if (this.offset) {
						Window.scrollTo(this.offset);
						this.offset = null;
					}
					App.search.clear();
				}
			}
		}
	},

	toggle: function(over) {
		this.over.modifyClass('hidden', !over);
		this.out.modifyClass('hidden', over);
	}
});

ResourceTab = HtmlElement.extend(new function() {
	var current = null;

	Document.addEvent('keydown', function(event) {
		if (!App.editing && !App.search.focused && current && /^(left|right)$/.test(event.key)) {
			current.setImage(current.currentImage + (event.key == 'left' ? -1 : 1));
			event.stop();
		}
	});

	return {
		_class: 'group-tab',
		_lazy: true,

		initialize: function() {
			var id = this.getId();
			var match = id.match(/^((.*)-(pictures|texts|films))-(\d*)$/);
			var resourceTab = $('div.' + match[3], '#' + match[2]);
			this.index = match[4];
			this.button = $('#' + match[1] + '-button');
			this.button.setup();
			this.group = $('#' + id + '-group');
			var that = this;
			if (match[3] == 'pictures') {
				var pictures = $$('table.picture', this);
				if (pictures.length > 0) {
					pictures.addClass('hidden');
					pictures[0].removeClass('hidden');
					this.images = pictures.getElement('img');
					// Retrieve the source urls now, so they can be set when they need to
					// <img url=""..> does not trigger loading! src="" does
					this.sources = this.images.getProperty('url');
					if (pictures.length > 1)
						this.images.wrap('a', { href: '#' }).addEvent('click', function(event) {
							that.setImage(that.currentImage + 1);
							event.stop();
						});
					this.pictures = pictures;
					this.currentImage = 0;
				}
			}
			$('a', this.group).addEvent('click', function(event) {
				resourceTab.groupTabs.each(function(tab) {
					if (tab != that)
						tab.open(false);
				})
				that.open(true);
				event.stop();
			});
			this.open(false);
		},

		open: function(open) {
			if (open) {
				current = this;
				this.button.index = this.index;
				if (this.sources) {
					// Set sources now, to force loading. Set first immediately,
					// all others with a little delay.
					this.images[0].setProperty('src', this.sources[0]);
					(function() {
						this.images.each(function(img, i) {
							img.setProperty('src', this.sources[i]);
						}, this);
						this.sources = null;
					}).delay(10, this);
				}
			}
			if (this.group)
				this.group.modifyClass('selected', open);
			this.opened = open;
			this.modifyClass('hidden', !open);
		},

		setImage: function(index) {
			if (this.pictures) {
				if (index < 0) index = this.pictures.length - 1;
				else if (index >= this.pictures.length) index = 0;
				this.pictures[this.currentImage].addClass('hidden');
				this.currentImage = index;
				this.pictures[index].removeClass('hidden');
			}
		}
	};
});

ListEntry = HtmlElement.extend({
	_class: 'entry',
	_lazy: true,

	initialize: function() {
		this.title = $('div.title', this);
		this.content = $('div.content', this);
		this.opened = true;
		var that = this;
		this.title.addEvent('click', function(event) {
			that.open(!that.opened);
			event.stop();
		});
		this.first = true;
		this.open(false);
	},

	setup: function() {
		if (this.first) {
			this.first = false;
			this.resourceTabs = $$('div.resource-tab', this);
			$$('a.menu-button', this).each(function(button) {
				button.setup();
			});
			// Open again when clicking inside, for key events
			this.addEvent('click', function(event) {
				this.open(true);
			});
			// Click first resources button only when opening, since
			// images are loaded then.
			var button = $('div.resources .selected', this);
			if (button) button.select();
		}
	},

	setupSearch: function() {
		// Produce hidden elements used for search.
		// These shall not contain any tags and weird spaces,
		// since that seems to break XPath search...
		// Make sure to use all texts, if there are more columns: $$(...).join(' ')
		this.search = this.injectBottom('div', {
			text: ($('img', this.title).getProperty('title') + ' ' +
			 	$$('div.text', this).getText().join(' ')).replace(/\s+/gi, ' '),
			'class': 'search hidden'
		});
	},

	open: function(open) {
		this.opened = open;
		this.setHeight(open ? 'auto' : 13, true);
		this.content.modifyClass('hidden', !open);
		if (open) {
			// Select current again for key events
			if (this.current)
				this.current.select();
			if (this.first)
				this.setup();
		}
	}
});

SearchField = Input.extend({
	_class: 'search',
	_lazy: true,

	initialize: function() {
		this.addEvents({
			focus: function() {
				if (this.getValue() == this.placeholder)
					this.showPlaceholder(false);
				this.focused = true;
			},

			blur: function() {
				if (!this.getValue())
					this.showPlaceholder(true);
				this.focused = false;
			},

			keyup: function(event) {
				if (event.key.length == 1 || event.key == 'backspace') {
					if (this.timer) this.timer.clear();
					this.clearHighlights();
					if (this.getValue())
						this.timer = this.search.bind(this).delay(500);
					else this.search();
				}
			},

			keydown: function(event) {
				if (event.key == 'enter') {
					this.clearHighlights();
					if (this.timer) this.timer.clear();
					this.search();
					event.stop();
				}
			}
		});
		this.button = $('#' + this.getId() + '-button');
		if (this.button) {
			var that = this;
			this.button.addEvent('click', function(event) {
				that.search();
				event.stop();
			});
		}
		this.showPlaceholder(true);
		this.first = true;
	},

	showPlaceholder: function(set) {
		this.placeholder = App.searchTerms ? 'e.g. ' + App.searchTerms[Math.rand(0, App.searchTerms.length - 1)] : '';
		this.setValue(set ? this.placeholder : '');
		this.modifyClass('search-placeholder', set);
	},

	clearHighlights: function() {
		if (this.highlights) {
			var highlights = $$('span.highlight');
			var parents = [];
			highlights.each(function(highlight) {
				parents.push(highlight.getParent());
				highlight.getChildren().insertBefore(highlight);
				highlight.remove();
			});
			// Flatten text:
			parents.each(function(parent) {
				parent.setHtml(parent.getHtml());
			});
			this.highlights = false;
		}
	},

	search: function() {
		if (this.first) {
			// Setup hidden search nodes if we are searching for the first time now
			App.entries.each(function(entry) {
				entry.setupSearch();
			});
			this.first = false;
		}
		var value = this.getValue();
		// Clear first
		this.clearHighlights();
		if (this.current && !App.menu.current)
			this.current.select();
		this.current = null;
		// Close opened entries again
		if (this.opened) {
			this.opened.each(function(entry) {
				entry.open(false);
			});
			this.opened = null;
		}
		if (value && value.length >= 2 && value != this.placeholder) {
			this.current = App.menu.current;
			App.menu.current = null;
			if (this.current)
				this.current.toggle(false);
			App.entries.addClass('hidden');
			var entries = null;
			// Search for seperate words, merge results
			var terms = value.split(/\s/);
			terms.each(function(value) {
				var found = $$('div.search:contains-caseless(' + value + ')').getParent('div.entry');
				if (!entries) entries = found;
				else entries.append(found);
			});
			if (entries.length > 0) {
				// Escape terms for highlights RegExp:
				var highlights = new RegExp('(' + terms.map(function(term) {
					return term.escapeRegExp();
				}).join('|') + ')', 'gi')
				entries.each(function(entry) {
					$$('div.text', entry).each(function(text) {
						// Replace tags first, then place them back after hightlight replacement,
						// to avoid destruction of tags.
						var tags = [];
						text.setHtml(
							text.getHtml().replace(/<([^>]*)>/gi, function(tag) {
								return '###' + (tags.push(tag) - 1) + '###';
							})
							.replace(highlights, '<span class="highlight">$1</span>')
							.replace(/###(\d*)###/gi, function(placeholder, index) {
								return tags[index];
							})
						);
					});
				});
				this.opened = [];
				entries.each(function(entry) {
					if (!entry.opened) {
						entry.open(true);
						this.opened.push(entry);
					}
					entry.removeClass('hidden');
				}, this);
				this.highlights = true;
			}
			this.searched = true;
		}
	},

	clear: function() {
		if (!this.focused && this.getValue() != this.placeholder) {
			this.showPlaceholder(true);
			this.search();
		}
	}
});
