function ContextMenu(menu, options){
	var self = this;
	var num = ContextMenu.count++;
	var opentime = new Date($.now());

	this.menu = menu;
	this.contextTarget = null;

	if(!(menu instanceof Array)){
		throw new Error("Parameter 1 must be of type Array");
	}

	if(typeof options !== "undefined"){
		if(typeof options !== "object"){
			throw new Error("Parameter 2 must be of type object");
		}
	}else{
		options = {};
	}

	window.addEventListener("resize", function(){
		if(ContextUtil.getProperty(options, "close_on_resize", true)){
			self.hide();
		}
	});

	this.setOptions = function(_options){
		if(typeof _options === "object"){
			options = _options;
		}else{
			throw new Error("Parameter 1 must be of type object")
		}
	}

	this.changeOption = function(option, value){
		if(typeof option === "string"){
			if(typeof value !== "undefined"){
				options[option] = value;
			}else{
				throw new Error("Parameter 2 must be set");
			}
		}else{
			throw new Error("Parameter 1 must be of type string");
		}
	}

	this.getOptions = function(){
		return options;
	}

	this.reload = function(){
		if(document.getElementById('cm_' + num) == null){
			var cnt = document.createElement("div");
			cnt.className = "cm_container";
			cnt.id = "cm_" + num;

			document.body.appendChild(cnt);
		}

		var container = document.getElementById('cm_' + num);
		container.innerHTML = "";

		container.appendChild(renderLevel(menu));
	}

	function renderLevel(level){
		var ul_outer = document.createElement("ul");

		if (!level)
			return ul_outer;

		level.forEach(function(item){
			var li = document.createElement("li");
			li.menu = self;

			if (item.type === null || typeof item.type === "undefined" || item.type === ""){
				var icon_span = document.createElement("span");
				icon_span.className = 'cm_icon_span';

				if(ContextUtil.getProperty(item, "icon", "") != ""){
					icon_span.innerHTML = ContextUtil.getProperty(item, "icon", "");
				}else{
					icon_span.innerHTML = ContextUtil.getProperty(options, "default_icon", "");
				}

				var text_span = document.createElement("span");
				text_span.className = 'cm_text';

				if(ContextUtil.getProperty(item, "text", "") != ""){
					text_span.innerHTML = ContextUtil.getProperty(item, "text", "");
				}else{
					text_span.innerHTML = ContextUtil.getProperty(options, "default_text", "item");
				}

				var sub_span = document.createElement("span");
				sub_span.className = 'cm_sub_span';

				if (typeof item.sub !== "undefined" && item.sub !== null){
					if(ContextUtil.getProperty(options, "sub_icon", "") != ""){
						sub_span.innerHTML = ContextUtil.getProperty(options, "sub_icon", "");
					}else{
						sub_span.innerHTML = '&#155;';
					}
				}

				li.appendChild(icon_span);
				li.appendChild(text_span);
				li.appendChild(sub_span);

				if(!ContextUtil.getProperty(item, "enabled", true)){
					li.setAttribute("disabled", "");
				}else{
					if (item.events !== null && typeof item.events === "object") {

						var keys = Object.keys(item.events);
						for (var i = 0; i < keys.length; i++){

							// This never worked right, so the hack below
							//var eventtype = keys[i];
							//var eventlistener = item.events[keys[i]];
							//li.addEventListener(eventtype, eventlistener);
						}
					}

					// Hack for "click" event only, because I never got the stuff above to work
					if (item.MenuCmd)
						li.addEventListener("click", function (e) { /*ToastMessage($(this).text());*/ e.stopPropagation(); if (HasLiChildren($(this))) return; OnMenuItemClick(item.MenuCmd); self.hide(); });

					if (item.clickhandler)
						li.addEventListener("click", function (e) { e.stopPropagation(); if (HasLiChildren($(this))) return; eval(item.clickhandler); self.hide(); });

					if (item.stcontent)
						li.addEventListener("click", function (e) { e.stopPropagation(); if (HasLiChildren($(this))) return; StandardButtonClickEventHandlerForStContent(item.stcontent, item.title); self.hide(); });

					if (item.launchurl)
						li.addEventListener("click", function (e) { e.stopPropagation(); if (HasLiChildren($(this))) return; LaunchUrl(item.launchurl); self.hide(); });

					// Sub item click
					// Clicking an item with subs should not close the menu. Block documentClick()
					if (typeof item.sub !== "undefined" && item.sub !== null)
						li.addEventListener("click", function (e) { opentime = new Date($.now()); /*ToastMessage(item.text);*/ });

					if (typeof item.sub !== "undefined" && item.sub !== null )
						li.appendChild(renderLevel(item.sub));
				}
			}else{
				if(item.type == ContextMenu.DIVIDER){
					li.className = "cm_divider";
				}
			}

			ul_outer.appendChild(li);
		});

		return ul_outer;
	}

	this.display = function(e, target){

		// Hide previous instances of this menu, so only one is dislayed
		self.hide();

		if (typeof target !== "undefined") {
			self.contextTarget = target;
		}else{
			self.contextTarget = e.target;
		}

		var menu = document.getElementById('cm_' + num);

		var clickCoords = {x: e.clientX, y: e.clientY};
		var clickCoordsX = clickCoords.x;
		var clickCoordsY = clickCoords.y;

		var menuWidth = menu.offsetWidth + 4;
		var menuHeight = menu.offsetHeight + 4;

		var windowWidth = window.innerWidth;
		var windowHeight = window.innerHeight;

		var mouseOffset = parseInt(ContextUtil.getProperty(options, "mouse_offset", 2));

		if((windowWidth - clickCoordsX) < menuWidth){
			menu.style.left = windowWidth - menuWidth + "px";
		}else{
			menu.style.left = (clickCoordsX + mouseOffset) + "px";
		}

		if((windowHeight - clickCoordsY) < menuHeight){
			menu.style.top = windowHeight - menuHeight + "px";
		}else{
			menu.style.top = (clickCoordsY + mouseOffset) + "px";
		}

		// Incorrect values
		var sizes = ContextUtil.getSizes(menu);
		//if((windowWidth - clickCoordsX) < sizes.width){
		//	menu.classList.add("cm_border_right");
		//}else{
		//	menu.classList.remove("cm_border_right");
		//}

		menu.classList.remove("cm_border_right");

		var bHangsBelowBottom = (windowHeight - clickCoordsY) < sizes.height;
		var bAboveTop = ((clickCoordsY - sizes.height) < 0) ? true: false;
		var bTallerThanWindow = (windowHeight < sizes.height);

		if (bHangsBelowBottom && !bAboveTop && !bTallerThanWindow){
			menu.classList.add("cm_border_bottom");
		}else{
			menu.classList.remove("cm_border_bottom");
		}

		menu.classList.add("display");

		if(ContextUtil.getProperty(options, "close_on_click", true)){
			window.addEventListener("click", documentClick);
		}

		// Esc key closes menu
		window.addEventListener("keydown", menuKeyDown);

		//$(menu).on('keydown', function (event) {
		//	if (event.key === "Escape") {
		//		self.hide();
		//	}
		//});
		//$(menu).focus();

		e.preventDefault();
	}

	this.hide = function () {
		// Hide previous instances of this menu, so only one is dislayed
		if (num > 0)
			document.getElementById('cm_' + (num-1)).classList.remove("display");
		document.getElementById('cm_' + num).classList.remove("display");
		window.removeEventListener("click", documentClick);
		window.removeEventListener("keydown", menuKeyDown);
	}

	function documentClick() {

		// Strangely, click comes in immediate after display
		// Ignore immediate clicks
		var clicktime = new Date($.now());
		var timeDiff = clicktime - opentime;
		if (timeDiff < 100)
			return;

		self.hide();
	}

	function menuKeyDown(event) {

		if (event.key === "Escape") {
			self.hide();
		}
	}

	this.reload();
}



