/* Copyright (c) 2011, Geert Bergman (geert@scrivo.nl)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of "Scrivo" nor the names of its contributors may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: TabPanel.js 616 2013-04-22 23:48:38Z geert $
 */

"use strict";

SUI.TabPanel = SUI.defineClass(
	/** @lends SUI.TabPanel.prototype */{

	/** @ignore */ baseClass: SUI.Box,

	/**
	 * @class
	 * SUI.TabPanel is a component that helps you to save space by stacking a
	 * number of panels/boxes and let the user swap them by clicking on the
	 * tabs. A SUI.TapPanel can host a number of SUI boxes, each of them has
	 * a tab with a title for the user to identify and select them.
	 * TODO: create a separate Tab class
	 *
	 * @augments SUI.Box
	 *
	 * @description
	 * Construct a SUI.TabPanel object. The titles of the tabs and  (optional)
	 * the client boxes are given as argument to this constructor.
	 * It is also possible to select the initial tab.
	 *
	 * @constructs
	 * @param see base class
	 * @param {object[]} items An object array containing objects with the
	 *    following members:
	 * @param {String} items[].title Tab title
	 * @param {SUI.Box} items[].box A client box (optional)
	 * @param {int} selected Index of tab that should be selected initially
	 *      (optional)
	 * @exception {String} If there is no valid initial selection for the
	 *     default client area
	 */
	initializer: function(arg) {

		// anchors default to all sides
		if (!arg.anchor) {
			arg.anchor = {left:true,right:true,top:true,bottom:true};
		}

		SUI.TabPanel.initializeBase(this, arg);

		if (arg.onSelectTab) {
			this.addListener("onSelectTab", arg.onSelectTab);
		}

		// create the boxes for the tab panel and add the event handlers
		this._buildControl(arg);
	},

	/**
	 * Padding of the content on the tab.
	 */
	PANEL_PADDING: 4,

	/**
	 * Border width of the line under the tab strip.
	 */
	TAB_BORDER_BOTTOM_WIDTH: 1,

	/**
	 * Border width of the lines of the tab.
	 */
	TAB_BORDER_WIDTH: 1,

	/**
	 * Height of the highlight strip on top of a selected tab.
	 */
	TAB_HILIGHT_HEIGHT: 5,

	/**
	 * Top margin of unselected tabs.
	 */
	TAB_MARGIN_TOP: 3,

	/**
	 * Height of the tab bar (including bottom border).
	 */
	TABBAR_HEIGHT : 29,

	/**
	 * Top padding of the text in the tabs.
	 */
	TABTEXT_PADDING_TOP: 5,

	/**
	 * Left padding of the text in the tabs.
	 */
	TABTEXT_PADDING_LEFT: 7,

	/**
	 * Left position of the image in a scroller button.
	 */
	SCROLL_IMG_PADDING_LEFT: 1,

	/**
	 * Top position of the image in a scroller button.
	 */
	SCROLL_IMG_PADDING_TOP: 5,

	/**
	 * Width of a the scroller button.
	 */
	SCROLLER_WIDTH: 20,

	/**
	 * Margin top of the selected tab.
	 */
	SELTAB_MARGIN_TOP: 0,

	/**
	 * Add a box to one of the client areas of the control.
	 * @param {SUI.Box} child The box to add to the control
	 * @param {int} i Index position of the client container to at the box to
	 */
	add: function(child, i) {
		this._tabs[i].content.add(child);
	},

	/**
	 * Display the tab panel control. Set the CSS size and position of the tabs
	 * and the currently displayed content panel.
	 */
	display: function() {

		if (this.width() <=0 || this.height() <=0) {
			return;
		}

		this.setDim();

		// set the CSS dimensions of the header
		this.tabstripvpt.setDim();
		this.tabstrip.setDim();

		// set the CSS dimensions of the tabs
		for (var i=0; i<this._tabs.length; i++) {
			this._tabs[i].tab.setDim();
			this._tabs[i].tabtext.setDim();
		}

		// set the CSS dimensions of the client area and its contents
		this.clientArea.setDim();
		this._selectedTab.content.display();

		// set the CSS dimensions of the tab highlight and the scrollers
		this.highlight.setDim();
		this.scrollRight.setDim();
		this.scrollLeft.setDim();
	},

	/**
	 * Lay out the tab panel control. Calculate the size and position of the
	 * tabs and client area.
	 */
	layOut: function() {

		// set the size of the tabstrip viewport
		this.tabstripvpt.setRect(0, 0, this.width(), this.TABBAR_HEIGHT);

		// set the size of the client area
		this.clientArea.setRect(this.TABBAR_HEIGHT, 0, this.width(),
			this.height()-this.TABBAR_HEIGHT);

		for (var i=0,l=0; i<this._tabs.length; i++) {

			// set the left and width of the tab
			this._tabs[i].tab.left(l);
			this._tabs[i].tab.width(this._tabs[i].textLength +
				2 * this.TABTEXT_PADDING_LEFT + 2 * this.TAB_BORDER_WIDTH);

			// set the size and position of the text box
			this._tabs[i].tabtext.setRect(
				this.TABTEXT_PADDING_TOP, this.TABTEXT_PADDING_LEFT,
				this._tabs[i].textLength, this._tabHeight()
				- this.TABTEXT_PADDING_TOP - this.TAB_BORDER_WIDTH);

			// set the size and position of the content container
			this._tabs[i].content.setRect(0,0,
				this.clientArea.clientWidth(), this.clientArea.clientHeight());

			// it this is the currently selected tab ...
			if (this._selectedTab === this._tabs[i]) {
				// ... layout the selected tab ...
				this._layOutSelectedTab(this._selectedTab);
				// ... and layout the the tab's contents
				this._tabs[i].content.layOut();
			} else {
				// ... else do the normal tab layout
				this._layOutNormalTab(this._tabs[i]);
			}

			// get the left of the next tab
			l += this._tabs[i].tab.width()-this.TAB_BORDER_WIDTH;
		}

		// set the width of the tab strop
		this.tabstrip.setRect(0, 0, l+this.TAB_BORDER_WIDTH,
			this.TABBAR_HEIGHT);

		// check if we need to draw scroller buttons ...
		if (this.tabstripvpt.width() < this.tabstrip.width()) {

			// ... yes: show the scrollers ...
			this.scrollRight.el().style.display = "block";
			this.scrollLeft.el().style.display = "block";

			// ... set their sizes and positions ...
			this.scrollRight.setRect(this.TAB_MARGIN_TOP,
				this.width() - this.SCROLLER_WIDTH, this.SCROLLER_WIDTH,
				this.TABBAR_HEIGHT - this.TAB_MARGIN_TOP);
			this.scrollLeft.setRect(this.scrollRight);
			this.scrollLeft.left(this.scrollLeft.left()
					- this.SCROLLER_WIDTH + this.TAB_BORDER_WIDTH);

			// ... add some extra width for the scrollers ...
			this.tabstrip.width(this.tabstrip.width()
				+ this.SCROLLER_WIDTH * 2);
			// ... and enable the scrollers.
			this._enableScrollers();

		} else {

			// ... no: hide the scrollers ...
			this.scrollRight.el().style.display = "none";
			this.scrollLeft.el().style.display = "none";
			// ... and the width of the tabstrip to its viewport
			this.tabstrip.width(this.tabstripvpt.width());
		}
	},

	/**
	 * onSelectTab event handler: is executed when the user clicks on a tab.
	 */
	onSelectTab: function() {
	},

	/**
	 * Get the top, left, right and bottom offset of the client area
	 * relative to the outer dimensions of the tab panel.
	 */
	clientAreaPosition: function() {
		return {
			top: this.TABBAR_HEIGHT + this.clientArea.border().top
				+ this.clientArea.padding().top,
			left: this.clientArea.border().left
				+ this.clientArea.padding().left,
			right: this.clientArea.border().right
				+ this.clientArea.padding().right,
			bottom: this.clientArea.border().bottom
				+ this.clientArea.padding().bottom
		};
	},

	/**
	 * Set or get the selected tab.
	 * @param {Object} tab (optional) the tab to set the selected tab to.
	 * @return {Object} the selected tab (null if method was used as setter)
	 */
	selectedTab: function(tab) {
		return tab !== undefined ? (this._selectedTab = tab) && null
			: this._selectedTab;
	},

	/**
	 * Set or get the index of the selected tab.
	 * @param {int} i (optional) the index to set the index of the selected
	 *    tab to.
	 * @return {int} the index of the selected tab (null if method was used
	 *    as setter)
	 */
	selectedTabIndex: function(i) {
		return i !== undefined
			? (this._selectedTab = this._tabs[i]) && null
			: this._tabs.indexOf(this._selectedTab);
	},

	/**
	 * Select a tab. Set the selected tab call the onSelectTab listener and
	 * draw it. Note: this method is probably most usefull when overriding, not
	 * to call it on a tab object directly.
	 * @param {Object} tab Tab to select
	 */
	selectTab: function(tab) {
		this._selectedTab = tab;
		this.callListener("onSelectTab", tab);
		this.draw();
	},

	// reference to the the selected tab
	_selectedTab: null,

	/* Add the onclick event handler on the tab
	 */
	_addOnClickTab: function(tab) {
		var that = this;
		// 'that' and 'tab' are two closure variables
		SUI.browser.addEventListener(tab.tab.el(), "click",
			function(e) {
				if (!that.selectTab(tab)) {
					SUI.browser.noPropagation(e);
				}
			}
		);
	},

	/* Make all required boxes for the control, set the CSS styles and add
	 * the event handlers.
	 */
	_buildControl: function(arg) {

		// start with an empty tab list
		this._tabs = [];

		// add the CSS class to the main box
		this.addClass("sui-tp-tabpanel");

		// create a viewport to scroll the tab strip
		this.tabstripvpt = new SUI.Box({parent: this});
		this.tabstripvpt.el().style.overflow = "hidden";
		this.tabstripvpt.addClass("sui-tp-tabstripvpt");

		// create the tap strip and add it to the viewport
		this.tabstrip = new SUI.Box({parent: this.tabstripvpt});
		this.tabstrip.border(
		 new SUI.Border(0, 0, this.TAB_BORDER_BOTTOM_WIDTH, 0));
		this.tabstrip.addClass("sui-tp-tabstrip");

		// create the content area to host the tab containers
		this.clientArea = new SUI.Box({parent: this});
		this.clientArea.addClass("sui-tp-border");
		this.clientArea.border(
		 new SUI.Border(0, this.TAB_BORDER_WIDTH, this.TAB_BORDER_WIDTH));
		this.clientArea.padding(
			new SUI.Padding(arg.panelMargin || this.PANEL_PADDING));

		// read in the items form the arguments object and store them in
		// a standardized way in the tabs list
		for (var i=0; i<arg.tabs.length; i++) {

			// start with a default profile
			var tab = {
				title: "Item "+i,
				box: null
			};
			for (var prop in arg.tabs[i]) {
				// and overwrite the tabault profile with the entries set
				// in the arguments
				if (arg.tabs[i].hasOwnProperty(prop)) {
					tab[prop] = arg.tabs[i][prop];
				}
			}

			// create boxes for the tab and set the event handler
			this._createTab(tab);

			// is this the selected tab ...
			if (i === arg.selected) {
				// ... yes, then select it
				this._selectedTab = tab;
			}

			this._tabs.push(tab);

			// if there is already content, then add it to the container
			if (tab.box) {
				this.add(tab.box, i);
			}
		}

		// create a little box on top of the tab to serve as highlight
		this.highlight = new SUI.Box({parent: this.tabstripvpt});
		this.highlight.border(
			new SUI.Border(this.TAB_BORDER_WIDTH));
		this.highlight.addClass("sui-tp-tabhighlight");

		// if no selected tab was given in the arguments ...
		if (!this._selectedTab && this._tabs.length) {
			// ... set the currently selected tab to first
			this._selectedTab = this._tabs[0];
		}

		if (!this._selectedTab) {
			throw "SUI.TabPanel: index for selected tab out of range";
		}

		// add the to scrollers that are needed of there are too many tabs
		// for the available width
		this.scrollLeft = this._createScroller(
			SUI.resource.tpScrollLeft, this._scrollLeft);
		this.scrollRight = this._createScroller(
			SUI.resource.tpScrollRight, this._scrollRight);

	},

	/* Create boxes for the tab and set the event handlers.
	 */
	_createTab: function(tab) {

		// create the tab
		tab.tab = new SUI.Box({parent: this.tabstripvpt});
		tab.tab.addClass("sui-tp-tab");
		tab.tab.border(new SUI.Border(this.TAB_BORDER_WIDTH,
		 this.TAB_BORDER_WIDTH, 0, this.TAB_BORDER_WIDTH));

		// store the length of the text on the tab
		tab.textLength = SUI.style.textLength(tab.title);

		// create a box for the tab text
		tab.tabtext = new SUI.TextBox({
			parent: tab.tab,
			text: tab.title
		});

		// create the content container for the tab
		tab.content = new SUI.AnchorLayout({parent: this.clientArea});
		tab.content.addClass("sui-tp-tabcontent");
		tab.content.el().style.overflow = "auto";
		tab.content.el().style.backgroundColor = "transparent";
		tab.content.el().style.display = "none";

		// add the onclick event handler for the tab
		this._addOnClickTab(tab);
	},

	/* Create a scroller button that is needed to scroll the tap strip
	 * if there are too many tabs for the available space.
	 */
	_createScroller: function(icon, fn) {

		// 'that' and 'fn' are the two closure variables
		var that = this;

		// create a box for the scroller
		var scroller = new SUI.Box({parent: this});
		scroller.addClass("sui-tp-scroller");
		scroller.border(new SUI.Border(this.TAB_BORDER_WIDTH,
		 this.TAB_BORDER_WIDTH, this.TAB_BORDER_BOTTOM_WIDTH));

		// and append an icon to it
		var img = document.createElement("IMG");
		img.src = SUI.imgDir + "/" + icon;
		img.style.marginTop = this.SCROLL_IMG_PADDING_TOP + "px";
		img.style.marginLeft = this.SCROLL_IMG_PADDING_LEFT + "px";
		scroller.el().appendChild(img);

		// add a handler to the onclick event of the box
		SUI.browser.addEventListener(scroller.el(), "click",
			function(e) {
				if (!fn.call(that)) {
					SUI.browser.noPropagation(e);
				}
			}
		);

		return scroller;
	},

	/* Enable the scrollers depending on the current scroll position
	 * of the tab strip in its viewport.
	 */
	_enableScrollers: function() {

		// if there is a left scroll offset enable the left scroller
		if (this.tabstripvpt.el().scrollLeft) {
			this.scrollLeft.removeClass("sui-tp-scroller-disabled");
		} else {
			this.scrollLeft.addClass("sui-tp-scroller-disabled");
		}

		// compare the distance of the left scroller with the right of
		// the last tab. If it is smaller enable the right scroller
		if (this._tabRight(this._tabs[this._tabs.length-1])
				< this._leftScrollerLeft()) {
			// ... disable the scroller
			this.scrollRight.addClass("sui-tp-scroller-disabled");
		} else {
			this.scrollRight.removeClass("sui-tp-scroller-disabled");
		}
	},

	/* Lay out a normal tab
	 */
	_layOutNormalTab: function(tab) {
		// Remove CSS class
		tab.tab.removeClass("sui-tp-tabselected");
		// set the dimensions
		tab.tab.setRect(this.TAB_MARGIN_TOP, tab.tab.left(), tab.tab.width(),
			this._tabHeight());
		tab.tabtext.top(this.TABTEXT_PADDING_TOP);
		// hide the tab content and highlight bar
		tab.content.el().style.display = "none";
	},

	/* Lay out a a selected tab
	 */
	_layOutSelectedTab: function(tab) {
		// Add CSS class
		tab.tab.addClass("sui-tp-tabselected");
		// set the dimensions
		tab.tab.setRect(this.SELTAB_MARGIN_TOP, tab.tab.left(),
			tab.tab.width(), this.TABBAR_HEIGHT - this.SELTAB_MARGIN_TOP);
		tab.tabtext.top(this.TABTEXT_PADDING_TOP + this.TAB_MARGIN_TOP
			- this.SELTAB_MARGIN_TOP);
		// show the tab content and highlight bar
		tab.content.el().style.display = "block";
		// set the tab highlight
		this.highlight.setRect(tab.tab);
		this.highlight.height(this.TAB_HILIGHT_HEIGHT);
	},

	/* Get the distance of the left scroller to the left side of the component.
	 */
	_leftScrollerLeft: function() {
		return this.tabstripvpt.el().scrollLeft + this.scrollLeft.left();
	},

	/* Scroll one tab to the left.
	 */
	_scrollLeft: function() {

		// find the tab of which the right side is larger or equeal to the left
		// side of the left scroller
		for (var i=0; i<this._tabs.length; i++) {
			if (this._tabRight(this._tabs[i]) >= this._leftScrollerLeft()) {
				break;
			}
		}

		// set the scroll-left of the vieport so that found tab's left
		// equals the left of the scroller
		this.tabstripvpt.el().scrollLeft = this._tabs[i].tab.left()
			- this.scrollLeft.left() + this.TAB_BORDER_WIDTH;

		this._enableScrollers();
	},

	/* Scroll one tab to the right.
	 */
	_scrollRight: function() {

		// find the tab of which the right side is further that the left
		// side of the left scroller
		for (var i=0; i<this._tabs.length-1; i++) {
			if (this._tabRight(this._tabs[i]) > this._leftScrollerLeft()) {
				break;
			}
		}

		// set the scroll-left of the viewport so that the found tab will
		// be adjacent to the left scroller
		this.tabstripvpt.el().scrollLeft =
			this._tabRight(this._tabs[i]) - this.scrollLeft.left();

		this._enableScrollers();
	},

	/* Height of an unselected tab (including top border but excluding bottom
	 * border)
	 */
	_tabHeight: function() {
		return this.TABBAR_HEIGHT - this.TAB_BORDER_BOTTOM_WIDTH
			 - this.TAB_MARGIN_TOP;
	},

	/* Get the position of the right side of a tab.
	 */
	_tabRight: function(tab) {
		return tab.tab.left() + tab.tab.width();
	}

});