/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Tab View Control
 *
 *    @version 2.0.2010xxxx
 *    @requires jquery.js (need to set: "BAJL.settings.common.useBackCompat = true")
 *    @requires bajl.js
 *    @requires bajl.fontSizeObserver.js
 *    @requires bajl.rollover.js (when using tab images)
 *    @requires bajl.tabView.css
 */
/* -------------------------------------------------------------------------- */
(function($) {



BAJL.settings.TabView = {
	'div.tabView' : {
		  'adjustHeight' : true
		, 'tabNodeExpr'  : 'li.tabView-tab'
		, 'paneNodeExpr' : 'div.tabView-pane'
		, 'tabRollover'  : {
			  'findAtOnce' : true
			, 'exclude'    : '.norollover'
			, 'statusSet'  : {
				  'normal' : ''
				, 'stay'   : 's'
				, 'hover'  : 'o'
			}
			, 'handlers'   : {
				  'select'    : function(node, event) { this.setStatus('stay'   ) }
				, 'unselect'  : function(node, event) { this.setStatus('normal' ) }
				, 'mouseover' : function(node, event) { this.setStatus('hover'  ) }
				, 'mouseout'  : function(node, event) { this.setStatus('default') }
			}
		}
	}
};



/* -------------------- Class : BAJL.TabView -------------------- */
/**
 * provide tab view interface.
 * @class tab view
 * @see BAJL.TabViewPane
 * @param {Element}               node               base elmenet node of tab view interface
 * @param {Boolean}               adjustHeight       flag to set all panes height to max height of the panes.
 * @param {BAJL.Rollover.Setting} rolloverSetting    rollover setting for tab images.
 */
BAJL.TabView = function(node, adjustHeight, rolloverSetting) {
	/** base elmenet node of tab view interface.
	    @type BAElement @const @private */
	this.node            = $(node).get(0);
	/** BAJL.TabViewPane instances controlled by this instance.
	    @type BAJL.TabViewPane[] */
	this.panes           = [];
	/** auto adjust all panes' height?
	    @type Boolean @const @private */
	this.adjustHeight    = Boolean(adjustHeight);
	/** ignoring flag of 'auto adjust height', workaround for WinIE6.
	    @type Boolean @private */
	this.preventAdjust   = false;
	/** rollover setting for tab images.
	    @type BAJL.Rollover.Setting @const @private */
	this.rolloverSetting = rolloverSetting;

	if (BAJL.env.isDOMReady) {
		this.init();
	}
}

/**
 * initialize, add panes automatically.
 * @private
 */
BAJL.TabView.prototype.init = function() {
	if (!this.node) {
		throw 'BATabView.init: base element node is not specified.';
	} else {
		// temporary disable adjusting panes height. #########################################################
		var adjHeightTemp = this.adjustHeight;
		this.adjustHeight = false;

		// create pane instances
		var cnodes = this.node.getChildNodesBA();
		var tabs   = null;
		for (var i = 0, n = cnodes.length; i < n && !tabs; i++) {
			var cnode = cnodes[i];
			if (cnode.nodeType == 1 && cnode.hasClassNameBA(BA_TABVIEW_TABSNODE_CLASSNAME)) {
				tabs = cnode;
			}
		}
		tabs.getElementsByClassNameBA(BA_TABVIEW_TABNODE_CLASSNAME).forEach(function(node) {
			this.addPane(new BATabViewPane(node, this.rolloverSetting));
		}, this);

		// restore original setting to adjust panes height.
		this.adjustHeight = adjHeightTemp;
		this.adjustPaneHeight();

		// if all panes are not selected, auto-select first pane.
		if (this.panes.every(function(pane) { return !pane.isSelected })) {
			this.switchTo(0);
		}
		
		// re-adjust by events.
		if (typeof BAFontSizeObserver != 'undefined') {
			BASingleton(BAFontSizeObserver).addCallBack('onChange', this.adjustPaneHeight, this);
		}
		window.addEventListenerBA('resize', function() {
			var timer = arguments.callee.__timer__;
			if (timer) timer.clearTimer();
			if (!this.preventAdjust) {
				arguments.callee.__timer__ = new BASetTimeout(this.adjustPaneHeight, 10, this);
			}
		}, this);



		// set document status to 'tab view is enabled'
		new BAJL.Timeout(function() {
			$(document.body).addClass(BA_TABVIEW_ENABLED_CLASSNAME);
		}, 1);  // workaround for IE to avoid speed down

	}
}




})(jQuery);


















