

/**
 * Control that maintains a list of DOM nodes that can be browsed using a page navigation element.
 */
var PagingControl = Class.create();
PagingControl.prototype = {
	
	/** Array of pages: DOM nodes to be shown/hidden */
	pages:null,
	/** DOM node containing the navigation items */
	navigationContainer:null,
	/** Array of DOM nodes used for page navigation */
	navigationItems:null,
	/** DOM node used as "previous" button */
	previousButton:null,
	/** DOM node used as "next" button */
	nextButton:null,
	/** Index of current page. First page has index 0. */
	currentPageIndex:0,
	/** Element to scroll to on page change. */
	scrollToElement:null,
	
	/**
	 * @constructor
	 *
	 * @param pages                 Array of DOM nodes to be shown/hidden when paging.
	 * @param navigationContainer   <ul> or <ol> node where this control can add <li> elements for navigating.
	 * @param previousButton        DOM node to use for "previous" button
	 * @param nextButton            DOM node to use for "next" button
	 */
	initialize:function( pages, navigationContainer, previousButton, nextButton ) {
		this.pages = $A(pages);
		this.navigationContainer = $(navigationContainer);
		this.previousButton = previousButton;
		this.nextButton = nextButton;
		this.buildNavigation();
		this.updateNavigationContainerWidth();
		this.initEvents();
	},
	
	unload:function() {
		this.navigationContainer = null;
		this.pages = null;
		this.previousButton = null;
		this.nextButton = null;
	},
	
	/**
	 * Build navigation DOM structure if it is not explicitly provided in the HTML.
	 */
	buildNavigation:function() {
		if( !this.hasNavigation() ) {
			for( var i = 0; i < this.pages.length; i++ ) {
				var a = Builder.node( "a", { href:"#" }, i+1 );
				var li = $(Builder.node( "li", [ a ] ));
				if( i == 0 ) { li.addClassName( "current" ) }
				this.navigationContainer.appendChild( li );
			}
		}

		this.navigationContainer.getElementsBySelector("a").each( function(el,i) { el.pageIndex = i } );
		this.navigationItems = $A(this.navigationContainer.getElementsBySelector("li"));
		
	},
	
	initEvents:function() {
		Event.observe( this.navigationContainer, "click", this.handleNavigationClick.bindAsEventListener( this ) );
		Event.observe( this.navigationContainer, "mousedown", function(e) { Event.stop(e) } );
		if( this.previousButton != null ) {
			Event.observe( this.previousButton, "click", this.handleButtonClick.bindAsEventListener(this) );
		}
		if( this.nextButton != null ) {
			Event.observe( this.nextButton, "click", this.handleButtonClick.bindAsEventListener(this) );
		}
	},
	
	hasNavigation:function() {
		return this.navigationContainer.down("li") != null;
	},
	
	/**
	 * Because of floating, the navigation container needs an explicit with. 
	 * This needs to be done at runtime.
	 */
	updateNavigationContainerWidth:function() {
		var width = 20; /* Start with som padding */
		this.navigationItems.each( function( item ) { width += item.getWidth() } );
		this.navigationContainer.setStyle({width:width+'px'});
	},
	
	handleNavigationClick:function( event ) {
		Event.stop( event );
		var t = $(Event.element(event));
		if( t.nodeName == "A" ) {
			this.gotoPage( t.pageIndex );
		}
	},
	
	handleButtonClick:function( event ) {
		Event.stop(event);
		var b = Event.element(event);
		if( b == this.previousButton ) {
			this.gotoPage( this.currentPageIndex - 1, false );
		} else if( b == this.nextButton ) {
			this.gotoPage( this.currentPageIndex + 1, false );
		}
	},
	
	gotoPage:function( index, restartIfNeccesary ) {
		index = Number( index );
		if( restartIfNeccesary && index >= this.pages.length ) { index = 0; }
		if( index >= 0 && index < this.pages.length ) {
			this.pages.each( function( page ) { page.removeClassName("current") } );
			this.pages[index].addClassName("current");
			this.currentPageIndex = index;
			this.updateNavigationItems();
			this.updateButtons();
			this.updateScroll();
		}
	},
	
	gotoNextPage:function( restartIfNeccesary ) {
		this.gotoPage( this.currentPageIndex + 1, restartIfNeccesary );
	},
	
	getCurrentPageIndex:function() {
		return this.currentPageIndex;
	},
	
	/** Update which navigation item is marked as current */
	updateNavigationItems:function() {
		this.navigationItems.each( function( li ) { li.removeClassName( "current" ) } );
		this.navigationItems[this.currentPageIndex].addClassName( "current" );
	},
	
	/** Update disabled/enabled state of next and previous buttons */
	updateButtons:function() {
		if( this.previousButton == null || this.nextButton == null ) {
			return;
		}
		if( this.currentPageIndex > 0 ) {
			this.previousButton.removeClassName("disabledPrevious");
		} else {
			this.previousButton.addClassName("disabledPrevious");
		}
		if( this.currentPageIndex < this.pages.length - 1 ) {
			this.nextButton.removeClassName("disabledNext");
		} else {
			this.nextButton.addClassName("disabledNext");
		}
	},
	
	/**
	 * Set which element to scroll to on page change. Set null if scrolling is not wanted.
	 */
	setScrollToElement:function( element ) {
		this.scrollToElement = element;
	},
	
	/** Scroll to top of preset element, if any */
	updateScroll:function() {
		if( this.scrollToElement != null ) {
			$(this.scrollToElement).scrollTo();
		}
	}
}





