/*
 * folding.js
 *  Copyright (C) 2009 ClearCode Inc.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
  *Note: This is based on DOM3 XPath API. For InternetExplorer or other
         legacy UAs, requires "JavaScript-XPath".
         http://coderepos.org/share/wiki/JavaScript-XPath

  Usage:
    new Folding(
      '/descendant::*[@class="items-should-be-folded"]',
      'Show Details',
      'Hide Details'
    );

  To expand all items, do "Folding.toggleShowHide(true)".
*/

function Folding()
{
	/*
		expression, show, hide
		expression, show, hide, defaultShow
		expression, show, hide, backward, defaultShow
		expression, show, hide, key, backward, defaultShow
	*/
	Folding.instances.push(this);
	this.items = [];
	if (arguments.length) {
		this.expression  = arguments[0];
		this.showLabel   = (arguments.length > 1 ? arguments[1] : '' ) || 'Show';
		this.hideLabel   = (arguments.length > 2 ? arguments[2] : '' ) || 'Hide';
		this.defaultShow = (arguments.length > 5 ? arguments[5] :
		                    arguments.length > 4 ? arguments[4] :
		                    arguments.length > 3 ? arguments[3] :
		                    false );
		this.backward    = (arguments.length > 5 ? arguments[4] :
		                    arguments.length > 4 ? arguments[3] :
		                    false );
		this.key         = (arguments.length > 5 ? arguments[3] :
		                    '' );
		this.init();
	}
}

Folding.instances = [];
Folding.toggleShowHide = function(aShow)
{
	for (var i in this.instances)
	{
		this.instances[i].toggleShowHide(aShow);
	}
};
Folding.createToggleShowHideButton = function(aShowLabel, aHideLabel)
{
	var button = Folding.prototype.createButton.call(Folding.prototype, null, function() {
			var shown = button.value == aShowLabel;
			Folding.toggleShowHide(shown);
			button.value = shown ? aHideLabel : aShowLabel ;
			return false;
		});
	button.value = aShowLabel;
	return button;
};

Folding.prototype = {
	kFOLDING_ITEM_ID   : 'folding-item-id',
	kFOLDING_TARGET    : 'folding-target',
	kID_PREFIX         : 'folding-item-',
	kSCROLL_OFFSET_TOP : 32,
	XHTMLNS            : 'http://www.w3.org/1999/xhtml',

	init : function()
	{
		var self = this;
		if ('addEventListener' in window) {
			window.addEventListener('load', function() {
				self.onLoad();
			}, false);
		}
		else if ('attachEvent' in window) {
			window.attachEvent('onload', function() {
				self.onLoad();
			});
		}
	},

	onLoad : function()
	{
		var nodes = document.evaluate(
				this.expression,
				document,
				null,
				XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
				null
			);
		if (this.backward) {
			for (var i = nodes.snapshotLength-1; i > -1; i--)
			{
				this.initItem(nodes.snapshotItem(i));
			}
		}
		else {
			for (var i = 0, maxi = nodes.snapshotLength; i < maxi; i++)
			{
				this.initItem(nodes.snapshotItem(i));
			}
		}
	},

	initItem : function(aItem)
	{
		var self = this;

		aItem.setAttribute(
			this.kFOLDING_ITEM_ID,
			this.kID_PREFIX+(this.key || Folding.instances.length)+'-'+(this.items.length+1)
		);

		var id = aItem.id || aItem.getAttribute(this.kFOLDING_ITEM_ID);

		var showLabel = this.showLabel;
		var hideLabel = this.hideLabel;

		var buttons = [];
		var toggleShowHide = function(aShow) {
				aItem.style.display = aShow ? 'block' : 'none' ;
				for (var i in buttons)
				{
					buttons[i].value = aShow ? self.hideLabel : self.showLabel ;
				}
			};

		var onClickHandler = function() {
				toggleShowHide(buttons[0].value == self.showLabel);
				location.hash = '#focused-folding-item('+id+')';
				return false;
			};

		if (aItem.nodeName.toLowerCase() == 'dd') {
			var dt = aItem;
			while (dt = document.evaluate(
					'preceding-sibling::*[1][local-name()="dt" or local-name()="DT"]',
					dt,
					null,
					XPathResult.FIRST_ORDERED_NODE_TYPE,
					null
				).singleNodeValue)
			{
				buttons.push(this.createButton(id, onClickHandler));
				dt.appendChild(buttons[buttons.length-1]);
			}
			if (dt == aItem) { // there is no DT
				var button = this.createButton(id, onClickHandler);
				buttons.push(button);
				dt.appendChild(button);
			}
		}
		else {
			var button = this.createButton(id, onClickHandler);
			buttons.push(button);
			aItem.parentNode.insertBefore(button, aItem);
		}

		aItem.buttons = buttons;
		aItem.toggleShowHide = toggleShowHide;
		this.items.push(aItem);

		this.initialShowHide(aItem);
	},

	initialShowHide : function(aItem)
	{
		var id = aItem.id || aItem.getAttribute(this.kFOLDING_ITEM_ID);
		var focused = location.hash.match(/^#(?:focused-folding-item\((.+)\)|(.+))$/);
		focused = !focused ? null : (focused[1] || focused[2]) ;

		var item;
		try {
			item = document.evaluate(
				'descendant::*[@'+this.kFOLDING_ITEM_ID+'="'+focused+'"] | '+
				'descendant::*[@id="'+focused+'"] | '+
				'preceding-sibling::*[@id="'+focused+'"]/descendant-or-self::*[@'+this.kFOLDING_ITEM_ID+'] | '+
				'preceding-sibling::*[@id="'+focused+'"]/following-sibling::*[@'+this.kFOLDING_ITEM_ID+'][1]',
				aItem,
				null,
				XPathResult.FIRST_ORDERED_NODE_TYPE,
				null
			).singleNodeValue
		}
		catch(e) {
		}
		if (focused == id || item == aItem) {
			aItem.toggleShowHide(true);
			var nodes = document.evaluate(
					'ancestor::*[@'+this.kFOLDING_ITEM_ID+']',
					aItem,
					null,
					XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
					null
				);
			for (var i = 0, maxi = nodes.snapshotLength; i < maxi; i++)
			{
				nodes.snapshotItem(i).toggleShowHide(true);
			}

			if (focused == id) {
				var offset = this.kSCROLL_OFFSET_TOP;
				window.setTimeout(function() {
					var node = aItem.buttons[0];
					var x = 0;
					var y = 0;
					while (node)
					{
						x += node.offsetLeft;
						y += node.offsetTop;
						node = node.offsetParent;
					}
					window.scrollTo(x, y - offset);
				}, 0);
			}
		}
		else {
			aItem.toggleShowHide(this.defaultShow);
		}
	},

	createButton : function(aId, aOnClickHandler)
	{
		var button = 'createElementNS' in document ?
				document.createElementNS(this.XHTMLNS, 'input') :
				document.createElement('input');
		if (aId)
			button.setAttribute(this.kFOLDING_TARGET, aId);
		button.type = 'button';
		button.value = this.hideLabel;
		button.onclick = function(event) {
			if (!event) event = window.event;
			if (event.button == 0) aOnClickHandler();
		};
		button.onkeypress = function(event) {
			if (!event) event = window.event;
			if (event.keyCode == 13 || event.key == ' ') aOnClickHandler();
		};
		return button;
	},

	toggleShowHide : function(aShow)
	{
		for (var i in this.items)
		{
			this.items[i].toggleShowHide(aShow);
		}
	}
};

