/**
 * eGroupWare egw_action framework - JS Menu abstraction
 *
 * @link http://www.egroupware.org
 * @author Andreas Stöckel <as@stylite.de>
 * @copyright 2011 by Andreas Stöckel
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package egw_action
 *
 * @Todo: @new-js-loader port to TypeScript
 */
import {egwMenuImpl} from './egw_menu_dhtmlx.js';
import {egw_shortcutIdx} from './egw_keymanager.js';
import {
	EGW_KEY_ARROW_DOWN,
	EGW_KEY_ARROW_LEFT,
	EGW_KEY_ARROW_RIGHT,
	EGW_KEY_ARROW_UP, EGW_KEY_ENTER,
	EGW_KEY_ESCAPE
} from "./egw_action_constants.js";
//Global variable which is used to store the currently active menu so that it
//may be closed when another menu openes
export var _egw_active_menu = null;

/**
 * Internal function which generates a menu item with the given parameters as used
 * in e.g. the egwMenu.addItem function.
 */
//TODO Icons: write PHP GD script which is cabable of generating the menu icons in various states (disabled, highlighted)
function _egwGenMenuItem(_parent, _id, _caption, _iconUrl, _onClick)
{
	//Preset the parameters
	if (typeof _parent == "undefined")
		_parent = null;
	if (typeof _id == "undefined")
		_id = "";
	if (typeof _caption == "undefined")
		_caption = "";
	if (typeof _iconUrl == "undefined")
		_iconUrl = "";
	if (typeof _onClick == "undefined")
		_onClick = null;

	//Create a menu item with no parent (null) and set the given parameters
	var item = new egwMenuItem(_parent, _id);
	item.set_caption(_caption);
	item.set_iconUrl(_iconUrl);
	item.set_onClick(_onClick);

	return item;
}

/**
 * Internal function which parses the given menu tree in _elements and adds the 
 * elements to the given parent.
 */
function _egwGenMenuStructure(_elements, _parent)
{
	var items = [];

	//Go through each object in the elements array
	for (var i = 0; i < _elements.length; i++)
	{
		//Go through each key of the current object
		var obj = _elements[i];
		var item = new egwMenuItem(_parent, null);
		for (var key in obj)
		{
			if (key == "children" && obj[key].constructor === Array)
			{
				//Recursively load the children.
				item.children = _egwGenMenuStructure(obj[key], item);
			}
			else
			{
				//Directly set the other keys
				//TODO Sanity neccessary checks here?
				//TODO Implement menu item getters?
				if (key == "id" || key == "caption" || key == "iconUrl" ||
				    key == "checkbox" || key == "checked" || key == "groupIndex" ||
				    key == "enabled" || key == "default" || key == "onClick" ||
				    key == "hint" || key == "shortcutCaption")
				{
					item['set_' + key](obj[key]);
				}
			}
		}

		items.push(item);
	}

	return items;
}

/**
 * Internal function which searches for the given ID inside an element tree.
 */
function _egwSearchMenuItem(_elements, _id)
{
	for (var i = 0; i < _elements.length; i++)
	{
		if (_elements[i].id === _id)
			return _elements[i];

		var item = _egwSearchMenuItem(_elements[i].children, _id);
		if (item)
			return item;
	}

	return null;
}

/**
 * Internal function which alows to set the onClick handler of multiple menu items
 */
function _egwSetMenuOnClick(_elements, _onClick)
{
	for (var i = 0; i < _elements.length; i++)
	{
		if (_elements[i].onClick === null)
		{
			_elements[i].onClick = _onClick;
		}
		_egwSetMenuOnClick(_elements[i].children, _onClick);
	}
}

/**
 * Constructor for the egwMenu object. The egwMenu object is a abstract representation
 * of a context/popup menu. The actual generation of the menu can by done by so
 * called menu implementations. Those are activated by simply including the JS file
 * of such an implementation.
 *
 * The currently available implementation is the "egwDhtmlxMenu.js" which is based
 * upon the dhtmlxmenu component.
 */
export function egwMenu()
{
	//The "items" variable contains all menu items of the menu
	this.children = [];

	//The "instance" variable contains the currently opened instance. There may
	//only be one instance opened at a time.
	this.instance = null;
}

/**
 * The private _checkImpl function checks whether a menu implementation is available.
 *
 * @returns bool whether a menu implemenation is available.
 */
egwMenu.prototype._checkImpl = function()
{
	return typeof egwMenuImpl == 'function';
}

/**
 * The showAtElement function shows the menu at the given screen position in an
 * (hopefully) optimal orientation. There can only be one instance of the menu opened at
 * one time and the menu implementation should care that there is only one menu
 * opened globaly at all.
 *
 * @param int _x is the x position at which the menu will be opened
 * @param int _y is the y position at which the menu will be opened
 * @param bool _force if true, the menu will be reopened at the given position,
 * 	even if it already had been opened. Defaults to false.
 * @returns bool whether the menu had been opened
 */