/**
 * Replace a standard dropdown <select> tag with a multicolumn one that can be styled with script.
 * If there is a <select> inside the provided target, it will be used as data source.
 * If a list is provided as constructor argument, it will be used as a data source instead.
 * The target will be cleared of all contents, and the new DOM structure will be placed in it.
 */
var DropdownControl = Class.create();
DropdownControl.prototype = {

	/** Target element for this widget */
	target:null,
	/** Array of value objects as { name, value } */
	data:[],
	/** Index of the initially selected item */
	selectedIndex:-1,
	
	/** Button element */
	button:null,
	/** List element */
	list:null,
	listInitialized:false,
	/** Flag indicating visibility */
	visible:false,
	
	/** List offset from button */
	listOffsetX : -20,
	listOffsetY :  35,
	
	changeListeners:null,
	toggleListeners:null,
	
	itemsPerColumn:5,
	
	/** Constructor */
	initialize:function( target, data, itemsPerColumn ) {
		this.target = target;
		this.changeListeners = [];
		this.toggleListeners = [];
		if( data != null ) {
			this.data = data;
			this.selectedIndex = -1;
		} else {
			this.importSelectList();
		}
		this.itemsPerColumn = itemsPerColumn;
		this.removeOldContents();
		this.buildDropdown();
		this.initEvents();
		this.hide();
	},
	
	unload:function() {
		this.data = null;
		this.button = null;
		this.list = null;
		this.changeListeners = null;
		this.toggleListeners = null;
		this.target = null;
	},
	
	/** Get list of element from <select> element in target */
	importSelectList:function() {
		var select = this.target.down("select");
		if( select == null ) {
			this.selectedIndex = -1;
		} else {
			this.selectedIndex = select.selectedIndex;
			var opt = select.options;
			for( var i = 0, len = opt.length; i < len; i++ ) {
				var o = opt[i];
				this.data[i] = { name:o.text, value:o.value };
			}
		}
	},
	
	/** Clear the target */
	removeOldContents:function() {
		this.target.childElements().each(function(element){element.hide()});
	},
	
	/** Get the name of the site currently selected */
	getSelectedItemValue:function() {
		if( this.selectedIndex == -1 ) { return ""; }
		var item = this.data[this.selectedIndex];
		if( item == null ) { return ""; }
		return this.data[this.selectedIndex].name;
	},
	
	getSelectedIndex:function() {
		return this.selectedIndex;
	},
	
	/** Build the new DOM structure */
	buildDropdown:function() {
		new Insertion.Bottom( this.target, '<a href="#" class="button">' + this.getSelectedItemValue() + '&nbsp;</a><div class="list"></div>' );
		this.button = this.target.down("a");
		this.list = this.target.down("div");
	},
	
	initList:function() {
		if( this.listInitialized ) { return; }
		this.buildLists();
		Position.absolutize(this.list);
		this.list.show();
		this.updateListSize();
		Event.observe( this.list, "click", this.handleListClick.bindAsEventListener( this ) );
		this.listInitialized = true;
		this.list.hide();
	},
	
	buildLists:function() {
		var numColumns = Math.ceil( this.data.size() / this.itemsPerColumn );
		var columns = this.data.eachSlice( this.itemsPerColumn );
		var html = [];
		var _this = this;
		columns.each( function( col, index ) { html[index] = '<ol>' + _this.createListItems( col ) + '</ol>' } );
		new Insertion.Bottom( this.list, html.join('') );
	},
	
	/** Create an Array of <li><a ... ></li> nodes with labeled links to sites */
	createListItems:function( list ) {
		return list.collect( 
			function(listItem) {
				return '<li><a href="' + listItem.value + '">' + listItem.name + '</a></li>';
			} ).join("");
	},
	
	/** Add event listeners */
	initEvents:function() {
		Event.observe( this.button, "mousedown", this.handleButtonClick.bindAsEventListener( this ) );
		Event.observe( this.button, "click", function(e) { Event.stop(e); } );
		Event.observe( document, "click", this.hide.bind( this ) );
		new PeriodicalExecuter( this.updateListBounds.bind( this ), 0.1 );
	},
	
	setSelected:function( index ) {
		this.selectedIndex = index;
		this.updateButtonLabel();
		this.triggerChangeListeners();
	},
	
	updateListBounds:function() {
		this.updateListSize();
	},
	
	/** Update list container size to match list columns */
	updateListSize:function() {
		if( this.list.visible() ) {
			var width = 0;
			var height = 0;
			var col = this.list.down( "ol" );
			while( col != null ) {
				height = Math.max( height, col.getHeight() );
				width += col.getWidth();
				col = col.next( "ol" );
			}
			this.list.setStyle( {
				width:width+"px",
				height:height+"px" } );
		}
	},
	
	/** Called when the button is clicked */
	handleButtonClick:function( event ) {
		Event.stop( event );
		this.toggle();
	},
	
	/** Called when an item in the list is clicked. */
	handleListClick:function( event ) {
		if( !this.hasChangeListeners() ) { return true; }
		
		var element = $(Event.element( event ));
		if( element.tagName.toUpperCase() == "A" ) {
			Event.stop( event );
			var li = element.up("li");
			this.selectedIndex = this.getListElementIndex(li);
			this.updateButtonLabel();
			this.close();
			this.triggerChangeListeners();
		}
	},
	
	/** Find out at what index a given <li> is at, including all columns. */
	getListElementIndex:function( li ) {
		var countInColumnsBefore = 0;
		var ol = li.up("ol");
		ol = ol.previous("ol");
		while( ol != null ) {
			countInColumnsBefore += ol.getElementsByTagName("li").length;
			ol = ol.previous("ol");
		}
		var indexInThisColumn = 0;
		li = li.previous("li");
		while( li != null ) { 
			indexInThisColumn += 1;
			li = li.previous("li");
		}
		return countInColumnsBefore + indexInThisColumn;
	},
	
	toggle:function() {
		this.initList();
		this.list.toggle();
		this.updateButtonIcon();
		this.triggerToggleListeners();
	},
	
	hide:function() {
		this.list.hide();
		this.updateButtonIcon();
		this.triggerToggleListeners();
	},
	
	close:function() {
		this.hide();
	},
	
	open:function() {
		this.initList();
		this.list.show();
		this.updateButtonIcon();
	},
	
	isOpen:function() {
		return this.list.visible();
	},
	
	updateButtonIcon:function() {
		if( this.list.visible() ) {
			this.button.addClassName("open");
		} else {
			this.button.removeClassName("open");
		}
	},
	
	updateButtonLabel:function() {
		this.button.update( this.getSelectedItemValue() );
	},
	
	setListOffset:function( x, y ) {
		this.listOffsetX = x;
		this.listOffsetY = y;
	},
	
	addChangeListener:function( listenerFunction ) {
		this.changeListeners[this.changeListeners.length] = listenerFunction;
	},
	
	triggerChangeListeners:function() {
		var dropdown = this;
		this.changeListeners.each( function( listener ) { listener( dropdown ); } )
	},
	
	hasChangeListeners:function() {
		return this.changeListeners.length > 0;
	},
	
	addToggleListener:function( listenerFunction ) {
		this.toggleListeners[this.toggleListeners.length] = listenerFunction;
	},
	
	triggerToggleListeners:function() {
		var dropdown = this;
		this.toggleListeners.each( function( listener ) { listener( dropdown ); } );
	}

}