var BA_TABVIEW_AS_INSTANCES = [];



/* -------------------- Settings for BATabView -------------------- */

var BA_TABVIEW_BASENODE_NODENAME    = 'div';
var BA_TABVIEW_TABSNODE_CLASSNAME   = 'tabView-tabs';
var BA_TABVIEW_TABNODE_CLASSNAME    = 'tabView-tab';
var BA_TABVIEW_PANENODE_CLASSNAME   = 'tabView-pane';
var BA_TABVIEW_ENABLED_CLASSNAME    = 'tabView-enabled';
var BA_TABVIEW_SELECTED_CLASSNAME   = 'pseudo-selected';
var BA_TABVIEW_SINGLEPANE_CLASSNAME = 'has-single-pane';



/* -------------------- Settings for BATabViewAutoSetup -------------------- */

var BA_TABVIEW_AS_ENABLED      = true;
var BA_TABVIEW_AS_EXCEPT_MACIE = true;
var BA_TABVIEW_AS_SETTINGS     = {
	'tabView' : {
		adjustHeight    : true,
		rolloverSetting : {
			exclude   : 'norollover',
			statusSet : {
				'normal' : '',
				'stay'   : 's',
				'hover'  : 'o' 
			},
			handlers  : {
				'select'    : function(node, pane) { this.setStatus('stay'   ) },
				'unselect'  : function(node, pane) { this.setStatus('normal' ) },
				'mouseover' : function(node, pane) { this.setStatus(pane.isSelected ? 'stay' : 'hover' ) },
				'mouseout'  : function(node, pane) { this.setStatus(pane.isSelected ? 'stay' : 'normal') }
			}
		}
	}
};



/* -------------------- Constructor : BATabView -------------------- */
/**
 * provide tab view interface.
 * @class tab view
 * @constructor
 * @see BATabViewPane
 * @param {BAElement} node               base node for tab view interface (required)
 * @param {Boolean}   adjustHeight       flag to set all panes height to max height of the panes.
 * @param {Object}    rolloverSetting    rollover setting of tab image: associative array of 'exclude', 'statusSet', and 'handlers'.
 */
function BATabView(node, adjustHeight, rolloverSetting) {
	/** base block node for tab view interface.
	    @type BAElement @const @private */
	this.node            = node;
	/** array of TabView_Pane instances.
	    @type Array */
	this.panes           = [];
	/** auto adjust all panes' height?
	    @type Number @const @private */
	this.adjustHeight    = adjustHeight;
	/** ignoring flag of 'auto adjust height', workaround for WinIE6.
	    @type Boolean @private */
	this.preventAdjust   = false;
	/** auto adjust all panes' height?
	    @type Number @const @private */
	this.rolloverSetting = rolloverSetting;

	if (BA.env.isDOMReady) {
		this.init();
	}
}

/**
 * initialize, add panes automatically.
 * @private
 */