egwMenu.prototype.showAt = function(_x, _y, _force)
{
	if (typeof _force == "undefined")
		_force = false;

	//Hide any other currently active menu
	if (_egw_active_menu != null)
	{
		if (_egw_active_menu == this && !_force)
		{
			this.hide();
			return false;
		}
		else
		{
			_egw_active_menu.hide();
		}
	}

	if (this.instance == null && this._checkImpl)
	{
		//Obtain a new egwMenuImpl object and pass this instance to it
		this.instance = new egwMenuImpl(this.children);

		_egw_active_menu = this;

		var self = this;
		this.instance.showAt(_x, _y, function() {
			self.instance = null;
			_egw_active_menu = null;
		});
		return true;
	}

	return false;
}

/**
 * Keyhandler to allow keyboard navigation of menu
 *
 * @return boolean true if we dealt with the keypress
 */
egwMenu.prototype.keyHandler = function(_keyCode, _shift, _ctrl, _alt)
{
	// Let main keyhandler deal with shortcuts
	var idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt);
	if (typeof egw_registeredShortcuts[idx] !== "undefined")
	{
		return false;
	}

	let current = this.instance.dhtmlxmenu.menuSelected;
	if(current !== -1)
	{
		let find_func = function(child) {
			if( child.id === current.replace(this.instance.dhtmlxmenu.idPrefix, ""))
			{
				return child;
			}
			else if (child.children)
			{
				for(let i = 0; i < child.children.length; i++)
				{
					const result = find_func(child.children[i]);
					if(result) return result;
				}
			}
			return null;
		}.bind(this);
		current = find_func(this);
	}
	else
	{
		current = this.children[0];
		jQuery("#"+this.instance.dhtmlxmenu.idPrefix + current.id).trigger("mouseover");
		return true;
	}

	switch (_keyCode) {
		case EGW_KEY_ENTER:
			jQuery("#"+this.instance.dhtmlxmenu.idPrefix + current.id).trigger("click");
			return true;
		case EGW_KEY_ESCAPE:
			this.hide();
			return true;
		case EGW_KEY_ARROW_RIGHT:
			if(current.children)
			{
				current = current.children[0];
			}
			break;
		case EGW_KEY_ARROW_LEFT:
			if(current.parent && current.parent !== this)
			{
				current = current.parent;
			}
			break;
		case EGW_KEY_ARROW_UP:
		case EGW_KEY_ARROW_DOWN:
			const direction = _keyCode === EGW_KEY_ARROW_DOWN ? 1 : -1;
			let parent = current.parent;
			let index = parent.children.indexOf(current);
			let cont = false;

			// Don't run off ends, skip disabled
			do
			{
				index += direction;
				cont = !parent.children[index] || !parent.children[index].enabled || !parent.children[index].id;
			} while (cont && index + direction < parent.children.length && index + direction >= 0);
			if(index > parent.children.length - 1)
			{
				index = parent.children.length-1;
			}
			if(index < 0)
			{
				index = 0;
			}
			current = parent.children[index];
			break;
		default:
			return false;
	}

	if(current)
	{
		jQuery("#" + this.instance.dhtmlxmenu.idPrefix + current.id).trigger("mouseover");
		this.instance.dhtmlxmenu._redistribSubLevelSelection(this.instance.dhtmlxmenu.idPrefix + current.id,this.instance.dhtmlxmenu.idPrefix + ( current.parent ? current.parent.id : this.instance.dhtmlxmenu.topId));
	}
	return true;
};

/**
 * Hides the menu if it is currently opened. Otherwise nothing happenes.
 */
egwMenu.prototype.hide = function()
{
	//Reset the currently active menu variable
	if (_egw_active_menu == this)
		_egw_active_menu = null;

	//Check whether an currently opened instance exists. If it does, close it.
	if (this.instance != null)
	{
		this.instance.hide();
		this.instance = null;
	}
}

/**
 * Adds a new menu item to the list and returns a reference to that object.
 *
 * @param string _id is a unique identifier of the menu item. You can use the
 * 	the getItem function to search a specific menu item inside the menu tree. The
 * 	id may also be false, null or "", which makes sense for items like seperators,
 * 	which you don't want to access anymore after adding them to the menu tree.
 * @param string _caption is the caption of the newly generated menu item. Set the caption
 * 	to "-" in order to create a sperator.
 * @param string _iconUrl is the URL of the icon which should be prepended to the
 * 	menu item. It may be false, null or "" if you don't want a icon to be displayed.
 * @param function _onClick is the JS function which is being executed when the
 * 	menu item is clicked.
 * @returns egwMenuItem the newly generated menu item, which had been appended to the
 * 	menu item list.
 */
