App = new function() {
	$document.addEvents({
		domready: function() {
			App.menu = $('#menu');
			App.search = $('#query');
			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.setScrollOffset(0, offset.y - 68);
				}
				App.first = false;
			}
			// Install open / close buttons
			$$('#expand a').addEvent('click', function(event) {
				var open = this.getId() == 'open';
				App.noFilms = true;
				App.entries.each(function(entry) {
					if (!entry.hasClass('hidden'))
						entry.open(open)
				});
				App.noFilms = false;
				event.stop();
			});
		},

		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: menu.getId(), type: menu.current.type });
			}, []);
		},

		afterupdate: function() {
			// Now restore states
			App.entries.each(function(entry) {
				entry.open(App.opened[entry.getId()]);
			});
			App.selected.each(function(values) {
				$('.' + values.type + '-button', '#' + values.menu).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: new Hash(),
		entries: null,
		first: true,
		editing: false,
		noFilms: false
	};
};

MenuButton = HtmlElement.extend({
	_class: 'menu-button',

	initialize: function() {
		var parent = this.getParent();
		var match = this.getClass().match(/menu-button (\w*)\-button/);
		if (match) {
			this.type = match[1];
			if (parent.match('#menu')) {
				// Menu button
				this.entries = $$('div.entry', '#' + this.type);
				this.menu = parent;
			} else if (parent.match('.menu')) {
				// Resources menu button
				this.index = 0; // TODO: Needed?
				this.menu = this.entry = parent.getParent('.entry');
				this.resource = true;
			}
			if (this.menu)
				App.menus[this.menu.getId()] = this.menu;
		} else {
			// Entry button
			this.type = 'entry';
		}
		this.first = true;
		if (!this.resource)
			this.setup();
	},

	setup: function() {
		if (this.first) {
			this.first = false;
			if (this.resource) {
				// 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.type, this.entry);
				this.resourceTab.groupTabs = $$('div.group-tab', this.resourceTab);
			}
			this.addEvents({
				click: function(event) {
					if (this.menu) {
						this.select();
						// Clear search if we're in main menu (project / information)
						if (!this.resource)
							App.search.clear();
						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.setSelected(false);
				}
				this.menu.current = this;
				this.setSelected(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.setScrollOffset(this.offset);
						this.offset = null;
					}
				}
			}
		}
	},

	setSelected: function(selected) {
		this.modifyClass('selected', selected);
	}
});

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), true);
			event.stop();
		}
	});

	return {
		_class: 'group-tab',
		_lazy: true,

		initialize: function() {
			var resourceTab = this.getParent();
			this.type = resourceTab.getClass().split(/\s/)[1];
			this.index = resourceTab.getChildren().indexOf(this);
			// Parent of resourceTab is .resources that also contains .menu
			this.button = $('.' + this.type + '-button', resourceTab.getParent());
			this.button.setup();
			this.group = $$('div.group', resourceTab)[this.index];
			var that = this;
			$('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);
			this.first = true;
		},

		setup: function() {
			if (/pictures|films/.test(this.type)) {
				var media = $$('img,embed', this);
				var fixTables = Browser.TRIDENT && Browser.VERSION < 8;
				var hasNumbers = media.length > 1;
				// Wrap media in a div and add caption from title and numbering.
				media.each(function(medium, i) {
					var div = medium.wrap('div', { className: 'medium' });
					var title = medium.getProperty('title');
					if (title || hasNumbers) {
						div.injectBottom('div', [
							['div', { className: 'caption', html: title && title.replace(/\n/g, '<br>') }],
							hasNumbers && ['div', { className: 'number', text: (i + 1) + ' / ' + media.length }]
						]);
					}
					// Replace div with table on IE for proper centering
					if (fixTables)
						div = div.replaceWith('table', { className: 'medium' },
							[ 'tbody', [ 'tr', [ 'td', div.removeChildren() ] ] ]);
					this[i] = div;
				});
				if (this.type == 'pictures' && media.length > 0) {
					media.addClass('hidden');
					media[0].removeClass('hidden');
					this.images = media.getElement('img');
					// Retrieve the source urls now, so they can be set when they need to
					// <img url=""..> does not trigger loading! src="" does
					if (media.length > 1) {
						var that = this;
						this.images.wrap('a', { href: '#' }).addEvent('click', function(event) {
							current = that;
							that.setImage(that.currentImage + 1, true);
							event.stop();
						});
					}
					this.pictures = media;
					this.currentImage = 0;
					this.setImage(0, false);
				}
			}
		},

		open: function(open) {
			if (open) {
				current = this;
				this.button.index = this.index;
				if (this.first) {
					this.setup.delay(1, this);
					this.first = false;
				}
			}
			if (this.group)
				this.group.modifyClass('selected', open);
			this.opened = open;
			this.modifyClass('hidden', !open);
		},

		setImage: function(index, load) {
			if (this.pictures) {
				function loadImage(image) {
					image.setProperty('src', image.getProperty('url'));
				}
				if (index < 0) index = this.pictures.length - 1;
				else if (index >= this.pictures.length) index = 0;
				// Set sources now, to force loading.
				if (load && !this.loaded) {
					this.images.each(loadImage);
					this.loaded = true;
				} else {
					// Make sure the first is loaded immediately.
					// All others once user starts to browse, using load=true.
					loadImage(this.images[index]);
				}
				if (this.pictures.length > 1) {
					this.pictures[this.currentImage].addClass('hidden');
					this.pictures[index].removeClass('hidden');
				}
				this.currentImage = index;
			}
		}
	};
});

ListEntry = HtmlElement.extend({
	_class: 'entry',
	_lazy: true,

	initialize: function() {
		this.title = $('a.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);
			// Select images instead of movies if we're opening loads at once
			if (App.noFilms && button && button.getText() == 'FILM') {
				button.removeClass('selected');
				button = $('div.resources .menu-button', 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: (this.title.getText() + ' ' +
				$$('div.text', this).getText().join(' ') /* +
				$$('img,embed', this).getProperty('title').join(' ')*/).replace(/\s+/gi, ' '),
			'class': 'search hidden'
		});
	},

	open: function(open) {
		this.opened = open;
		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.inject({
	initialize: function() {
		// TODO: Solve this automatically in Bootstrap
		this.base.apply(this, arguments);
		this.addEvents({
			search: this.search,

			keydown: function(event) {
				if (event.key == 'enter')
					event.stop();
			}
		});
		this.first = true;
	},

	clearHighlights: function() {
		if (this.highlights) {
			var highlights = $$('span.highlight');
			var parents = highlights.each(function(highlight) {
				this.push(highlight.getParent());
				highlight.getChildNodes().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 >= 3 && value != this.placeholder) {
			this.current = App.menu.current;
			App.menu.current = null;
			if (this.current)
				this.current.setSelected(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 && 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;
			}
		}
	}
});
