/*\
title: $:/core/modules/utils/dom.js
type: application/javascript
module-type: utils

Various static DOM-related utility functions.

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

/*
Determines whether element 'a' contains element 'b'
Code thanks to John Resig, http://ejohn.org/blog/comparing-document-position/
*/
exports.domContains = function(a,b) {
	return a.contains ?
		a !== b && a.contains(b) :
		!!(a.compareDocumentPosition(b) & 16);
};

exports.domMatchesSelector = function(node,selector) {
	return node.matches ? node.matches(selector) : node.msMatchesSelector(selector);
};

exports.removeChildren = function(node) {
	while(node.hasChildNodes()) {
		node.removeChild(node.firstChild);
	}
};

exports.hasClass = function(el,className) {
	return el && el.hasAttribute && el.hasAttribute("class") && el.getAttribute("class").split(" ").indexOf(className) !== -1;
};

exports.addClass = function(el,className) {
	var c = (el.getAttribute("class") || "").split(" ");
	if(c.indexOf(className) === -1) {
		c.push(className);
		el.setAttribute("class",c.join(" "));
	}
};

exports.removeClass = function(el,className) {
	var c = (el.getAttribute("class") || "").split(" "),
		p = c.indexOf(className);
	if(p !== -1) {
		c.splice(p,1);
		el.setAttribute("class",c.join(" "));
	}
};

exports.toggleClass = function(el,className,status) {
	if(status === undefined) {
		status = !exports.hasClass(el,className);
	}
	if(status) {
		exports.addClass(el,className);
	} else {
		exports.removeClass(el,className);
	}
};

/*
Get the first parent element that has scrollbars or use the body as fallback.
*/
exports.getScrollContainer = function(el) {
	var doc = el.ownerDocument;
	while(el.parentNode) {
		el = el.parentNode;
		if(el.scrollTop) {
			return el;
		}
	}
	return doc.body;
};

/*
Get the scroll position of the viewport
Returns:
	{
		x: horizontal scroll position in pixels,
		y: vertical scroll position in pixels
	}
*/
exports.getScrollPosition = function(srcWindow) {
	var scrollWindow = srcWindow || window;
	if("scrollX" in scrollWindow) {
		return {x: scrollWindow.scrollX, y: scrollWindow.scrollY};
	} else {
		return {x: scrollWindow.document.documentElement.scrollLeft, y: scrollWindow.document.documentElement.scrollTop};
	}
};

/*
Adjust the height of a textarea to fit its content, preserving scroll position, and return the height
*/
exports.resizeTextAreaToFit = function(domNode,minHeight) {
	// Get the scroll container and register the current scroll position
	var container = $tw.utils.getScrollContainer(domNode),
		scrollTop = container.scrollTop;
    // Measure the specified minimum height
	domNode.style.height = minHeight;
	var measuredHeight = domNode.offsetHeight || parseInt(minHeight,10);
	// Set its height to auto so that it snaps to the correct height
	domNode.style.height = "auto";
	// Calculate the revised height
	var newHeight = Math.max(domNode.scrollHeight + domNode.offsetHeight - domNode.clientHeight,measuredHeight);
	// Only try to change the height if it has changed
	if(newHeight !== domNode.offsetHeight) {
		domNode.style.height = newHeight + "px";
		// Make sure that the dimensions of the textarea are recalculated
		$tw.utils.forceLayout(domNode);
		// Set the container to the position we registered at the beginning
		container.scrollTop = scrollTop;
	}
	return newHeight;
};

/*
Gets the bounding rectangle of an element in absolute page coordinates
*/
exports.getBoundingPageRect = function(element) {
	var scrollPos = $tw.utils.getScrollPosition(element.ownerDocument.defaultView),
		clientRect = element.getBoundingClientRect();
	return {
		left: clientRect.left + scrollPos.x,
		width: clientRect.width,
		right: clientRect.right + scrollPos.x,
		top: clientRect.top + scrollPos.y,
		height: clientRect.height,
		bottom: clientRect.bottom + scrollPos.y
	};
};