BATabView.prototype.init = function() {
	if (!this.node) {
		throw 'BATabView.init: base element node is not specified.';
	} else {
		BAAppendStateClassName(BA_TABVIEW_ENABLED_CLASSNAME);

		// temporary disable adjusting panes height.
		var adjHeightTemp = this.adjustHeight;
		this.adjustHeight = false;

		// search create pane instances.
		var cnodes = this.node.getChildNodesBA();
		var tabs   = null;
		for (var i = 0, n = cnodes.length; i < n && !tabs; i++) {
			var cnode = cnodes[i];
			if (cnode.nodeType == 1 && cnode.hasClassNameBA(BA_TABVIEW_TABSNODE_CLASSNAME)) {
				tabs = cnode;
			}
		}
		tabs.getElementsByClassNameBA(BA_TABVIEW_TABNODE_CLASSNAME).forEach(function(node) {
			this.addPane(new BATabViewPane(node, this.rolloverSetting));
		}, this);

		// restore original setting to adjust panes height.
		this.adjustHeight = adjHeightTemp;
		this.adjustPaneHeight();

		// if all panes are not selected, auto-select first pane.
		if (this.panes.every(function(pane) { return !pane.isSelected })) {
			this.switchTo(0);
		}
		
		// re-adjust by events.
		if (typeof BAFontSizeObserver != 'undefined') {
			BASingleton(BAFontSizeObserver).addCallBack('onChange', this.adjustPaneHeight, this);
		}
		window.addEventListenerBA('resize', function() {
			var timer = arguments.callee.__timer__;
			if (timer) timer.clearTimer();
			if (!this.preventAdjust) {
				arguments.callee.__timer__ = new BASetTimeout(this.adjustPaneHeight, 10, this);
			}
		}, this);
	}
}

/**
 * add pane instance.
 * @param {BATabViewPane} pane    pane instance to add
 */
BATabView.prototype.addPane = function(pane) {
	if (!pane /* || !(pane instanceof BATabViewPane) */) {
		throw 'BATabView.addPane: first argument must be a BATabViewPane instance.';
	} else {
		if (this.panes.indexOf(pane) < 0) {
			this.panes.push(pane);
			this.adjustPaneHeight();
			pane.addCallBack('onSelect', this.switchTo, this);
		}
		if (this.panes.length == 1) {
			this.node.appendClassNameBA(BA_TABVIEW_SINGLEPANE_CLASSNAME);
		} else {
			this.node.removeClassNameBA(BA_TABVIEW_SINGLEPANE_CLASSNAME);
		}
	}
}

/**
 * get max height of the panes (exclude 'tabs' part).
 * @return pixel value
 * @type Number
 */
BATabView.prototype.getPaneHeight = function() {
	return this.panes.map(function(pane) { return pane.getHeight() }).sort(function(a, b) { return a - b }).pop() || 0;
}

/**
 * set height of the all panes (exclude 'tabs' part).
 * @param {Number} height    pixel value to set
 */
BATabView.prototype.setPaneHeight = function(height) {
	if (typeof height != 'number' || height < 0) {
		throw 'BATabView.setPaneHeight: first argument must be a positive integer or 0.';
	} else {
		this.panes.forEach(function(pane) { pane.setHeight(height) });
	}
}

/**
 * set all panes height to max height of the panes.
 */
BATabView.prototype.adjustPaneHeight = function() {
	if (this.adjustHeight && !this.preventAdjust) {
		this.preventAdjust = true;
		new BASetTimeout(function() { this.preventAdjust = false }, 100, this);

		this.panes.forEach(function(pane) { pane.resetHeight() });
		this.setPaneHeight(this.getPaneHeight());
	}
}

/**
 * switch current visible pane.
 * @param {BATabViewPane} arg    pane instance to switch
 * @param {Number}        arg    index number of the pane to switch
 * @param {String}        arg    id string of the pane to switch
 */
BATabView.prototype.switchTo = function(arg) {
	if (typeof arg == 'string') {
		arg = this.panes.filter(function(pane) { return (pane.getId() == arg) })[0];
	}
	var idx = (typeof arg == 'number') ? Math.max(arg, -1) : this.panes.indexOf(arg);
	if (idx != -1) {
		this.panes.forEach(function(pane, num) {
			if (num == idx) {
				pane.select();
			} else {
				pane.unselect();
			}
		});
	}
}



/* -------------------- Constructor : BATabViewPane : inherits BAObservable -------------------- */
/**
 * provides pane that contain a pair of 'tab' and 'pane' for BATabView.
 * @class tab pane
 * @constructor
 * @see BATabView
 * @param {BAElement} tabNode            node for the 'tab' block: an anchor in this should indicate 'pane' block by #id (required).
 * @param {Object}    rolloverSetting    rollover setting of tab image: associative array of 'exclude', 'statusSet', and 'handlers'.
 */
