var ishObject = {},
	forEach = 'forEach',
	extend = 'extend',
	_window = window,
	_doc = document,
	dummy = document.createElement('i');


/**
 * Simple selector engine based on <code>querySelectorAll</code>. The usage and result is similar to <code>jQuery(selector)</code>.
 * @name  ish
 * @augments ishObject
 * @function
 * @memberof ish
 * @param   {String|Node}   selector   A CSS Selector compatible with document.querySelectorAll or a single `Node`.
 * @param   {ishObject|Array|NodeList|Node} context  Used to give a selector a search context.
 * @param   {String} forceSelector    Set the ish('selector').selector paramter forcibly.
 * @return  {ishObject}                A list of nodes with inherited library methods.
 * @example
 * ish('selector');
 * //filter the collection with some context of type Node || NodeList
 * ish('selector', Node);
 * ish('selector', NodeList);
 */
var $ = function(selector, context, forceSelector) {
	context = context || document;

	var found;
	if (isNode(selector) || selector === window || selector === document) {
		found = [selector];
		selector = forceSelector || selector;
	} else {
		// if context is an ishObject
		if (context.length) {
			found = [];
			var nodesFound;
			for (var i = 0; i < context.length; i++) {
				nodesFound = context[i].querySelectorAll(selector || '☺');
				// might be able to improve this....
				// https://blog.jscrambler.com/12-extremely-useful-hacks-for-javascript
				for (var el = 0; el < nodesFound.length; el++) {
					found.push(nodesFound[el]);
				}
			}
		} else {
			// querySelectorAll requires a string with a length
			// otherwise it throws an exception
			found = context.querySelectorAll(selector || '☺');
		}
	}
	var length = found.length;
	var obj = Object.create(ishObject);

	for (var n = 0; n < length; n++) {
		obj[n] = found[n] || found;
	}

	/**
	 * The number of items found in the collection.
	 * @memberOf ishObject
	 * @name  length
	 */
	obj.length = length;
	/**
	 * The selector string as given when calling ish(selector, context, forceSelector). This value can be overridden if using the ish() forceSelector parameter.
	 * @memberOf ishObject
	 * @name  selector
	 */

	obj.selector = selector;
	/**
	 * The context as given when calling ish(selector, context).
	 * @memberOf ishObject
	 * @name  context
	 */
	obj.context = context;
	return obj;
};

/**
 * An object which stores prototype objects.
 * @memberOf ish
 * @name  ish.fn
 */
$.fn = {
	/**
	 * @mixin ish.fn.ishObject
	 * @description
	 * When you invoke the `ish('selector')` method `ish.fn.ishObject` members are inherited through Prototype Delegation to the returned collection.
	 * The result is just like a jQuery Object, there is utility methods, a length, context and selector property.
	 * 
	 * @example
	 * // Cache an ishObject collection
	 * var collection = ish('selector');
	 * 
	 * // Call an ishObject method
	 * ish('selector').attr('attributeName');
	 * 
	 * // There is a length property
	 * ish('selector').length; 
	 * 
	 * // and also a selector property which refers to the first parameter passed into ish();
	 * ish('selector').selector;
	 *
	 * // You can call native methods on collection items just like you would in jQuery
	 * ish('selector')[0].style.display = 'block';
	 * 
	 */
	ishObject: ishObject
};

//Returns true if it is a DOM node
function isNode(o) {
	return (
		typeof Node === "object" ? o instanceof Node :
		o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"
	);
}

/**
 * Returns the item at the specified index in the `ishObject`.
 * @name  ish.fn.ishObject.nth
 * @function
 * @param  {Number} int    The index of the item in the `ishObject`.
 * @example
 * ish('selector').nth(0); //gets the first node
 */

ishObject.nth = function(int) {
	return $(this[int], null, this.selector);
};



/**
 * Iterates an `ishObject` returning each `Node` in an individual `ishObject`, along with its index 
 * in the original collection. This method will iterate every item in the collection and cannot be broken.
 * @name  ish.fn.ishObject.forEach
 * @function
 * @param  {Function} fn    The callback function which will be called with each iteration
 * @param  {Object}   scope The scope in which the callback function will be called. 
 * @return {ishObject}        Returns the `ishObject` which called it. Method is chainable. 
 * @example
 * ish('selector', function( $item, index ) {
 *     //iterates the collections Dom Nodes, cannot be broken out of.    
 * });
 */