egwMenu.prototype.addItem = function(_id, _caption, _iconUrl, _onClick)
{
	//Append the item to the list
	var item = _egwGenMenuItem(this, _id, _caption, _iconUrl, _onClick);
	this.children.push(item);

	return item;
}

/**
 * Removes all elements fromt the menu structure.
 */
egwMenu.prototype.clear = function()
{
	this.children = [];
}

/**
 * Loads the menu structure from the given object tree. The object tree is an array
 * of objects which may contain a subset of the menu item properties. The "children"
 * property of such an object is interpreted as a new sub-menu tree and appended
 * to that child.
 *
 * @param array _elements is a array of elements which should be added to the menu
 */
egwMenu.prototype.loadStructure = function(_elements)
{
	this.children = _egwGenMenuStructure(_elements, this);
}

/**
 * Searches for the given item id within the element tree.
 */
egwMenu.prototype.getItem = function(_id)
{
	return _egwSearchMenuItem(this.children, _id);
}

/**
 * Applies the given onClick handler to all menu items which don't have a clicked
 * handler assigned yet.
 */
egwMenu.prototype.setGlobalOnClick = function(_onClick)
{
	_egwSetMenuOnClick(this.children, _onClick);
}

/**
 * Constructor for the egwMenuItem. Each entry in a menu (including seperators)
 * is represented by a menu item.
 */
export function egwMenuItem(_parent, _id)
{
	this.id = _id;
	this.caption = "";
	this.checkbox = false;
	this.checked = false;
	this.groupIndex = 0;
	this.enabled = true;
	this.iconUrl = "";
	this.onClick = null;
	this["default"] = false;
	this.data = null;
	this.shortcutCaption = null;

	this.children = [];
	this.parent = _parent;
}

/**
 * Searches for the given item id within the element tree.
 */
egwMenuItem.prototype.getItem = function(_id)
{
	if (this.id === _id)
		return this;

	return _egwSearchMenuItem(this.children, _id);
}

/**
 * Applies the given onClick handler to all menu items which don't have a clicked
 * handler assigned yet.
 */
egwMenuItem.prototype.setGlobalOnClick = function(_onClick)
{
	this.onClick = _onClick;
	_egwSetMenuOnClick(this.children, _onClick);
}

/**
 * Adds a new menu item to the list and returns a reference to that object.
 *
 * @param string _id is a unique identifier of the menu item. You can use the
 * 	the getItem function to search a specific menu item inside the menu tree. The
 * 	id may also be false, null or "", which makes sense for items like seperators,
 * 	which you don't want to access anymore after adding them to the menu tree.
 * @param string _caption is the caption of the newly generated menu item. Set the caption
 * 	to "-" in order to create a sperator.
 * @param string _iconUrl is the URL of the icon which should be prepended to the
 * 	menu item. It may be false, null or "" if you don't want a icon to be displayed.
 * @param function _onClick is the JS function which is being executed when the
 * 	menu item is clicked.
 * @returns egwMenuItem the newly generated menu item, which had been appended to the
 * 	menu item list.
 */
egwMenuItem.prototype.addItem = function(_id, _caption, _iconUrl, _onClick)
{
	//Append the item to the list
	var item = _egwGenMenuItem(this, _id, _caption, _iconUrl, _onClick);
	this.children.push(item);

	return item;
}


//Setter functions for the menuitem properties

egwMenuItem.prototype.set_id = function(_value)
{
	this.id = _value;
}

egwMenuItem.prototype.set_caption = function(_value)
{
	//A value of "-" means that this element is a seperator.
	this.caption = _value;
}

egwMenuItem.prototype.set_checkbox = function(_value)
{
	this.checkbox = _value;
}

egwMenuItem.prototype.set_checked = function(_value)
{
	if (_value && this.groupIndex > 0)
	{
		//Uncheck all other elements in this radio group
		for (var i = 0; i < this.parent.children.length; i++)
		{
			var obj = this.parent.children[i];
			if (obj.groupIndex == this.groupIndex)
				obj.checked = false;
		}
	}
	this.checked = _value;
}

egwMenuItem.prototype.set_groupIndex = function(_value)
{
	//If groupIndex is greater than 0 and the element is a checkbox, it is
	//treated like a radio box
	this.groupIndex = _value;
}

egwMenuItem.prototype.set_enabled = function(_value)
{
	this.enabled = _value;
}

egwMenuItem.prototype.set_onClick = function(_value)
{
	this.onClick = _value;
}

egwMenuItem.prototype.set_iconUrl = function(_value)
{
	this.iconUrl = _value;
}

egwMenuItem.prototype.set_default = function(_value)
{
	this["default"] = _value;
}

egwMenuItem.prototype.set_data = function(_value)
{
	this.data = _value;
}

egwMenuItem.prototype.set_hint = function(_value)
{
	this.hint = _value;
}

egwMenuItem.prototype.set_shortcutCaption = function(_value)
{
	this.shortcutCaption = _value;
}