function BATabViewPane(tabNode, rolloverSetting) {
	/** id string of this tab pane (this is same to fragment-id of the pane block).
	    @type String @private */
	this.id              = '';
	/** element node for the 'tab' block.
	    @type BAElement @const @private */
	this.tabNode         = tabNode;
	/** anchor node in the tab block.
	    @type BAElement @private */
	this.anchorNode      = null;
	/** element node for the pane block.
	    @type BAElement @private */
	this.paneNode        = null;
	/** setting data for tab image rollover: associative array of 'exclude', 'statusSet', and 'handlers'.
	    @type Object @private */
	this.rolloverSetting = rolloverSetting;
	/** BARollover instance to switch tab image.
	    @type BARollover @private */
	this.tabImage        = null;
	/** is this pane is currently visible?
	    @type Boolean */
	this.isSelected      = false;
	/** current height of this pane.
	    @type Number @private */
	this.paneHeight      = 0;

	if (BA.env.isDOMReady) {
		this.init();
	}
}

BATabViewPane.prototype = new BAObservable;

/**
 * initialize, add this pane to parent tab view.
 * @returns this instance.
 * @type BATabViewPane
 */
BATabViewPane.prototype.init = function() {
	if (!this.tabNode) {
		throw 'BATabViewPane.init: element for the \'tab\' block is not specified.';
	} else {
		var anchor = (this.tabNode.nodeName.toLowerCase() == 'a') ?
		             	this.tabNode :
		             	this.tabNode.getElementsByTagNameBA('a')[0];
		var href   = anchor.getAttributeBA('href').split('#')[1] || '';
		var pane   = (href) ? document.getElementByIdBA(href) : null;
		if (!pane) {
			throw 'BATabViewPane.init: element node for the pane block is not found.';
		} else {
			this.id         = href;
			this.anchorNode = anchor;
			this.paneNode   = pane;
			this.isSelected = this.tabNode.hasClassNameBA(BA_TABVIEW_SELECTED_CLASSNAME);
			this.anchorNode.addEventListenerBA('click', function(e) {
				this.doCallBack('onSelect', this);
				e.preventDefault();
			}, this);
			this.initRollover();
			this.paneNode.appendClassNameBA(BA_TABVIEW_PANENODE_CLASSNAME);

			if (this.isSelected) {
				this.doCallBack('onSelect', this);
			}

			// aware of a behavior of BASmoothScroll.
			if (typeof BASmoothScroll == 'function' && typeof BA_SMOOTHSCROLL_AS_IGNORE_NODE_CNAME == 'string') {
				this.anchorNode.appendClassNameBA(BA_SMOOTHSCROLL_AS_IGNORE_NODE_CNAME);
			}
		}
	}
}

/**
 * initialize rollover behavior of tab node.
 * @private
 */
BATabViewPane.prototype.initRollover = function() {
	if (typeof BARollover != 'function') {
		return;
	} else {
		var node      = this.anchorNode;
		var setting   = this.rolloverSetting;
		var statusSet = (setting && setting.statusSet) ? setting.statusSet : {};
		var exclude   = (setting && setting.exclude  ) ? setting.exclude   : '';
		var handlers  = (setting && setting.handlers ) ? setting.handlers  : {};

		this.tabImage = new BARollover(node, statusSet, exclude);
		for (var eventType in handlers) {
			node.addEventListenerBA(eventType, this.doRolloverCallBack, this);
		}
	}
}

/**
 * do rollover call callback.
 * @param {Event} e    event object.
 * @private
 */
BATabViewPane.prototype.doRolloverCallBack = function(e) {
	if (this.tabImage) {
		var type     = e.type;
		var node     = e.currentTarget;
		var setting  = this.rolloverSetting;
		var handlers = (setting && setting.handlers ) ? setting.handlers : {};
		if (handlers[type]) {
			handlers[type].call(this.tabImage, node, this);
		}
	}
}

/**
 * get id string of this tab pane.
 * @returns id
 * @type String
 */