var DoubleDropdownControl = Class.create();
DoubleDropdownControl.prototype = {
	
	firstDropdown:null,
	secondDropdowns:[],
	secondDropdownTargets:[],
	data:null,
	
	listOffsetX:0,
	listOffsetY:0,
	
	changeListeners:null,
	selectedData:null,
	
	/**
	 * @param target1 DOM element to place the first dropdown in
	 * @param target2 DOM element to place the second dropdown in
	 * @param data A 2-dimensional array of lists to use as data. 
	 *             The items in the data array must be objects like this: { name:<string label for first dropdown>, list:<array of value objects for second dropdown> }.
	 *             See DropdownControl for definition of value objects for second dropdown.
	 */
	initialize:function( target1, target2, data, itemsPerColumn1, itemsPerColumn2 ) {
		this.data = data;
		this.changeListeners = [];
		this.initDropdowns( target1, target2, data, itemsPerColumn1, itemsPerColumn2 );
		this.showSecondDropdown(0);
		this.setListOffset( 0, 20 );
	},
	
	unload:function() {
		this.firstDropdown.unload();
		this.firstDropdown = null;
		this.secondDropdowns.each(function(dd){dd.unload()});
		this.secondDropdowns = null;
	},
	
	initDropdowns:function( target1, target2, data, itemsPerColumn1, itemsPerColumn2 ) {
		var firstData = [];
		for( var i = 0; i < data.length; i++ ) {
			firstData[i] = { name: data[i].name, value: data[i].name };
			this.addSecondDropdown( target2, data[i].list, itemsPerColumn2 );
		}
		this.firstDropdown = new DropdownControl( target1, firstData, itemsPerColumn1 );
		this.firstDropdown.addToggleListener( this.handleToggleEvent.bind( this ) );
		this.firstDropdown.addChangeListener( this.handleChangeEvent.bind( this ) );
		this.firstDropdown.setSelected(0);
	},
	
	addSecondDropdown:function( target, data, itemsPerColumn ) {
		var index = this.secondDropdowns.length;
		this.secondDropdownTargets[index] = $(target.appendChild( Builder.node( "div" ) ));
		this.secondDropdowns[index] = new DropdownControl( this.secondDropdownTargets[index], data, itemsPerColumn );
		this.secondDropdowns[index].addToggleListener( this.handleToggleEvent.bind( this ) );
		this.secondDropdowns[index].addChangeListener( this.handleChangeEvent.bind( this ) );
		this.secondDropdownTargets[index].hide();
	},
	
	showSecondDropdown:function( index ) {
		if( index < 0 ) { return; }
		var dd = this.secondDropdownTargets;
		var len = dd.length;
		for( var i = 0; i < len; i++ ) {
			if( i == index ) {
				dd[i].show();
				this.secondDropdowns[i].setSelected(0);
			} else {
				dd[i].hide();
			}
		}
	},
	
	setListOffset:function( x, y ) {
		this.listOffsetX = x;
		this.listOffsetY = y;
		this.firstDropdown.setListOffset( x, y );
		for( var i = 0; i < this.secondDropdowns.length; i++ ) {
			this.secondDropdowns[i].setListOffset(x,y);
		}
	},
	
	handleToggleEvent:function( dropdown ) {
		if( dropdown.isOpen() ) {
			this.firstDropdown.close();
			this.secondDropdowns.each( function( dd ) { dd.close() } );
			dropdown.open();
		}
	},
	
	handleChangeEvent:function( dropdown ) {
		if( dropdown == this.firstDropdown ) {
			var index = dropdown.getSelectedIndex();
			this.showSecondDropdown( index );
		}
		this.updateSelectedData();
		this.triggerChangeListeners();
	},
	
	updateSelectedData:function() {
		var firstIndex = this.firstDropdown.getSelectedIndex();
		var secondIndex = this.secondDropdowns[firstIndex].getSelectedIndex();
		if( firstIndex >= 0 && secondIndex >= 0 ) { 
			var list = this.data[firstIndex].list;
			this.selectedData = list[secondIndex];
		}
	},
	
	addChangeListener:function( listenerFunction ) {
		this.changeListeners[this.changeListeners.length] = listenerFunction;
	},
	
	triggerChangeListeners:function() {
		var dropdown = this;
		this.changeListeners.each( function( listener ) { listener( dropdown ) } );
	},
	
	getSelectedData:function() {
		return this.selectedData;
	}
	
	
}


