/**
 * fusionLib JavaScript library implementing DOM manipulation methods.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Copyright (c) 2007 - 2016 fusionLib. All rights reserved.
 */

(function() {
	fusionLib.extend({

		/**
		 * Manipulate the DOM.
		 *
		 * @param string html The HTML to use.
		 * @param function fn The callback function to do the insert.
		 * @return fusionLib This object.
		 */
		domManip:function(html, fn) {
			return this.each(function() {
				var div = document.createElement('div');
				div.innerHTML = html;
				fn.call(this, div.lastChild);
				div = null;
			});
		},

		/**
		 * Append the html at the end of the current.
		 *
		 * @param string html The HTML to use.
		 * @return fusionLib This object.
		 */
		append: function(html) {
			return this.domManip(html, function(e) { this.appendChild(e); });
		},

		/**
		 * Prepend the html at the start of the current.
		 *
		 * @param string html The HTML to use.
		 * @return fusionLib This object.
		 */
		prepend: function(html) {
			return this.domManip(html, function(e) { this.insertBefore(e, this.firstChild); });
		},

		/**
		 * Replace the element with the html.
		 *
		 * @param string html The HTML to use.
		 * @return fusionLib This object.
		 */
		replaceWith: function(html) {
			return this.domManip(html, function(e) { this.parentNode.replaceChild(e, this); });
		},

		/**
		 * Wrap the element with the html.
		 *
		 * @param string html The HTML to use.
		 * @return fusionLib This object.
		 */
		wrap: function(html) {
			return this.domManip(html, function(e) {
				this.parentNode.replaceChild(e, this);
				e.appendChild(this);
			});
		},

		/**
		 * Remove elements from the document.
		 *
		 * @return fusionLib Empty object.
		 */
		remove: function() {
			this.each(function() { this.parentNode.removeChild(this); });
			return this._result([]);
		},

		/**
		 * Insert the html before the element.
		 *
		 * @param string html The HTML to use.
		 * @return fusionLib This object.
		 */
		before: function(html) {
			return this.domManip(html, function(e) { this.parentNode.insertBefore(e, this); });
		},

		/**
		 * Insert the html after the element.
		 *
		 * @param string html The HTML to use.
		 * @return fusionLib This object.
		 */
		after: function(html) {
			return this.domManip(html, function(e) { this.parentNode.insertBefore(e, this.nextSibling); });
		},

		/**
		 * Set the html within an the elements or get the current html.
		 *
		 * @param string html The HTML to set or undefined to read the current html.
		 * @return fusionLib This object or the read html.
		 */
		html: function(html) {
			if(html === undefined)
				return this[0].innerHTML;
			else
				return this.each(function() { this.innerHTML = html; });
		},

		/**
		 * Set the text within an the elements or get the current text.
		 *
		 * @param string text The text to set or undefined to read the current text.
		 * @return fusionLib This object or the read text.
		 */
		text: function(text) {
			if(text === undefined)
				return this[0].innerText || this[0].textContent;
			else {
				if(this[0].innerText !== undefined)
					return this.each(function() { this.innerText = text; });
				else
					return this.each(function() { this.textContent = text; });
			}
		},

		/**
		 * Set or get an attribute.
		 *
		 * @param string attribute The name of the attribute to set or get.
		 * @param string value The value to set, false or null to remove the attribute. Not supplied
		 * to read the attribute.
		 * @return fusionLib This object or if reading the value of the attribute on the first element.
		 */
		attr: function(attribute, value) {
			if(arguments.length == 2)
				return this.each(function() { (value === null || value === false) ? this.removeAttribute(attribute) : this.setAttribute(attribute, value === true ? attribute : value); });
			else
				return this[0] ? this[0].getAttribute(attribute) : '';
		},

		/**
		 * Remove the attribute.
		 *
		 * @param string attribute The attirbute to remove.
		 * @returns fusionLib This object.
		 */
		removeAttr: function(attribute) {
			return this.each(function() { this.removeAttribute(attribute); });
		},

		/**
		 * Set the focus to the 1st element in the selection.
		 */
		focus: function() {
			this[0].focus();
			return this;
		},

		/**
		 * Set or get the value of elements. When getting only the 1st elements value is returned.
		 *
		 * @param string v The value to set if present.
		 * @prarm fusionLib This object when setting or the value of the element.
		 */
		val: function(v) {
			if(arguments.length) {
				return this.each(function() {
					if(this.type == 'select-multiple' && v instanceof Array) {
						for(var i=0;i<this.options.length;i++)
							this.options[i].selected = v.indexOf(this.options[i].value) != -1;
					}
					else
						this.value = v;
				});
			}
			else {
				var val = false;

				if(this[0] != null) {
					if(this[0].type == 'select-multiple') {
						val = [];
						for(var i=0;i<this[0].options.length;i++) {
							if(this[0].options[i].selected)
								val.push(this[0].options[i].value);
						}
					}
					else
						val = this[0].value;
				}

				return val;
			}
		},

		/**
		 * Test the matched elements against a selector.
		 *
		 * @param string selector The selector to test for.
		 * @returns bool true if matches; else false.
		 */
		is: function(selector) {
			var p;

			if(selector == ':checked')
				return this[0].checked;
			else if(p = selector.match(/#([a-z0-9_-]+)/i))
				return this[0].id == p[1];
			else if(p = selector.match(/\.([a-z0-9_-]+)/i))
				return this.hasClass(p[1]);
			else
				return this[0].nodeName.toLowerCase() == selector;
		},

		/**
		 * Get the scroll top.
		 *
		 * @params int position The position to scroll to.
		 * @return The scroll top in pixels.
		 */
		scrollTop: function(position) {
			if(this[0] == window) {
				if (position == undefined) {
					if (window.pageYOffset != undefined)
						return window.pageYOffset;
					else
						return document.documentElement.scrollTop || document.body.scrollTop || 0;
				}
				else {
					window.scrollTo($fl(window).scrollLeft(), position);
				}
			} else {
				if(position == undefined)
					return this[0].scrollTop;
				else
					this[0].scrollTop = position;
			}

			return this;
		},

		/**
		 * Get the scroll left.
		 *
		 * @params int position The position to scroll to.
		 * @return The scroll left in pixels.
		 */
		scrollLeft: function(position) {
			if(this[0] == window) {
				if (position == undefined) {
					if (window.pageXOffset != undefined)
						return window.pageXOffset;
					else
						return document.documentElement.scrollLeft || document.body.scrollLeft || 0;
				}
				else {
					window.scrollTo(position, $fl(window).scrollTop());
				}
			} else {
				if(position == undefined)
					return this[0].scrollLeft;
				else
					this[0].scrollLeft = position;
			}

			return this;
		},

		/**
		 * Get the position of an element relative to the document.
		 *
		 * @returns { top:, left: }
		 */
		offset: function() {
			var b = this[0].getBoundingClientRect();
			return {
				top: Math.ceil((b.bottom - b.height) + fusionLib(window).scrollTop()),
				left: Math.ceil(b.left + fusionLib(window).scrollLeft())
			};
		},

		/**
		 * Get the position of an element.
		 *
		 * @returns { top:, left: }
		 */
		position: function() {
			var parentOffset = {
				top: 0,
				left: 0
			},
				offset = this.offset();

			if(this.css('position') != 'fixed') {
				var p = $fl(this[0]).parent();
				while(p[0].nodeName.toLowerCase() != 'html' && p.css('position') == 'static') {
					p = p.parent();
				}

				if(p[0].nodeName.toLowerCase() != 'html')
					parentOffset = p.offset();
			}

			return {
				top: offset.top - parentOffset.top,
				left: offset.left - parentOffset.left
			};
		}
	});
})();
