/* 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: PopupMenu.js 616 2013-04-22 23:48:38Z geert $
 */

"use strict";

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

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

	/**
	 * @class
	 * SUI.PopupMenu is a popupmenu as typically can be seen as context menu,
	 * or as drop down menu on a main menu bar. The items in the menu are
	 * linked to action handlers or to another (sub)menu.
	 *
	 * @augments SUI.Box
	 *
	 * @description
	 * Construct a popup menu. A large number of variables can be set
	 * to customize the listview to your specific needs.
	 *
	 * @constructs
	 * @param see base class
	 * @param {SUI.ActionList} arg.actionlist (optional) an action list with
	 *     the definition of the actions
	 * @param {object[]} arg.cols The menu item definition: an array with
	 *     objects which can contain the following fields:
	 * @param {boolean} arg.cols[].separator Is the item a separator
	 * @param {String} arg.cols[].icon The location of the icon for the menu
	 *     item
	 * @param {boolean} arg.cols[].disabled The enabled state of the menu item
	 * @param {String} arg.cols[].title The menu item text
	 * @param {String} arg.cols[].actionId An action identifier if an action
	 *     defined in an action list
	 * @param {Function} arg.cols[].handler A callback to execute on menu
	 *     item selection
	 * @param {object[]} arg.cols[].arg.items The item of a submenu (follows
	 *     same definition)
	 */
	initializer: function(arg) {

		SUI.PopupMenu.initializeBase(this, arg);

		// are we creating a sub menu
		this._main = arg.sub !== true;

		// is this menu associated with and action list
		this._actionList = arg.actionlist || null;

		// start with an empty items array
		this.items = [];

		this._buildControl(arg.items);
	},

	/**
	 * Width of the outer border of the menu
	 */
	BORDER_WIDTH: 1,

	/**
	 * Size (width and height) of the menu's icons
	 */
	ICON_SIZE: 16,

	/**
	 * Width of the area where the icons are displayed
	 */
	ICONBAR_WIDTH: 23,

	/**
	 * Border width of a menu item
	 */
	ITEM_BORDER_WIDTH: 1,

	/**
	 * The height (inclusive border) of a menu item
	 */
	ITEM_HEIGHT: 22,

	/**
	 * Padding of the menu items
	 */
	ITEM_PADDING: 2,

	/**
	 * Padding of the menu (4 sides and not the item padding)
	 */
	MENU_PADDING: 1,

	/**
	 * The height of the menu separator line
	 */
	SEPARATOR_LINE_HEIGHT: 1,

	/**
	 * The height of a menu separator
	 */
	SEPARATOR_HEIGHT: 5,

	/**
	 * Display the popup menu. Set the CSS size and position of the menu
	 * and all the items.
	 */
	display: function() {

		// set the CSS dimensions of the menu
		this.setDim();
		this.iconbar.setDim();

		// set the CSS dimensions of the menu items
		for (var i=0; i<this.items.length; i++) {
			this.items[i].bar.setDim();
			if (!this.items[i].separator) {
				this.items[i].titlediv.setDim();
			}
		}
	},

	/**
	 * Lay out the popup menu. Calculate the size and position of the
	 * menu and its items.
	 */
	layOut: function() {

		// calculate height
		this.height(this.border().height + this.padding().height);

		for (var i=0; i<this.items.length; i++) {
			if (this.items[i].separator) {
				this.height(this.height() + this.SEPARATOR_HEIGHT);
			} else {
				this.height(this.height() + this.ITEM_HEIGHT);
			}
		}

		// if the menu will drop off the screen correct the top and or left
		// of the menu.
		if (this.top() + this.height() > SUI.browser.viewportHeight) {
			this.top(SUI.browser.viewportHeight - this.height());
		}
		if (this.left() + this.width() > SUI.browser.viewportWidth) {
			this.left(SUI.browser.viewportWidth - this.width());
		}

		// get the size and position of the icon bar
		this.iconbar.setRect(0, 0, this.ICONBAR_WIDTH,
			this.clientHeight() + this.padding().height);

		var l = 0;
		var t = 0;

		// get the size an positions of the items
		for (var i=0; i<this.items.length; i++) {

			if (this.items[i].separator) {

				// get separator size and position
				this.items[i].bar.setRect(t,
					l + this.ICONBAR_WIDTH
						+ this.SEPARATOR_HEIGHT,
					this.clientWidth() - this.ICONBAR_WIDTH -
						- 2 * this.SEPARATOR_HEIGHT,
					(this.SEPARATOR_HEIGHT / 2 | 0)
						+ this.SEPARATOR_LINE_HEIGHT);

				t += this.SEPARATOR_HEIGHT;

			} else {

				// get menu item size and position
				this.items[i].bar.setRect(t, l,
					this.clientWidth(), this.ITEM_HEIGHT);
				this.items[i].titlediv.setRect(0, this.ICONBAR_WIDTH,
					this.clientWidth() - this.ICONBAR_WIDTH, this.ITEM_HEIGHT);

				t += this.ITEM_HEIGHT;

				// It the menu was displayed before the item can still have
				// the selected CSS class, so clear it
				this.items[i].bar.removeClass("sui-popup-item-selected");

				// It the menu was disabled before the item can still have
				// the disabled CSS class, so clear it
				this.items[i].bar.removeClass("sui-popup-item-disabled");
				if (SUI.browser.isIE) {
					this.items[i].titlediv.removeClass(
						"sui-popup-item-disabled");
				}
				// if the item is not enabled set the disabled CSS style
				if (!this._isEnabled(this.items[i])) {
					this.items[i].bar.addClass("sui-popup-item-disabled");
					if (SUI.browser.isIE) {
						this.items[i].titlediv.addClass(
							"sui-popup-item-disabled");
					}
				}

			}
		}
	},

	/**
	 * Remove the currently active menu from the screen. Note that this
	 * function normally will be called from the framework and that there is
	 * no need to call it yourself. Note also that it removes the currently
	 * active menu, so not necessarily the one that you have created.
	 * @param {int} top Top position of the menu
	 * @param {int} left Left position of the menu
	 */
	removeMenu: function() {
		// if there is an active menu
		if (SUI.PopupMenu.activeMenu) {
			// remove it and ...
			SUI.PopupMenu.activeMenu._removeMenu();
			// ... call the onRemoveMenu changed event handler
			SUI.PopupMenu.activeMenu.callListener("onRemoveMenu");
			SUI.PopupMenu.activeMenu = null;
		}
	},

	/**
	 * Show the popup menu at the specified location.
	 * @param {int} top Top position of the menu
	 * @param {int} left Left position of the menu
	 */
	showMenu: function(top, left) {

		// if we're drawing the main(top) menu we'll need to take care of
		// some extra stuff to. We'll assign the menu to a static variable
		// because there only can be one menu active at the same time. So
		// if the static is already set, we'll remove the menu first.
		if (this._main) {
			// there can be only one popupmenu, remove the old one first
			this.removeMenu();
			// add a event listener on the document to catch the clicks next
			// to the menu and remove it
			var that = this;
			SUI.browser.addEventListener(document, "mousedown",
				function (e) {
					if (!that.removeMenu()) {
						SUI.browser.noPropagation(e);
					}
				}
			);
			// make this menu the active submenu
			SUI.PopupMenu.activeMenu = this;
		}

		// Add the menu to the document body ...
		// TODO: look for a better attachment point
		this.parent({el: function() { return document.body; }});

		// ... and draw the popup at the required position
		this.left(left);
		this.top(top);
		this.draw();
	},

	/**
	 * onRemoveMenu event handler: is executed when the the menu is removed.
	 */
	onRemoveMenu: function() {
	},

	_activeSubmenuItem: null,

	/* Set event handlers of a menu item:
	 * mousedown => _selectItem;
	 * mouseover => _highlightItem;
	 * mouseout => _restoreItem
	 */
	_addEventHandlers: function(item) {

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

		// Do _selectItem on the mousedown event of a menu item.
		SUI.browser.addEventListener(item.bar.el(), "mousedown",
			function(e) {
				if (!that._selectItem(item)) {
					SUI.browser.noPropagation(e);
				}
			}
		);

		// Do _highlightItem on the mouseover event of a menu item.
		SUI.browser.addEventListener(item.bar.el(), "mouseover",
			function(e) {
				if (!that._highlightItem(item)) {
					SUI.browser.noPropagation(e);
				}
			}
		);

		// Do _restoreItem on the mouseout event of a menu item.
		SUI.browser.addEventListener(item.bar.el(), "mouseout",
			function(e) {
				if (!that._restoreItem(item)) {
					SUI.browser.noPropagation(e);
				}
			}
		);

	},

	/* Make all required boxes for the popup menu, set event handlers and
	 * target actions
	 */
	_buildControl: function(items) {

		// set the style for the control
		this.border(new SUI.Border(this.BORDER_WIDTH));
		this.padding(new SUI.Padding(this.MENU_PADDING));
		this.addClass("sui-popup");

		// create and area for the icons
		this.iconbar = new SUI.Box({parent: this});
		this.iconbar.addClass("sui-popup-iconbar");

		var hasSub = false;

		// loop through all the items
		for (var i=0; i<items.length; i++) {

			// create a default item profile
			var item = {
				separator: items[i].separator || false,
				icon: items[i].icon || null,
				enabled: !items[i].disabled || false,
				title: items[i].title || "Item "+i,
				sub: false
			};

			// is this item associated with an action in an action list ...
			if (items[i].actionId !== undefined && this._actionList) {

				// ... yes, get the data for the menu item from the action list
				var a = this._actionList.get(items[i].actionId);
				item.actionId = items[i].actionId;
				item.title = a.title;
				item.icon = a.icon;
				item.handler = a.handler;

			} else if (items[i].handler !== undefined) {

				// ... else if a handler was given use that one
				item.handler = items[i].handler;

			} else if (items[i].items !== undefined) {

				// ... else create a submenu if submenu data
				items[i].sub = true;
				item.submenu = new SUI.PopupMenu(items[i]);
				hasSub = true;

			}

			// add the item to the menu's item array
			this.items.push(this._createItem(item));
		}

		// set the width of the menu to the current width (width of the
		// longest text) plus additional elements.
		this.width(this.width() + this.ICONBAR_WIDTH
			+ 2 * (this.ITEM_PADDING + this.MENU_PADDING
			+ this.ITEM_BORDER_WIDTH + this.BORDER_WIDTH)
			+ (hasSub ? this.ICON_SIZE : 0));

	},

	/* Create the boxes for the popup menu item.
	 */
	_createItem: function(item) {

		if (item.separator) {

			// create a box for a separator line
			item.bar = new SUI.Box({parent: this});
			item.bar.border(new SUI.Border(0, 0, this.SEPARATOR_LINE_HEIGHT));
			item.bar.addClass("sui-popup-separator");

		} else {

			// create a box for the popup menu item
			item.bar = new SUI.Box({parent: this});
			item.bar.border(new SUI.Border(this.ITEM_BORDER_WIDTH));
			item.bar.addClass("sui-popup-item");

			// and a inner box for the menu item text
			item.titlediv = new SUI.Box({parent: item.bar});
			item.titlediv.padding(
				new SUI.Padding(this.ITEM_PADDING));
			item.titlediv.el().innerHTML = item.title;

			// if the items as a sub-menu set the sub-menu marker
			if (item.submenu) {
				item.titlediv.el().style.backgroundImage =
					"url("+SUI.imgDir+"/"+SUI.resource.pmSub+")";
			}

			// if the item has an icon add that to the menu
			if (item.icon) {
				var img = document.createElement("IMG");
				img.src = SUI.imgDir + "/" + item.icon;
				img.style.padding = this.ITEM_PADDING + "px";
				item.bar.el().appendChild(img);
			}

			// set the event handlers
			this._addEventHandlers(item);
		}

		// get the text width of the item text ...
		var iw = SUI.style.textLength(item.title);
		// ... and if it is wider that the current text ...
		if (iw > this.width()) {
			// ... use that width
			this.width(iw);
		}

		return item;
	},

	/* Highlight a menu item.
	 */
	_highlightItem: function(item) {

		// highlighting is only possible if an item is enabled
		if (this._isEnabled(item)) {

			// if there is a submenu shown and we want to highlight another
			// menu item ...
			if (this._activeSubmenuItem && this._activeSubmenuItem !== item) {
				// ... then remove it ...
				this._activeSubmenuItem.submenu._removeMenu();
				/// ... and clear the highlight
				this._activeSubmenuItem.bar.removeClass(
					"sui-popup-item-selected");
				this._activeSubmenuItem = null;
			}

			// add the highlight to the requested item
			item.bar.addClass("sui-popup-item-selected");

			// if the item as a submenu them show it
			if (item.submenu && !this._activeSubmenuItem) {
				var top = this.top() + item.bar.top() - this.BORDER_WIDTH;
				var left = this.left() + this.clientWidth();
				item.submenu.showMenu(top, left);
				this._activeSubmenuItem = item;
			}
		}

	},

	/* Is a menu item eabled.
	 */
	_isEnabled: function(item) {
		// if a action list was used then get the enabled state from the
		// action list, else use its local setting
		return item.actionId
			? this._actionList.get(item.actionId).enabled : item.enabled;
	},

	/* Remove the menu and it currently shown submenus.
	 */
	_removeMenu: function() {
		// if a submenu was shown ...
		if (this._activeSubmenuItem) {
			// ... recurse into the submenu
			this._activeSubmenuItem.submenu._removeMenu();
			this._activeSubmenuItem = null;
		}
		// remove the menu from the document tree
		this.removeBox();
	},

	/* Remove the highlight from the menu item.
	 */
	_restoreItem: function(item)  {
		// remove the highlight only if we move to another menu item and the
		// currently highlighted one is not a submenu (in that case we'll
		// remove the highlight not until we highlighting the new menu item)
		if (this._activeSubmenuItem !== item) {
			item.bar.removeClass("sui-popup-item-selected");
		}
	},

	/* Select an item, execute the handler if the item is not a submenu
	 * and remove the popup.
	 */
	_selectItem: function(item)  {
		// if the item is enabled and not a submenu ...
		if (this._isEnabled(item) && !item.submenu) {
			// ... execute the handler and remove the popup.
			this.removeMenu();
			item.handler(this);
		}
	}

});