BATabViewPane.prototype.getId = function() {
	return this.id;
}

/**
 * get height of the pane node.
 * @return pane node's offsetHeight in pixel value.
 * @type Number
 */
BATabViewPane.prototype.getHeight = function() {
	this.paneNode.appendClassNameBA(BA_TABVIEW_SELECTED_CLASSNAME);
	var height = this.paneNode.offsetHeight;
	if (!this.isSelected) {
		this.paneNode.removeClassNameBA(BA_TABVIEW_SELECTED_CLASSNAME);
	}
	return height;
}

/**
 * set height of the pane node.
 * @param {Number} height    pixel value to set
 * @param {Stfing} height    if 'auto' is given, set pane-node's style height to 'auto'
 * @return pane node's offsetHeight in pixel value.
 * @type Number
 */
BATabViewPane.prototype.setHeight = function(height) {
	if (typeof height != 'number' || height < 0) {
		throw 'BATabViewPane.setHeight: first argument must be a positive integer, 0, or \'auto\'.';
	} else {
		height = Math.max(height, 0);
		this.paneNode.style.height = height + 'px';

		height = Math.max(height * 2 - this.getHeight(), 0)
		this.paneNode.style.height = height + 'px';

		return (this.height = height);
	}
}

/**
 * reset pane height - set style height of the pane node to 'auto'.
 * @return pane node's offsetHeight in pixel value.
 * @type Number
 */
BATabViewPane.prototype.resetHeight = function() {
	this.paneNode.style.height = 'auto';
	return (this.height = this.getHeight());
}

/**
 * select this pane.
 */
BATabViewPane.prototype.select = function() {
	if (this.tabNode && this.paneNode) {
		this.isSelected = true;
		this.tabNode .appendClassNameBA(BA_TABVIEW_SELECTED_CLASSNAME);
		this.paneNode.appendClassNameBA(BA_TABVIEW_SELECTED_CLASSNAME);
		this.doRolloverCallBack({ type : 'select', currentTarget : this.anchorNode });
	}
}

/**
 * unselect this pane.
 */
BATabViewPane.prototype.unselect = function() {
	if (this.tabNode && this.paneNode) {
		this.isSelected = false;
		this.tabNode .removeClassNameBA(BA_TABVIEW_SELECTED_CLASSNAME);
		this.paneNode.removeClassNameBA(BA_TABVIEW_SELECTED_CLASSNAME);
		this.doRolloverCallBack({ type : 'unselect', currentTarget : this.anchorNode });
	}
}






/* -------------------- Function : BATabViewAutoSetup -------------------- */
/**
 * Auto setup tabViews.
 * @see BATabView
 */
function BATabViewAutoSetup() {
	if (BA.ua.isMacIE) {
		BA_TABVIEW_AS_ENABLED &= !BA_TABVIEW_AS_EXCEPT_MACIE;
	}
	if (BA_TABVIEW_AS_ENABLED && !BAAlreadyApplied(arguments.callee)) {
		var cssText = '${0}.${1} { visibility: hidden }';
		var sheet   = document.getStyleSheetsBA()[0];
		var tagName = BA_TABVIEW_BASENODE_NODENAME;
		if (sheet && !BA.ua.isMacIE) {
			for (var className in BA_TABVIEW_AS_SETTINGS) {
				sheet.addRuleBA(cssText.formatTextBA([tagName, className])); // addRuleBA() is unavailable in MacIE...
			}
		}

		BAAddOnload(function() {
			for (var className in BA_TABVIEW_AS_SETTINGS) {
				var setting = BA_TABVIEW_AS_SETTINGS[className];
				var tagName = BA_TABVIEW_BASENODE_NODENAME;
				document.getElementsByClassNameBA(className, tagName).forEach(function(node) {
					BA_TABVIEW_AS_INSTANCES.push(new BATabView(node, setting.adjustHeight, setting.rolloverSetting));
				});
			}
		});
	}
}






/* -------------------- Main : register start-up -------------------- */

if (typeof BA == 'object' && BA.ua.isDOMReady) {
	BATabViewAutoSetup();
}