ishObject[forEach] = function(fn, scope) {
	scope = scope || this;
	var i = 0,
		s,
		len = this.length;
	for (i; i < len; i++) {
		s = $(this[i], null, this.selector);
		fn.call(scope, s, i);
	}
	return this;
};
/**
 * Gets the index of a specific `Node` in an `ishObject`
 * @name  ish.fn.ishObject.indexOf
 * @function
 * @param  {Node} needle    The `Node` to find in the `ishObject`.
 * @return {Number}         Index of the `Node` in the `ishObject`. Returns -1 if not found.
 * @example
 * ish('selector').indexOf(Node);
 */
ishObject.indexOf = function(needle) {
	var i = 0,
		len = this.length;

	for (i; i <= len; i++) {
		var item = this[i];
		if (item === needle) {
			return i;
		} else if (!item) {
			return -1;
		}
	}
	//return this;
};
/**
 * Gets an attributes value for the first element in the `ishObject`. If the second argument is supplied the 
 * method sets an attribute and its value on all `Node`'s in the given `ishObject`. Prototyped method and properties are shadowed in the new object.
 * @name  ish.fn.ishObject.attr
 * @function
 * @param  {String} name        A valid CSS attribute selector.
 * @param  {String} value       The attirbute value to be set.
 * @return {ishObject|String}     If setting an attribute the method returns the `ishObject` which called it 
 * and is chainable. If getting an attribute value the method will return the value found or undefined if 
 * it's not found.
 * @example
 * ish('selector').attr('attribute-name'); //get an attribute value
 * ish('selector').attr('attribute-name','attribute-value'); //set an attribute value
 */
ishObject.attr = function(name, value) {
	var returnVal;
	if (typeof value === "string") {
		this[forEach](function(el) {
			el[0].setAttribute(name, value);
		});
		returnVal = this;
	} else if (this[0]) {
		returnVal = this[0].getAttribute(name);
	}
	return returnVal;
};

/**
 * Accepts a series objects, merging the second parameter into the first recursively, if more than two objects are supplied the other objects are merged into the first in the order given.
 * @name  ish#extend
 * @function
 * @param  {Object} object1 An `Object` that will have values of object2 recursively merged.
 * @param  {Object} [,obectj2] A series of `Objects` to merge into object1.
 * @return {Object}         A merged `Object`.
 * @example
 * var obj1 = {a:'a',b:{ba:'ba',bb:'bb',bc:{bca:'bca'}},c:[1,2,3]};
 * var obj2 = {d:'d',b:{ba:'ba-change',bc:{bcb:'added'}},c:[4,5,6]};
 * ish.extend(obj1,obj2);
 */

function extendProp (targetObject, toMerge, prop){
	var propValue = toMerge[prop];
	if (propValue === null || propValue === undefined) {
		return; // skip null and undefined values
	} else if (propValue.constructor === Object || Array.isArray(propValue)) { // recurse objects that already exisit on the target
		targetObject[prop] = $[extend](targetObject[prop] || {}, propValue);
	} else {
		/*var descriptor = Object.getOwnPropertyDescriptor(targetObject, prop);
		console.log('descriptor ',prop, descriptor, (typeof descriptor !== 'undefined' && descriptor.writable === 'true'), !targetObject[prop] );
		
		if((typeof descriptor !== 'undefined' && typeof descriptor.set !== 'undefined') || !targetObject[prop]) {
			console.log('extend ',prop);
			targetObject[prop] = propValue;
		}*/

		targetObject[prop] = propValue;
	}
}
$[extend] = function() {
	var args = arguments;
	var targetObject = args[0];
	// TODO: I dont think this will copy over any constructor prototypes implementations...
	// TODO: should I consider shallow copies?
	for (var i = 1; i < args.length; i++) {
		var toMerge = args[i];
		if(Array.isArray(targetObject)){
			var newArray = [];
			for (var e = 0; e < toMerge.length; e++) {
				if (toMerge[e] === null || toMerge[e] === undefined) {
					continue; // skip null and undefined values
				} else if (toMerge[e].constructor === Object ){ 
					newArray[e] = $[extend](newArray[e] || {}, toMerge[e]);
				} else if ( Array.isArray( toMerge[e])) {
					newArray[e] = $[extend](newArray[e] || [], toMerge[e]);
				} else {
					targetObject[e]  = toMerge[e];
				}
			}
		} else if(targetObject.constructor === Object){
			// all keys incuding non-enums
			var allKeys = Object.getOwnPropertyNames(toMerge);			
			for (var prop in toMerge) {
				var keyInx = allKeys.indexOf(prop);
				allKeys.splice(keyInx, 1);
				extendProp (targetObject, toMerge, prop);
			}
			// extend any non-enumerable props left over
			for (var each = 0; each < allKeys.length; each++) {
				extendProp (targetObject, toMerge, allKeys[each]);
			}
		}
	}
	return targetObject;
};