ContextMenu.count = 0;
ContextMenu.DIVIDER = "cm_divider";

const ContextUtil = {
	getProperty: function(options, opt, def){
		if(typeof options[opt] !== "undefined"){
			return options[opt];
		}else{
			return def;
		}
	},


	getSizes: function (obj) {

		var totalWidth = obj.scrollWidth;
		var totalHeight = obj.scrollHeight;

		// Strangely, this container has a scrollbar, from some css attribute
		// The max height is about 600 pixels
		// The height returned above seemed to be derived from (28.62 * nItems)
		if (Number(totalHeight) > 600)
			totalHeight = 600;

		return {
			"width": totalWidth,
			"height": totalHeight
		};
	}




// OLD..........................................
// Fails to get total height of all ul elements in cascading menu
// .............................................
//	getSizes: function(obj){
//		var lis = obj.getElementsByTagName('li');

//		var width_def = 0;
//		var height_def = 0;

//		for(var i = 0; i < lis.length; i++){
//			var li = lis[i];

//			if(li.offsetWidth > width_def){
//				width_def = li.offsetWidth;
//			}

//			if(li.offsetHeight > height_def){
//				height_def = li.offsetHeight;
//			}
//		}

//		var width = width_def;
//		var height = height_def;

//		for(var i = 0; i < lis.length; i++){
//			var li = lis[i];

//			var ul = li.getElementsByTagName('ul');
//			if(typeof ul[0] !== "undefined"){
//				var ul_size = ContextUtil.getSizes(ul[0]);

//				if(width_def + ul_size.width > width){
//					width = width_def + ul_size.width;
//				}

//				if(height_def + ul_size.height > height){
//					height = height_def + ul_size.height;
//				}
//			}
//		}

//		return {
//			"width": width,
//			"height": height
//		};
//	}
};



/**
 * Ugly hack
 * This is necessary to allow li elements to be clicked without closing the menu
 * Parent elements that have sub items should allow clicks without closing menu
 * @param {any} lielement
 */
function HasLiChildren(lielement) {

	if (!lielement)
		return;
	var AllElements = $(lielement).find("li");
	return (AllElements != null && AllElements.length > 0);
}

/**
 * Ugly hack!
 * Remove the 'click' event listener from the parent
 * This is necessary to allow li elements to be clicked without closing the menu
 * Parent elements that have sub items should allow clicks without closing menu
 * @param {any} lielement
 */
//function CheckRemoveEventListenerFromParentLiElement(lielement) {

//	if (!lielement)
//		return;

//	var parent = $(lielement).parent();
//	if (!parent)
//		return;

//	var ParentElementType = $(parent).prop("nodeName");
//	if (!ParentElementType)
//		return;

//	if (ParentElementType.toLowerCase() === "ul") {

//		parent = $(parent).parent();
//		ParentElementType = $(parent).prop("nodeName");
//		if (ParentElementType.toLowerCase() === "li") {
//			$(parent).unbind('click');
//			$(parent).off('click');
//        }
//    }
//}


