/**
 * CompactSectionsManager v1.9.2
 *
 * Unobtrusive DHTML clickey-hidey content sections.
 * Expandable or tabbed
 *
 * Dependencies class.Utilities.js, JSON.js
 *
 * @author Andrew Ramsden <http://irama.org/web/dhtml/compact-sections/>
 * @license Common Public License Version 1.0 <http://www.opensource.org/licenses/cpl1.0.txt>
 *
 *
 * Example, use the following HTML:
 <div class="compact">
 	<div class="section" id="first-section">
		<h2>Section title</h2>
		<p>Some content.</p>
	</div>
	<div class="section" id="another">
		<h2>Another title</h2>
		<p>Some content.</p>
	</div>
 </div>
 *
 * Then use the following JavaScript on page load:
 * <code>CompactManager = new CompactSectionsManager('CompactManager');</code>
 *
 * Note each section MUST have a heading (H2) and must have a unique id.
 *
 * @param String objName the name of the instantiated object
 */
function CompactSectionsManager ( /* string */ objName )
{
	this.init = function ( /* string */ objName )
	{
		this.compactTagName        = 'DIV';
		this.sectionTagName        = 'DIV';
		this.sectionHeadingTagName = 'H2';
		this.contentWrapperTagName = 'DIV';
		this.compactClass          = 'compact';
		this.sectionClass          = 'section';
		this.contentWrapperClass   = 'content';
		this.expandableTypeClass   = 'expandable';
		this.tabbedTypeClass       = 'tabbed';
		this.tabsClass             = 'tabs';
		this.currentTabClass       = 'current';
		this.TOCClass              = 'toc';
		this.activeAreaClass       = 'active';
		this.closedClass           = 'closed';
		this.errorClass            = 'error';
		this.hoverClass            = 'hover';
		
		
		if (typeof(Utilities) == 'undefined') return false;
		this.u = new Utilities();
		this.cookieName = 'CompactSectionsManager-'+objName;
		this.cookie = new CookieManager (this.cookieName);
		
		
		// We need a reference to this instance of the class.
		if (typeof(objName) != 'undefined' && objName != '') {
			this.obj = objName;
		} else {
			this.obj = this.ulID + "CompactSectionsManager";
		}
		eval(this.obj + "=this;");
		
		// The URL for this page (without any hash value)
		this.pageURL = 
			  window.location.protocol + '//'
			+ window.location.host
			+ window.location.pathname
			+ window.location.search
		;
		
		this.postActivateCallback = '';
		
		this.areaCount = 0;
		this.areas = [];
		this.initialiseAreas();
		
		// wipe cookie (use if necessary)
		//this.cookie.set({});
		
		this.currentStates = this.cookie.get();
		if (this.currentStates==null) {
			this.currentStates = {};
		}
		if (typeof(this.currentStates[this.pageURL]) == 'undefined') this.currentStates[this.pageURL] = [];
		
		
		//console.log(this.u.JSON.objectToString(this.currentStates));
		
		this.activateCurrentStates();
		
		// run this.pollHash every second
		this.pollHashInterval = window.setInterval(this.u.escapeJSString(this.obj)+'.pollHash()', 1000);
	}
	
	/**
	 * Checks if the hash has been updated, then activates
	 * the appropriate tab.
	 */
	this.pollHash = function ()
	{
		if (this.u.getURLHash() == this.currentHash) return;
		this.activate(this.u.getURLHash());
	}
	
	
	this.stopPollHash = function ()
	{
		return window.clearInterval(this.pollHashInterval);
	}
	
	
	/**
	 * @param string The name of the function that will be called
	 *        after an activate event. The function will be sent 1
	 *        parameter, the section element that the activate event
	 *        was applied to.
	 */
	this.setPostActivateCallback = function (callback)
	{
		this.postActivateCallback = callback;
	}
	
	this.initialiseAreas = function ()
	{
		areas = this.u.getElementsByClass(this.compactClass,this.compactTagName,document);
		success = true;
		for (areaId in areas) {
			success = (this.initialiseSections (areas[areaId])?success:false);
		}
		
		return success;
	}
	
	this.initialiseSections = function (areaElement)
	{
		objReference = this.u.escapeJSString(this.obj);
		
		// setup heading onclick action
		actionStr = "if (typeof("+objReference+")!='undefined') "+objReference+".toggle(this);";
		eval("clickAction = function () { "+actionStr+"; return false; };");
		
		// setup mouseover action
		actionStr = "if (typeof("+objReference+")!='undefined') "+objReference+".u.addClass(this, '"+this.hoverClass+"');";
		eval("mouseOverAction = function () { "+actionStr+"; return false; };");
		
		
		// setup mouseout action
		actionStr = "if (typeof("+objReference+")!='undefined') "+objReference+".u.removeClass(this, '"+this.hoverClass+"');";
		eval("mouseOutAction = function () { "+actionStr+"; return false; };");
		
		// set expandable type display as default
		if (!this.u.hasClass(areaElement, this.tabbedTypeClass) &&
		!this.u.hasClass(areaElement, this.expandableTypeClass)) {
			!this.u.addClass(areaElement, this.expandableTypeClass)	
		}
		areaType = (!this.u.hasClass(areaElement, this.tabbedTypeClass))?'expandable':'tabbed';
		
		
		potentialSections = this.u.getElementsByClass(this.sectionClass,this.sectionTagName,areaElement);
		
		sections = [];
		sectionCount = 0;
		
		for (eachSection in potentialSections) {
			
			// test for id
			if (typeof(potentialSections[eachSection].id) == 'undefined' || potentialSections[eachSection].id == '') {
				this.u.addClass(potentialSections[eachSection],this.errorClass);
				continue; // This section could not be initialised, because there were no id
			}
			
			if ((heading = this.getFirstChildOfType(potentialSections[eachSection], this.sectionHeadingTagName)) == false) {
				this.u.addClass(potentialSections[eachSection],this.errorClass);
				continue; // This section could not be initialised, because there were no headings.
			}
			
			// close the section (unless areaType is tabbed and this is the first section)
			if (!(areaType == 'tabbed' && sectionCount==0)) {
				if (!this.u.hasClass(potentialSections[eachSection], this.closedClass)) this.u.addClass(potentialSections[eachSection], this.closedClass);
			}
			
			
			// Add the click action to heading
			heading.onclick = clickAction;
			heading.onmouseover = mouseOverAction;
			heading.onmouseout = mouseOutAction;
			headingText = this.u.getFirstInnerTextNode(heading);
			heading.title = 'Click to expand the '+headingText+' section';
			
			sections[sectionCount] = {
				'sectionId':potentialSections[eachSection].id,
				'sectionElement':potentialSections[eachSection],
				'headingElement':heading,
				'headingText': headingText
			};
			
			this.addContentWrapperToSection(sections[sectionCount]);
			
			sectionCount++;
		}
		
		
		if (areaType == 'tabbed') {
			// add in the index links if tabbed view
			indexNav = this.addIndexNav(areaElement, sections, clickAction);
		} else {
			indexNav = null;
		}
		
		
		if (sectionCount == 0) {
			// This area has no valid sections
			return false;
		} else {
			// This wrapper is to workaround an IE6 bug
			areaElement = this.addAreaWrapper (areaElement);
			
			// Add active class to area
			this.u.addClass(areaElement, this.activeAreaClass);
		}
		
		
		this.areas[this.areaCount++] = {
			'area' : areaElement,
			'areaType' :areaType, // expandable or tabbed
			'sections' : sections,
			'indexNav' : indexNav // only for tabbed type
		};
	}
	
	/**
	 * This function works around the IE6 bug that prevents the
	 * use of multiple classes on our area DIV.
	 * By spreading the classes over the area DIV and the new
	 * wrapper DIV, means IE6 can target them successfully with CSS.
	 */
	this.addAreaWrapper = function (areaElement)
	{
		wrapperDiv = document.createElement('div');
		this.u.addClass(wrapperDiv, this.compactClass);
		this.u.removeClass(areaElement, this.compactClass);
		
		areaElement.parentNode.replaceChild(wrapperDiv, areaElement);
		wrapperDiv.appendChild(areaElement);
		return wrapperDiv;
	}
	
	
	/**
	 * Adds all elements within the section except for the heading
	 * to a content div, then removes them from the section.
	 */
	this.addContentWrapperToSection = function (sectionArray)
	{
		if (
			typeof(sectionArray['sectionElement']) == 'object'
			&& sectionArray['sectionElement'].hasChildNodes()
		) {
			contentWrapper = document.createElement(this.contentWrapperTagName);
			this.u.addClass(contentWrapper, this.contentWrapperClass);
				
			// one way for IE, one way for the rest
			if (document.all) {
				sectionArray['sectionElement'].removeChild(sectionArray['headingElement']);
				temp = sectionArray['sectionElement'].innerHTML;
				sectionArray['sectionElement'].innerHTML = '';

				sectionArray['sectionElement'].appendChild(sectionArray['headingElement']);
				sectionArray['sectionElement'].appendChild(contentWrapper);
				contentWrapper.innerHTML += temp;
			} else {
				elementsToBeRemoved = [];
				removeCount = 0;
				
				for (elId in sectionArray['sectionElement'].childNodes) {
					if (
						typeof(sectionArray['sectionElement'].childNodes[elId]) != 'undefined'
						&& sectionArray['sectionElement'].childNodes[elId] != sectionArray['headingElement']
						&& typeof(sectionArray['sectionElement'].childNodes[elId].cloneNode) == 'function'
					) {
						tempEl = sectionArray['sectionElement'].childNodes[elId].cloneNode(true);
						elementsToBeRemoved[removeCount++] = sectionArray['sectionElement'].childNodes[elId];
						contentWrapper.appendChild(tempEl);
					}
				}
				sectionArray['sectionElement'].appendChild(contentWrapper);
				
				// clean up elements to be removed
				for (elKey in elementsToBeRemoved) {
					elementsToBeRemoved[elKey].parentNode.removeChild(elementsToBeRemoved[elKey]);
				}
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Builds a nl (until XHTML2 an <ul class="nl">) which links to the sections
	 */
	this.addIndexNav = function (areaElement, sections, clickAction)
	{
		nl = document.createElement('UL');
		this.u.addClass(nl, 'nl');
		this.u.addClass(nl, this.tabsClass);
		// build up navigation list from sections, assign action to links
		for (sectionKey in sections) {
			actionStr = "if (typeof("+objReference+")!='undefined') "+objReference+".activate(\""+sections[sectionKey]['sectionId']+"\");";
			eval("indexAction = function () { "+actionStr+"; return false; };");
		
			tempLi = document.createElement('LI');
			tempA = document.createElement('A');
			tempA.href = '#'+sections[sectionKey]['sectionId'];
			tempA.title = 'Activate the '+sections[sectionKey]['headingText']+' section';
			tempA.onclick = indexAction;
			tempText = document.createTextNode(sections[sectionKey]['headingText']);
			
			tempA.appendChild(tempText);
			tempLi.appendChild(tempA);
			nl.appendChild(tempLi);
		}
		
		// add nav as first child of areaElement
		this.u.prependChild(areaElement, nl);
		return nl;
	}
	
	this.getFirstChildOfType = function (element, nodeName)
	{
		if (!element.getElementsByTagName) return false;
		collection = element.getElementsByTagName(nodeName);
		if (collection == null || collection == [] || collection == false) {
			return false;
		}
		
		return (typeof(collection[0])!='undefined')?collection[0]:false;
	}
	
	/**
	 * a wrapper for activate with toggle switch set to true
	 * Useful for the click action of the expandable sections.
	 */
	this.toggle = function (sectionHeadingElementOrSectionId)
	{
		return this.activate (sectionHeadingElementOrSectionId, toggle=true);
	}
	
	this.activate = function (sectionHeadingElementOrSectionId, /* optional - default = false */ toggle)
	{
		toggle = (typeof(toggle)!='undefined')?(toggle)?true:false:false;
		
		if (typeof(sectionHeadingElementOrSectionId)=='string') {
			//console.log('string: '+sectionHeadingElementOrSectionId);
			if (sectionHeadingElementOrSectionId == '') return false;
			if (!(sectionElement = this.u.findObj(sectionHeadingElementOrSectionId))) return false;
			sectionHeadingElement = this.getFirstChildOfType(sectionElement, this.sectionHeadingTagName);
		} else {
			if (typeof(sectionHeadingElementOrSectionId) != 'object') return false;
			sectionHeadingElement = sectionHeadingElementOrSectionId;
			sectionElement = sectionHeadingElement.parentNode;
		}
		
		// get the area that this section heading is in
		areaKey = this.getAreaKeyOfSection(sectionElement);
		
		// if this section is already open
		if (!this.u.hasClass(sectionElement, this.closedClass)) {
			if (!toggle) {
				if (this.areas[areaKey]['areaType'] == 'tabbed') {
					this.flagCurrentTabForArea(areaKey, sectionElement.id);
				} else {
					// already open, mission accomplished
					this.currentHash = sectionElement.id;	
				}
				return true;
			} else {
				// close the section
				this.u.addClass(sectionElement, this.closedClass);
				// remove hash from URL
				this.u.setURLHash();
				this.currentHash = '';
				this.setCurrentState(areaKey, '');
			}
		} else {
			// close all sections within this area
			for (eachSection in this.areas[areaKey]['sections']) {
				this.u.addClass(this.areas[areaKey]['sections'][eachSection]['sectionElement'], this.closedClass);
			}
			// open the clicked section
			this.u.removeClass(sectionElement, this.closedClass);
			this.u.setURLHash(sectionElement.id);
			this.currentHash = sectionElement.id;
			this.setCurrentState(areaKey, sectionElement.id);
		}
		
		// update the cookie for this area
		this.cookie.set(this.currentStates);
		
		if (this.areas[areaKey]['areaType'] == 'tabbed') {
			this.flagCurrentTabForArea(areaKey, sectionElement.id);
		}
		
		
		if (this.postActivateCallback != '') {
			// if the function exists, call it, and send the element that
			// triggered the activate.
			eval('if (typeof('+this.postActivateCallback+') == "function") { '+this.postActivateCallback+'(sectionElement); }');
		}
	}
	
	
	/**
	 * An internal method, used only for tabbed type areas.
	 * adds the current class to the li which conatins the 
	 * tabAnchorElement which activated the section.
	 */
	this.flagCurrentTabForArea = function (areaKey, sectionId)
	{
		tabNl = this.areas[areaKey]['indexNav'];
		if (tabNl == null) return false;
		
		// find matching anchor element
		tabAnchors = tabNl.getElementsByTagName('A');
		parentElement = null;
		for (tabAnchorKey in tabAnchors) {
			if (this.u.hasClass(tabAnchors[tabAnchorKey].parentNode, this.currentTabClass)) this.u.removeClass(tabAnchors[tabAnchorKey].parentNode, this.currentTabClass);
			
			if (typeof(tabAnchors[tabAnchorKey].href) != 'undefined') {
				hrefHash = tabAnchors[tabAnchorKey].href.substring(tabAnchors[tabAnchorKey].href.lastIndexOf('#')+1, tabAnchors[tabAnchorKey].href.length )
				//console.log(hrefHash);
				if (hrefHash == sectionId) {
					this.u.addClass(tabAnchors[tabAnchorKey].parentNode, this.currentTabClass);
				}
			}
		}
		return true;
	}
	
	this.getAreaKeyOfSection = function (sectionElement)
	{
		for (areaKey in this.areas) {
			for (sectionKey in this.areas[areaKey]['sections']) {
				if (this.areas[areaKey]['sections'][sectionKey]['sectionElement'] == sectionElement) {
					return areaKey;
				}
			}
		}
		return 0; // default
	}
	
	this.setCurrentState = function (areaKey, activeSectionId)
	{
		this.currentStates[this.pageURL][areaKey] = {
			'activeSectionId':activeSectionId
		};
	}
	
	this.getCurrentState = function (areaKey)
	{
		if (typeof(this.currentStates[this.pageURL][areaKey])=='undefined') {
			// nothing open
			this.currentStates[this.pageURL][areaKey] = null; // this.getFirstSectionIdOfArea(areaKey);
		}
		
		return this.currentStates[this.pageURL][areaKey];
	}
	
	this.activateCurrentStates = function ()
	{
		for (areaKey in this.areas) {
			// load states from URL
			if (this.u.getURLHash() != '' && this.sectionIdIsWithinThisArea(this.u.getURLHash(), areaKey)) {
				// load section from hash
				this.activate(this.u.getURLHash());
			} else {
				// load from cookie
				currentState = this.getCurrentState(areaKey);
				if (currentState != null && currentState != 'null') {
					this.activate(currentState['activeSectionId']);
				} else if (this.areas[areaKey]['areaType'] == 'tabbed') {
					this.activateFirstSection(areaKey);
				}
			}
			
		}
	}
	
	
	this.activateFirstSection = function (areaKey)
	{
		if (
			this.areas && 
			this.areas[areaKey] && 
			this.areas[areaKey]['sections'] &&
			this.areas[areaKey]['sections'][0] &&
			typeof(this.areas[areaKey]['sections'][0]['sectionElement']) == 'object'
		) {
			return this.activate(this.areas[areaKey]['sections'][0]['sectionElement'].id);
		}
		return false;
	}
	
	this.sectionIdIsWithinThisArea = function (hashId, areaKey)
	{
		for (sectionKey in this.areas[areaKey]['sections']) {
			if (this.areas[areaKey]['sections'][sectionKey]['sectionElement'].id == hashId) {
				return true;
			}
		}
		return false;
	}
	
	this.getFirstSectionIdOfArea = function (areaKey)
	{
		section = this.getFirstSectionOfArea(areaKey);
		if (section != false) {
			return section.id;
		} else {
			return false;
		}
	}
	
	this.getFirstSectionOfArea = function (areaKey)
	{
		if (typeof(this.areas[areaKey]) == 'undefined') {
			return false;
		}
		
		sections = this.areas[areaKey]['sections'];
		
		if (typeof(sections[0]) == 'undefined') {
			return false;
		}
		
		return sections[0];
	}
	
	this.init(objName);
}