/**
 * Returns the left and top offset in pixels for the first element in the `ishObject`. 
 * @name  ish.fn.ishObject.offset
 * @function
 * @return {Object}         An `Object` with values for left and top offsets in pixel values.
 * @example
 * ish('selector').offset();
 */
ishObject.offset = function() {
	var ol = 0;
	var ot = 0;
	if (this[0].offsetParent) {

		ol = this[0].offsetLeft;
		ot = this[0].offsetTop;

	}
	return {
		left: ol,
		top: ot
	};
};

/**
 * Gets the width or height the first element in the supplied `ishObject`.
 * @name  ish.fn.ishObject.dimension
 * @function
 * @param  {String} type          'width' or 'height'.
 * @param  {Boolean} margins      Include margins in the return result.
 * @param  {Boolean} clientHeight Exclude the horizontal scrollbars height from the result.
 * @return {Integer}              The height of the element.
 * @example
 * ish('selector').width();
 */
ishObject.dimension = function(type, margins, clientHeight) {
	var disp;
	if (this.selector !== (window || document)) {
		disp = this[0].style.display;
		if (disp === 'none') this[0].style.display = 'block';
	}
	var height = 0;
	var mt = 0;
	var mb = 0;
	if (margins) {
		mt = type === 'height' ? this.css('marginTop') : this.css('marginLeft');
		mb = type === 'height' ? this.css('marginBottom') : this.css('marginRight');
		height = mt + mb;
	}

	if (this[0] === window) {
		height += type === 'height' ? this[0].outerHeight : this[0].outerWidth;
	} else if (clientHeight) {
		height += type === 'height' ? this[0].clientHeight : this[0].clientWidth;
		//this[0].style.display = '';
	} else {
		height += type === 'height' ? this[0].offsetHeight : this[0].offsetWidth;

	}
	if (this.selector !== (window || document))
		if (disp === 'none') this[0].style.display = 'none';
	return height;
};

/**
 * Gets the CSS value of the first element in the supplied `ishObject`. Or sets the CSS value on all items in an `ishObject`.
 * @name  ish.fn.ishObject.css
 * @function
 * @param  {String} prop   The name of the CSS property in camelCase. eg. 'margin-left' would be passed as 'marginLeft'.  
 * @param  {String} value   Exclude the horizontal scrollbars height from the result.
 * @return {ishObject}       Returns the `ishObject` which called it. Method is chainable. 
 * @example
 * ish('selector').css();
 */
ishObject.css = function(prop, value) {
	if(typeof prop === 'object') {
		for(var each in prop) {
			this.css(each, prop[each]);
		}
	} else if (!value) {
		//get the style
		var typeStr = prop.slice(0, 6) + '-' + prop.slice(6).toLowerCase();
		return parseInt(this[0].style[prop] || window.getComputedStyle(this[0]).getPropertyValue(typeStr));
	} else {
		// set the style
		this[forEach](function($el) {
			$el[0].style[prop] = value;
		});
	}
	return this;
};