/*
Saves a named password in the browser
*/
exports.savePassword = function(name,password) {
	var done = false;
	try {
		window.localStorage.setItem("tw5-password-" + name,password);
		done = true;
	} catch(e) {
	}
	if(!done) {
		$tw.savedPasswords = $tw.savedPasswords || Object.create(null);
		$tw.savedPasswords[name] = password;
	}
};

/*
Retrieve a named password from the browser
*/
exports.getPassword = function(name) {
	var value;
	try {
		value = window.localStorage.getItem("tw5-password-" + name);
	} catch(e) {
	}
	if(value !== undefined) {
		return value;
	} else {
		return ($tw.savedPasswords || Object.create(null))[name] || "";
	}
};

/*
Force layout of a dom node and its descendents
*/
exports.forceLayout = function(element) {
	var dummy = element.offsetWidth;
};

/*
Pulse an element for debugging purposes
*/
exports.pulseElement = function(element) {
	// Event handler to remove the class at the end
	element.addEventListener($tw.browser.animationEnd,function handler(event) {
		element.removeEventListener($tw.browser.animationEnd,handler,false);
		$tw.utils.removeClass(element,"pulse");
	},false);
	// Apply the pulse class
	$tw.utils.removeClass(element,"pulse");
	$tw.utils.forceLayout(element);
	$tw.utils.addClass(element,"pulse");
};

/*
Attach specified event handlers to a DOM node
domNode: where to attach the event handlers
events: array of event handlers to be added (see below)
Each entry in the events array is an object with these properties:
handlerFunction: optional event handler function
handlerObject: optional event handler object
handlerMethod: optionally specifies object handler method name (defaults to `handleEvent`)
*/
exports.addEventListeners = function(domNode,events) {
	$tw.utils.each(events,function(eventInfo) {
		var handler;
		if(eventInfo.handlerFunction) {
			handler = eventInfo.handlerFunction;
		} else if(eventInfo.handlerObject) {
			if(eventInfo.handlerMethod) {
				handler = function(event) {
					eventInfo.handlerObject[eventInfo.handlerMethod].call(eventInfo.handlerObject,event);
				};
			} else {
				handler = eventInfo.handlerObject;
			}
		}
		domNode.addEventListener(eventInfo.name,handler,false);
	});
};

/*
Get the computed styles applied to an element as an array of strings of individual CSS properties
*/
exports.getComputedStyles = function(domNode) {
	var textAreaStyles = window.getComputedStyle(domNode,null),
		styleDefs = [],
		name;
	for(var t=0; t<textAreaStyles.length; t++) {
		name = textAreaStyles[t];
		styleDefs.push(name + ": " + textAreaStyles.getPropertyValue(name) + ";");
	}
	return styleDefs;
};

/*
Apply a set of styles passed as an array of strings of individual CSS properties
*/
exports.setStyles = function(domNode,styleDefs) {
	domNode.style.cssText = styleDefs.join("");
};

/*
Copy the computed styles from a source element to a destination element
*/
exports.copyStyles = function(srcDomNode,dstDomNode) {
	$tw.utils.setStyles(dstDomNode,$tw.utils.getComputedStyles(srcDomNode));
};

/*
Copy plain text to the clipboard on browsers that support it
*/
exports.copyToClipboard = function(text,options) {
	options = options || {};
	var textArea = document.createElement("textarea");
	textArea.style.position = "fixed";
	textArea.style.top = 0;
	textArea.style.left = 0;
	textArea.style.fontSize = "12pt";
	textArea.style.width = "2em";
	textArea.style.height = "2em";
	textArea.style.padding = 0;
	textArea.style.border = "none";
	textArea.style.outline = "none";
	textArea.style.boxShadow = "none";
	textArea.style.background = "transparent";
	textArea.value = text;
	document.body.appendChild(textArea);
	textArea.select();
	textArea.setSelectionRange(0,text.length);
	var succeeded = false;
	try {
		succeeded = document.execCommand("copy");
	} catch (err) {
	}
	if(!options.doNotNotify) {
		$tw.notifier.display(succeeded ? "$:/language/Notifications/CopiedToClipboard/Succeeded" : "$:/language/Notifications/CopiedToClipboard/Failed");
	}
	document.body.removeChild(textArea);
};

exports.getLocationPath = function() {
	return window.location.toString().split("#")[0];
};


})();
