/**
 * Journal module.
 */
(function($) {

	var ROOT    = '#nav-article';
	var CONTENT = '#content-main .article';
	var CSS_ACTIVE = 'active';
	var CSS_OPEN = 'open';
	var ACTIVE_SELECTOR = '.' + CSS_ACTIVE;
	var ARROW_HTML = '<span class="arrow">arrow</span>';

	var Journal = function() {
		this.root = $(ROOT);
		this.content = $(CONTENT);
		this.rootParent = this.root.find('ul:first');
		this.rootItems = this.rootParent.children('li');
		this.rootParent.find('li:has(ul)').prepend(ARROW_HTML);
		this.chapters = this.getChapters();
		this.jsScroll = false;
		this.minContentSize = this.content.height();
		
		// Bind events
		$('a', ROOT).bind('click', this.handleLinkClick.bind(this));
		$('.arrow', ROOT).bind('click', this.handleArrowClick.bind(this));
		this.content.bind('scroll', this.handleContentScroll.bind(this));
	};
	
	Journal.prototype = {
	
		/**
		 * Initializes the component.
		 */
		initialize: function() {
			this.setVisibleChapterActive();
		},
		
		/**
		 * Handles the click on a link.
		 * @param {Event} e the event
		 */
		handleLinkClick: function(e) {
			e.preventDefault();
			this.setActive(this.getItem(e.target));
			this.scrollTo($(e.target));
		},
		
		/**
		 * Handles the click on a item.
		 * @param {Event} e the event
		 */
		handleArrowClick: function(e) {
			this.toggleCollapse(this.getItem(e.target));
		},
		
		/**
		 * Handles the scroll event on the content div.
		 * @param {Event} e the event
		 */
		handleContentScroll: function(e) {
			if (!this.jsScroll) {
				this.setVisibleChapterActive();
			}
		},
		
		/**
		 * Reset the active items.
		 */
		resetActive: function() {
			this.root.find(ACTIVE_SELECTOR).removeClass(CSS_ACTIVE);
		},
		
		/**
		 * Closes the item.
		 * @param {jQuery} item the item
		 */
		close: function(item) {
			item.removeClass(CSS_OPEN);
			this.checkContentHeight();
		},
		
		/**
		 * Opens the item.
		 * @param {jQuery} item the item
		 */
		open: function(item) {
			item.addClass(CSS_OPEN);
			this.checkContentHeight();
		},
		
		/**
		 * Toggles the item.
		 * @param {jQuery} item the item
		 */
		toggleCollapse: function(item) {
			if (item.hasClass(CSS_OPEN)) {
				this.close(item);
			} else {
				this.open(item);
			}
		},
		
		/**
		 * Opens the parents of the item.
		 * @param {jQuery} item the item
		 */
		openParents: function(item) {
			this.open(item.parents('li'));
		},
		
		/**
		 * Sets the given item to active.
		 * @param {jQuery} item the item
		 */
		setActive: function(item) {
			if (!this.isActive(item)) {
				this.resetActive();
				item.addClass(CSS_ACTIVE);
			}
		},
		
		/**
		 * Check if an item is active.
		 * @param {jQuery} item the item
		 * @return {Boolean} true if active
		 */
		isActive: function(item) {
			if (item.hasClass(CSS_ACTIVE)) {
				return true;
			}
			return false;
		},

		/**
		 * Gets the closest item or itself if the node is an 'li'.
		 * @param {Node or jQuery} node the node (e.g. 'a', 'span', 'li')
		 * @return {jQuery} the item ('li')
		 */
		getItem: function(node) {
			var result = $(node);
			if (result.length > 0) {
				if (result.is('li')) {
					return result;
				}
				return result.closest('li');
			}
			return result;
		},
		
		/**
		 * Scrolls the content to the item that has the id of the anchor of the given link.
		 * @param {jQuery} link the link
		 */
		scrollTo: function(link) {
			var linkObj = $(link);
			var anchor = linkObj.attr('href');
			var self = this;
			this.jsScroll = true;
			this.content.scrollTo($(anchor), {
				axis: 'y',
				duration: 300, 
				onAfter: function() {
					self.jsScroll = false;
				}
			});
		},
		
		/**
		 * Gets the chapters in the content.
		 * @return {Array of [{id: [String], size: [Float]}]} the chapters
		 */
		getChapters: function() {
			var result = new Array();
			var scrollHeight = this.content[0].scrollHeight;
			// Get headers
			var headers = this.content.find(':header');
			var sizeTotal = 0;
			for (i = 0; i < headers.length; i++) {
				var header = $(headers[i]);
				// Get the id
				var id = header.attr('id');
				// Get the vertical position
				var top = header.position().top;
				var size = 0;
				// Check if there is a next header
				if (i + 1 < headers.length) {
					var next = $(headers[i+1]);
					// Get the vertical position of the next header
					var nextTop = next.position().top;
					// Caculate the size of this chapter
					size = nextTop - top;
				} else {
					// The size of the last chapter is remainder of the scroll height
					size = scrollHeight - sizeTotal;
				}
				// Add the size to the total size
				sizeTotal += size;
				// Add the chapter to the result
				var chapter = {id:id, size:size};
				result.push(chapter)
			}
			return result;
		},
		
		/**
		 * Gets the id's of the visible chapters.
		 * @return {Array of [String]} the id's
		 */
		getVisible: function() {
			var result = new Array();
			// Get the nr of pixels that is scrolled
			var scrollTop = this.content.scrollTop();
			
			// Get the start and end of the visible area in px (substract 50px of the end to let the code think the visible area is smaller)
			var visibleStart = scrollTop;
			var visibleEnd = scrollTop + this.content.outerHeight() - 50;
			
			var sizeTotal = 0;
			for (i = 0; i < this.chapters.length; i++) {
				var chapter = this.chapters[i];
				// Get the area of the chapter
				var chapterStart = sizeTotal;
				sizeTotal += chapter.size;
				var chapterEnd = sizeTotal;

				// Check if the start of the chapter is visible or the end of the chapter is visible or the visible area is between the start and end of the chapter
				if ((chapterStart >= visibleStart && chapterStart <= visibleEnd) 
						|| (chapterEnd >= visibleStart && chapterEnd <= visibleEnd) 
						|| (visibleStart >= chapterStart && visibleStart <= chapterEnd)) {
					// This chapter is visible, so add the id to the result set
					result.push(chapter.id);
				}
				
			}
			return result;
		},
		
		/**
		 * Sets the first visible chapter to active.
		 */
		setVisibleChapterActive: function() {
			var visible = this.getVisible();
			if (visible.length > 0) {
				// Get the link in the menu
				var link = this.root.find('a[href=#' + visible[0] + ']');
				if (link.length > 0) {
					var item = this.getItem(link);
					this.setActive(item);
					this.openParents(item);
				}
			}
		},
		
		/**
		 * Checks the height of the content area and makes it bigger if the menu gets bigger.
		 */
		checkContentHeight: function() {
			var navHeight = this.root.height();
			var contentHeight = this.content.height();
			// Check if the size of the menu is smaller then the minimum size
			if (navHeight < this.minContentSize) {
				// Set the content to the minimum height
				if (contentHeight != this.minContentSize) {
					this.content.height(this.minContentSize);
				}				
			} else {
				// Set the content size to the menu size
				if (contentHeight != navHeight) {
					this.content.height(navHeight);
				}
			}
		}
	};
	
	/**
	 * Initializes the journal
	 */
	$(function() {
		if ($(ROOT).length > 0) {
			var journal = new Journal();
			journal.initialize();
		}
	});
	
})(jQuery);
