(function() {
	"use strict"

	var fn = SelectorBuilder.fn;
	var array_return = SelectorBuilder.array_return;	
	var data = SelectorBuilder.data;
	var resolve = SelectorBuilder.resolve;

	var isArrayLike = Util.isArrayLike;	
	var camelcase = Util.camelcase;
	
	var isNode = DOMUtil.isNode;
	var isElement = DOMUtil.isElement;
	var isHtml = DOMUtil.isHtml;
	var markup = DOMUtil.markup;
	var accessor = DOMUtil.accessor;
	var assemble = DOMUtil.assemble;
	var isShowing = DOMUtil.isShowing;
	var computed = DOMUtil.computed;
	var boundary = DOMUtil.boundary;
	var wrappingstringevent = DOMUtil.wrappingstringevent;	
	
	
	// function template for node attributes
	function type1(attr, arg) {
		if( !arg.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this[attr]);
			});
			return array_return(arr);
		}
	
		this.each(function() {
			this[attr] = resolve(this, arg[0]);
		});
		
		return this;
	}

	// Let's define core functions
	// identifier & attributes
	fn.accessor = function() {
		var arr = [];
		this.each(function() {
			arr.push(accessor(this));
		});			
		return array_return(arr);
	};

	fn.id = function(id) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this.id);
			});
			return array_return(arr);
		}
	
		this.each(function() {
			this.id = resolve(this, id);
		});
		
		return this;
	};
	
	fn.tag = function() {
		var arr = [];
		this.each(function() {
			var tag = this.tagName && this.tagName.toLowerCase();	
			arr.push(tag);
		});
		return array_return(arr);
	};

	fn.value = fn.val = function(value) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this.value);
			});
			return array_return(arr);
		}
	
		this.each(function() {
			this.value = resolve(this, value);
		});
		
		return this;
	};

	fn.checked = function(checked) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this.checked);
			});
			return array_return(arr);
		}
	
		this.each(function() {
			var c = resolve(this, checked);
			this.checked = ( c === true ) ? true : false;
		});
		
		return this;
	};

	fn.check = function() {
		return this.each(function() {
			this.checked = true;	
		});
	};

	fn.uncheck = function() {
		return this.each(function() {
			this.checked = false;	
		});
	};

	fn.selected = function(selected) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this.checked);
			});
			return array_return(arr);
		}
	
		this.each(function() {
			var c = resolve(this, selected);
			this.selected = ( c === true ) ? true : false;
			if( this.selected ) $(this).fire('selected');
			else $(this).fire('deselected');
		});
		
		return this;
	};

	fn.select = function() {
		var $ = this.$;
		return this.each(function() {
			this.selected = true;
			
			$(this).fire('selected');
		});
	};

	fn.deselect = function() {
		return this.each(function() {
			this.selected = false;
			
			$(this).fire('deselected');
		});
	};

	fn.name = function(name) {
		if( !arguments.length ) return this.attr('name');
		return this.attr('name', name);
	};

	fn.attr = function(key, value) {		
		if( typeof(key) !== 'string' ) return console.error('invalid key', key);
		
		if( arguments.length === 1 ) {
			var arr = [];
			this.each(function() {
				if( isElement(this) ) arr.push(this.getAttribute(key));
				else arr.push(null);
			});
			return array_return(arr);
		}
	
		this.each(function() {
			if( !isElement(this) ) return;
			var v = resolve(this, value);
			if( v === null || v === undefined ) this.removeAttribute(key);
			else this.setAttribute(key, (v + ''));
		});
		
		return this;
	};

	fn.attrs = function(attrs) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				var attrs = this.attributes;
				if( !attrs ) return arr.push([]);
				var o = {}; 
				for(var i= attrs.length-1; i>=0; i--) {
					o[attrs[i].name] = attrs[i].value;
				}

				arr.push(o);
			});			
			return array_return(arr);
		}
	
		if( typeof(attrs) !== 'object' ) return console.error('attrs must be an object', attrs);
		for(var k in attrs) this.attr(k, attrs[k]);
		return this;
	};
	
	fn.outer = function() {
		var arr = [];
		this.each(function() {
			arr.push(this.outerHTML);
		});
		return array_return(arr);
	};


	// style handling
	fn.css = function(key, value) {
		if( !arguments.length ) {
			var styles = this.style();
		
			if( !Array.isArray(styles) ) styles = [styles];
			var arr = [];
			for(var i=0; i < styles.length; i++) {
				arr.push(styles[i].get());
			}
			return array_return(arr);
		}
	
		return this.style.apply(this, arguments);
	};

	fn.style = function(key, value) {		
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(new StyleSession(this));
			});
			return array_return(arr);
		}
	
		if( typeof(key) === 'object' ) {
			return this.each(function() {
				new StyleSession(this).set(key).commit();
			});
		} else if( arguments.length === 1 ) {
			if( key === false ) {
				return this.each(function() {
					new StyleSession(this).clear().commit();
				});
			} else if( typeof(key) === 'string' && ~key.indexOf(':') ) {
				return this.each(function() {
					new StyleSession(this).text(key).commit();
				});
			} else if( typeof(key) === 'string' ) {
				var arr = [];
				this.each(function() {
					arr.push(new StyleSession(this).get(key));
				});
				return array_return(arr);
			} else {
				return console.error('illegal key', key);
			}
		}
	
		this.each(function() {
			var v = resolve(this, value);
			new StyleSession(this).set(key, v).commit();
		});
		
		return this;
	};

	fn.computed = function(key) {
		var arr = [];
		this.each(function() {
			arr.push(computed(this, key));
		});
		return array_return(arr);		
	};
	
	fn.classes = function(cls, flag) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this.className.trim().split(' '));
			});
			return array_return(arr);
		}
	
		return this.each(function() {			
			var classes = resolve(this, cls);

			var el = this;
			var o = (el.className || '').trim();

			if( typeof(flag) === 'boolean' ) {
				if( !classes ) return this;
				if( Array.isArray(classes) ) classes = classes.join(' ');
				classes = classes.split(' ');

				var args = el.className.trim().split(' ');
				for(var i=0; i < classes.length; i++) {
					var c = classes[i];
					if( c ) {
						if( !flag && ~args.indexOf(c) ) args.splice(args.indexOf(c), 1);
						else if( flag && !~args.indexOf(c) ) args.push(c);
					}
				}

				el.className = args.join(' ').trim();
			} else {
				el.className = '';
				el.removeAttribute('class');
				if( Array.isArray(classes) ) classes = classes.join(' ').trim();
				if( classes ) el.className = classes.trim();
			}
		});
	};

	fn.ac = fn.addClass = function(s) {
		return this.classes(s, true);
	};
	
	fn.hc = fn.hasClass = function(s) {
		if( !s || typeof(s) !== 'string' ) return s;
		s = s.split(' ');
	
		var hasnot = false;
		this.each(function() {
			for(var i=0; i < s.length; i++) {
				var cls = s[i];
				if( !cls || !~this.className.split(' ').indexOf(cls) ) hasnot = true;
			}
		});
	
		return !hasnot;
	};

	fn.rc = fn.removeClass = function(s) {
		return this.classes(s, false);
	};

	fn.cc = fn.clearClass = function() {
		return this.each(function() {
			var el = this;
			el.className = '';
			el.removeAttribute('class');
		});
	};
	
	// evaluation
	fn.is = function(selector, or) {
		if( selector === '*' ) return true;
		if( !selector || typeof(selector) !== 'string' ) return false;
		
		var selectors = selector.split(',');
		if( !this.length || !selectors.length ) return false;
			
		if( or === true ) {
			var isnt = false;
			this.each(function() {
				var contains = false;
				for(var i=0; i < selectors.length;i++) {
					var s = selectors[i].trim();
					if( s && match(this, selectors[i].trim()) ) contains = true;
				}
				
				if( contains === false ) isnt = true;
			});
			return !isnt;
		} else {
			var isnt = false;
			this.each(function() {
				for(var i=0; i < selectors.length;i++) {
					var s = selectors[i].trim();
					if( !s || !match(this, selectors[i].trim()) ) isnt = true;
				}
			});
			return !isnt;
		}
		
		return false;
	};

	fn.not = function(selector) {
		return !this.is(selector);
	};
	
	fn.contains = function(child) {
		if( !child ) return false;
		
		var contains = false;
		if( typeof(child) === 'string' ) {
			var has = false;
			var selectors = child.split(',');
			this.each(function() {
				for(var i=0; i < selectors.length;i++) {
					var s = selectors[i].trim();
					if( s && match(this, s) ) {
						has = true;
						return false;
					}
				}
			});		
			return has;
		} else if( isNode(child) ) {
			this.each(function() {
				if( this === child ) {
					contains = true;
					return false;
				}
			});
		} else {
			return console.log('unsupported compare type', child);
		}
		
		return contains;
	};
	
	fn.hasChild = function(child) {
		if( !child ) return false;
					
		var contains = false;
		this.each(function() {
			if( typeof(child) === 'string' ) {
				var result = this.querySelector(child);
				if( result ) contains = true;
			}
			
			if( this.contains && this.contains(child) ) contains = true;
		});
		
		return contains;
	};
		
	// save & restore
	fn.save = function(name) {
		return this.each(function() {
			var attrs = this.attributes;
			if( !attrs ) return;
			var o = {};
			for(var i= attrs.length-1; i>=0; i--) {
				o[attrs[i].name] = attrs[i].value;
			}
		
			var o = {
				html: this.innerHTML,
				attrs: o
			};
		
			data(this, 'save.' + name, o);
			data(this, 'save.#last', o);
		});
	};

	fn.restore = function(name) {
		return this.each(function() {
			var saved = name ? data(this, 'save.' + name) : data(this, 'save.#last');
		
			if( !saved ) return (name && ~name.indexOf('#')) ? null : console.warn('no saved status', name || '');			
			this.innerHTML = saved.html || '';
		
			// remove current attributes
			var attrs = this.attributes;
			for(var i= attrs.length-1; i>=0; i--) {
				this.removeAttribute(attrs[i].name);
			}
		
			attrs = saved.attrs;
			for(var k in attrs) {
				this.setAttribute(k, attrs[k]);
			}
		});
	};
	
	// creation
	fn.clone = function(args) {
		if( !args ) args = [null];
		if( typeof(args) === 'number') args = new Array(args);
		if( args && typeof(args.length) !== 'number' ) args = [args];
		
		var $ = this.$;
		var arr = [];
		this.each(function() {
			if( !isNode(this) ) return;
			
			for(var i=0, len=args.length; i < len; i++) {
				var el = this.cloneNode(true);
				arr.push(el);
			}
		});
		return $(arr).owner(this);
	};
	
	fn.update = function(accessor, args, fn) {
		var result = this.finds(accessor);
		if( !result.length ) return this.create.apply(this, arguments);
		
		var nothing = {};
		if( !args ) args = [nothing];
		
		var $ = this.$;
		
		var arr = [];
		if( isArrayLike(args) ) {
			for(var i=0; i < args.length; i++) {
				var el = result[i];
				if( isElement(el) ) {
					if( args[i] !== nothing ) $(el).arg(args[i]);
					arr.push(el);
					if( typeof(fn) === 'function' ) fn.call(this, $(el).arg());
				}
			}
		}
		
		return $(arr).owner(this);			
	};

	fn.create = function(accessor, args, fn) {
		if( typeof(accessor) !== 'string' ) return console.error('invalid accessor', accessor);
	
		if( arguments.length === 2 && typeof(args) === 'function' ) {
			fn = args;
			args = [null];
		}
		
		if( !args ) args = [null];
	
		if( typeof(args) === 'number') args = new Array(args);
		if( typeof(args) === 'string') args = [args];
		if( args && typeof(args.length) !== 'number' ) args = [args];
	
		var document = this.document;
		var $ = this.$;
		
		function create(accessor) {
			if( !accessor || typeof(accessor) !== 'string' ) return console.error('invalid parameter', accessor);

			if( isHtml(accessor) ) {
				return markup(document, accessor).children;
			}
			
			var o = assemble(accessor);
			var tag = o.tag;
			var classes = o.classes;
			var id = o.id;

			if( !tag ) return console.error('illegal accessor(missing tag)', accessor);

			var el = document.createElement(tag);
			if( id ) el.id = id;
			if( classes ) el.className = classes;

			return [el];
		}
		
		var arr = [];
		this.each(function() {
			for(var i=0, len=args.length; i < len; i++) {
				var elements = create(accessor);
				var d = args[i];
				if( elements ) {
					for(var j=0; j < elements.length; j++) {
						var element = elements[j];
						data(element, 'arg', d);
						$(this).append(element);
						arr.push(element);
						if( typeof(fn) === 'function' ) fn.call(element, d);
					}
				}
			}
		});
		
		return $(arr).owner(this);
	};
	
	fn.createText = function(text, args, fn) {
		if( typeof(text) !== 'string' ) return console.error('illegal text', text);
		if( arguments.length === 2 && typeof(args) === 'function' ) {
			fn = args;
			args = [null];
		}
		
		if( !args ) args = [null];		
		if( typeof(args) === 'number') args = new Array(args);
		if( typeof(args) === 'string') args = [args];
		if( args && typeof(args.length) !== 'number' ) args = [args];
		
		var document = this.document;
		var $ = this.$;
		
		var arr = [];
		this.each(function() {
			var target = this;
			for(var i=0, len=args.length; i < len; i++) {
				var t = document.createTextNode(text);
				var data = args[i];
				
				$(this).append(t);
				arr.push(t);
				if( typeof(fn) === 'function' ) fn.call(t, data);
			}
		});
		
		return this.$(arr).owner(this);
	};
	
	// find in current selector
	fn.first = function() {
		return this.$(this[0]).owner(this);
	};

	fn.last = function() {
		return this.$(this[this.length - 1]).owner(this);
	};

	fn.at = function(index) {
		return this.$(this[index]).owner(this);
	};

	fn.filter = fn.except = function(fn) {
		return this.subset(fn, false);
	};

	fn.subset = function(selector, positive) {
		var items = [];
	
		positive = (positive === false) ? false : true;
	
		var fn = selector;
		if( typeof(selector) === 'string' ) {
			fn = function() {
				return match(this, selector);
			}
		}
	
		this.each(function() {
			var result = fn.apply(this, arguments);
			if( positive ) {
				if( result === true ) items.push(this);
			} else {
				if( result !== true ) items.push(this);
			}			
		});
		return this.$(items).owner(this);
	};
	
	// find parent & children
	fn.parent = function(real) {
		var arr = [];
		this.each(function() {
			if( !isNode(this) ) arr.push(null);
			
			var p = this.parentNode;
			if( !p ) arr.push(null);		
			else arr.push((real === true ? p : (p.__host__ || p)));
		});
		return array_return(arr);
	};
	
	fn.parents = function(real) {
		var arr = [];
		this.each(function() {
			if( !isNode(this) ) return;
			
			var p = this.parentNode;
			if( !p ) return;	
			else arr.push((real === true ? p : (p.__host__ || p)));
		});
		return this.$(arr).owner(this);
	};

	fn.all = function(includeself) {
		if( includeself === true ) return this.$(this).add(this.$('*', this)).owner(this);
		return this.$('*', this).owner(this);
	};

	fn.finds = function(selector, includeself) {
		if( includeself && this.is(selector) ) return this.$(selector, this).add(this).owner(this);
		else return this.$(selector, this).owner(this);
	};
	
	fn.find = function(selector, includeself) {
		if( !arguments.length ) return this.$([]).owner(this);
		if( includeself && this.is(selector) ) return this.$(selector, this, true).add(this).owner(this);
		return this.$(selector, this, true).owner(this);
	};

	fn.byId = function(cmpid) {
		return this.find('#' + cmpid);
	};
	
	fn.byName = function(name) {
		return this.finds('[name="' + name + '"]');
	};

	fn.one = function(selector, includeself) {
		if( !arguments.length ) selector = '*';
		if( includeself && this.is(selector) ) return this[0];
		var el;
		this.each(function() {
			el = this.querySelector(selector);	
			if( el ) return false;
		});
		return el;
	};
	
	fn.querySelectorAll = function(selector) {
		var arr = [];
		var $ = this.$;
		this.each(function() {
			if( $(this).is(selector, true) ) arr.push(this);
			var result = this.querySelectorAll(selector);
			if( result ) {
				for(var i=0; i < result.length;i++) {
					arr.push(result[i]);
				}
			}
		});
		
		arr.item = function(index) {
			return this[index];
		};
		
		return arr;
	};
	
	fn.querySelector = function(selector) {
		var arr = [];
		var $ = this.$;
		this.each(function() {
			if( $(this).is(selector) ) {
				arr.push(this);
				return false;
			}
			var result = this.querySelector(selector);
			if( result ) {
				arr.push(result);
				return false;
			}
		});
		
		return arr[0];
	};

	fn.children = function(selector) {
		var arr = this.$([]).owner(this);
		this.each(function() {
			if( !isElement(this) ) return;
			
			var arr = [];
			if( typeof(selector) === 'number' ) {
				arr.add(this.children[selector]);
			} else if( typeof(selector) === 'string' && !selector.startsWith('arg:') ) {	// find by selector
				var children = this.children;
				for(var i=0; i < children.length; i++) {
					var el = children[i];
					if( match(el, selector) ) arr.push(el);
				}
			} else {	// all children
				arr.add(this.children);
			}
		});
		return arr;
	};

	fn.contents = function(newcontents) {
		if( !arguments.length ) {
			var arr = this.$([]).owner(this);
			this.each(function() {
				if( !isElement(this) ) return;				
				arr.add(this.childNodes);
			});
			return arr;
		}
		
		return this.html(newcontents);
	};

	fn.visitup = function(visitor, containSelf) {
		if( typeof(visitor) !== 'function' ) return console.error('visitor must be a function');
	
		containSelf = (containSelf === true) ? true : false;
	
		return this.each(function() {			
			if( containSelf && visitor.call(this) === false ) return false;

			var propagation = function(el) {
				var p = el.parentNode;
				if( p ) {
					if( visitor.call(p) !== false ) {
						propagation(p);
					} else {
						return false;
					}
				}
			};

			return propagation(this);
		});
	};

	fn.visit = function(visitor, containSelf, allcontents) {
		if( typeof(visitor) !== 'function' ) return console.error('visitor must be a function');
	
		containSelf = (containSelf === true) ? true : false;
		allcontents = (allcontents === true) ? true : false;
		
		var $ = this.$;
		return this.each(function() {			
			if( containSelf && visitor.call(this) === false ) return false;

			var propagation = function(el) {
				var argc = $((allcontents) ? el.childNodes : el.children);
				argc.each(function() {
					if( visitor.call(this) !== false ) {
						propagation(this);
					} else {
						return false;
					}
				});
			};

			return propagation(this);
		});
	};
			
	// contents		
	fn.text = function(value) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this.innerText);
			});
			return array_return(arr);
		}
	
		this.each(function() {
			this.innerText = resolve(this, value);
		});
		
		return this;
	};

	fn.html = function(value) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				arr.push(this.innerHTML);
			});
			return array_return(arr);
		}
					
		var $ = this.$;
		var document = this.document;
		return this.each(function() {
			if( !isElement(this) ) return;
			
			var target = $(this);
			target.empty();
			
			var values = resolve(this, value);
			if( !values ) return;
			
			if( !isArrayLike(values) ) values = [values];
			
			for(var i=0; i < values.length;i++) {
				target.append(values[i]);
			}
		});
	};

	fn.empty = function() {
		this.each(function() {
			if( !isElement(this) ) return;
			this.innerHTML = '';
		});
		
		return this;
	};
	
	// insertion
	fn.append = function(items) {
		if( !items ) return console.error('items was null', items);
			
		var $ = this.$;
		var document = this.document;
		this.each(function() {
			if( !isElement(this) ) return;
			
			var target = this;
			var array = resolve(this, items);
			
			if( typeof(array) === 'string' && isHtml(array) ) array = $.html(array);
			if( !isArrayLike(array) ) array = [array];

			Array.prototype.forEach.call(array, function(item) {
				var element;
				if( typeof(item) === 'string' ) {
					element = document.createTextNode(item);
				} else if( isNode(item) ){
					element = item;
				}
				
				if( !element || !isNode(element) || (element.nodeName === '#text' && !element.nodeValue) ) return console.error('not a valid node', items, array, element);
				
				target.appendChild(element);
			});
		});
		
		return this;
	};
		
	fn.appendTo = function(target) {			
		var $ = this.$;
		return this.each(function() {
			var dest = resolve(this, target);
		
			if( typeof(dest) === 'string' ) dest = $(dest);
			if( dest instanceof Commons ) dest = dest[dest.length - 1];
		
			if( !isNode(this) || (this.nodeName === '#text' && !this.nodeValue) ) return;
			if( dest ) $(dest).append(this);
		});
	};

	fn.prepend = function(items) {
		if( !items ) return console.error('items was null', items);
		
		var $ = this.$;
		return this.each(function() {
			if( !isElement(this) ) return;
			
			var target = this;
			var array = resolve(this, items);
			if( !isArrayLike(array) ) array = [array];

			Array.prototype.forEach.call(array, function(item) {
				var element;
				if( isHtml(item) ) {
					element = $.html(item);
				} else if( typeof(item) === 'string' ) {
					element = document.createTextNode(item);
				} else if( isNode(item) ){
					element = item;
				}
				
				if( !element || !isNode(element) || (element.nodeName === '#text' && !element.nodeValue) ) return console.error('not a valid node', items, element);
				
				if( target.childNodes.length ) target.insertBefore(element, target.childNodes[0]);
				else target.appendChild(element);
			});
		});
		
		return this;
	};

	fn.before = fn.insertBefore = function(items) {
		if( !items ) return console.error('items was null', items);
		
		var $ = this.$;
		this.each(function() {
			var els = resolve(this, items);
		
			if( !(els instanceof Commons) ) els = $(els);
		
			var target = this.parentNode;
			var before = this;
			if( target ) {
				els.each(function() {
					if( !isNode(this) || this.nodeName === '#text' && !this.nodeValue ) return;
					//console.error('before', this, before, target);
					target.insertBefore(this, before);
				});
			}
		});
		
		return this;
	};

	fn.after = fn.insertAfter = function(items) {
		if( !items ) return console.error('items was null', items);
		
		var $ = this.$;
		this.each(function() {
			var els = resolve(this, items);
		
			if( !(els instanceof Commons) ) els = $(els);
		
			var target = this.parentNode;
			if( target ) {
				var before = this.nextSibling; //;target.children[target.children.indexOf(this) + 1];
				els.each(function() {
					if( !isNode(this) || this.nodeName === '#text' && !this.nodeValue ) return;
					if( before ) target.insertBefore(this, before);
					else target.appendChild(this);
				});
			}
		});

		return this;
	};

	fn.prependTo = function(target) {			
		var $ = this.$;
		return this.each(function() {
			var dest = resolve(this, target);
		
			if( typeof(dest) === 'string' ) dest = $(dest);
			if( dest instanceof Commons ) dest = dest[dest.length - 1];
		
			if( !isNode(this) || this.nodeName === '#text' && !this.nodeValue ) return;
			if( dest ) $(dest).prepend(this);
		});
	};

	fn.detach = function() {
		this.each(function() {
			var p = this.parentNode;
			if( p ) p.removeChild(this);
		});
		
		return this;
	};

	fn.unwrap = function() {
		return this.each(function() {
			var p = this.parentNode;
			if( !p ) return;
		
			var nodes = p.childNodes;
			var argc = [];
			if( nodes ) for(var a=0; a < nodes.length;a++) argc.push(nodes[a]);

			if( argc ) {
				if( p.parentNode ) {
					for(var a=0; a < argc.length;a++) {
						p.parentNode.insertBefore(argc[a], p);
					}
					p.parentNode.removeChild(p);
				}
			}
		});
	};

	fn.wrap = function(accessor) {
		var el = this.$.create(accessor)[0];
		if( !isElement(el) ) return console.error('invalid accessor or html', accessor);
			
		var arr = [];
		var $ = this.$;
		this.each(function() {
			var p = this.parentNode;
			var newp = $(el).clone()[0];
			if( p ) p.insertBefore(newp, this);
			newp.appendChild(this);
			arr.push(newp);
		});		
		return $(arr).owner(this);
	};


	// events
	fn.on = function(types, fn, capture) {
		return this.each(function() {
			DOMUtil.on(this, types, fn, capture);
		});
	};

	fn.off = function(types, fn, capture) {
		return this.each(function() {
			DOMUtil.off(this, types, fn, capture);
		});
	};
	
	fn.firethru = function() {
		this.fire.apply(this, arguments);			
		return this;
	};
	
	fn.fire = function(types, values, cancelcable, bubbles) {
		var arr = [];
		this.each(function() {			
			arge.push(DOMUtil.fire(this, types, values, cancelcable, bubbles));
		});
		
		return array_return(arr);
	};
	
	// action & href shim		
	fn.action = function(href, e) {
		var $ = this.$;
		return this.each(function() {
			if( !isElement(this) ) return;
			
			var el = $(this);
			if( !href ) href = el.attr('href');
			if( !href ) return;
			
			if( href.toLowerCase().trim().startsWith('javascript:') ) {
				wrappingstringevent(href.trim().substring(11)).call(this, e);
			} else {
				if( href.startsWith('#') || !target ) {
					location.href = href;
				} else {
					//TODO: target 이 있을경우...어떻게 해야 하나.
					location.href = href;
				}
			}
		});
	};
	
	fn.href = function(href) {
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				var href = this.getAttribute && this.getAttribute('href');
				arr.push(href);
			});				
			return array_return(arr);
		}

		var $ = this.$;
		return this.each(function() {
			var el = $(this);
			
			var tag = el.tag();
			if( !tag ) return;
			
			if( !href ) el.attr('href', false);
			else el.attr('href', href);
							
			href = el.attr('href');
			if( tag !== 'a' ) {
				var listener = el.data('href_listener');
				
				if( !href ) {
					if( listener ) el.off('click', listener);
				} else {
					if( !listener ) {
						listener = function(e) {
							var href = this.getAttribute('href');
							if( href ) el.action(href, e);
						};
					}
					
					el.on('click', listener);
				}
			}
		});
	};
	

	// view handling
	fn.hide = function(options, fn) {
		var self = this;
		var $ = this.$;
		var internal = function() {
			self.each(function() {
				this.style.display = 'none';
				$(this).fire('hide');
			});

			if(fn) fn.apply(self, arguments);
		};

		if( typeof(options) === 'object' ) {
			this.anim(options, scope || this).run(internal);
		} else {
			if( typeof(options) === 'function' ) fn = options;
			internal.call(this);
		}
	
		return this;
	};

	fn.invisible = function(options, fn) {
		var self = this;
		var $ = this.$;
		var internal = function() {
			self.each(function() {
				this.style.visibility = 'hidden';
				$(this).fire('invisible');
			});

			if(fn) fn.apply(self, arguments);
		};

		if( typeof(options) === 'object' ) {
			this.anim(options, scope || this).run(internal);
		} else {
			if( typeof(options) === 'function' ) fn = options;
			internal.call(this);
		}
	
		return this;
	};

	fn.show = function(options, fn) {
		var self = this;
		var $ = this.$;
		var internal = function() {
			self.each(function() {
				var el = $(this);
			
				this.style.display = '';
				this.style.visibility = '';
				if( !this.style.cssText ) el.attr('style', false);
			
				if( el.computed('display').toLowerCase() === 'none' ) this.style.display = 'block';
				if( el.computed('visibility').toLowerCase() === 'hidden' ) this.style.display = 'visible';
				el.fire('show');
			});

			if(fn) fn.apply(self, arguments);
		};

		if( typeof(options) === 'object' ) {
			this.anim(options, scope || this).run(internal);
		} else {
			if( typeof(options) === 'function' ) fn = options;
			internal.call(this);
		}
	
		return this;
	};

	fn.fade = function(start, end) {
		return this.each(function() {
			var el = this;
		
			var opacity = 0;
			el.style.opacity = 0;
			el.style.filter = '';

			var last = +new Date();
			var tick = function() {
				opacity += (new Date() - last) / 400;
				el.style.opacity = opacity;
				el.style.filter = 'alpha(opacity=' + (100 * opacity)|0 + ')';

				last = +new Date();

				if (opacity < 1) {
					(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
				}
			};

			tick();
		});
	};

	fn.fadeIn = function() {
		return this.fade(0, 1);
	};

	fn.fadeOut = function() {
		return this.fade(1, 0);
	};
	

	// status
	fn.staged = function() {		
		var cnt = 0;
		this.each(function() {
			if( document.body.contains(this) ) cnt++;
		});
	
		return (cnt > 0 && cnt === this.length);
	};

	fn.showing = function(index) {
		if( typeof(index) === 'number' ) return isShowing(this[index]);
	
		var cnt = 0;
		this.each(function() {
			if( isShowing(this) ) cnt++;
		});
	
		return (cnt > 0 && cnt === this.length);
	};
	
	// size & position
	fn.boundary = function() {
		var arr = [];
		this.each(function() {
			arr.push(boundary(this));
		});
		return array_return(arr);
	};

	// misc
	fn.bg = function(bg) {
		var $ = this.$;
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				var el = this, o = {};

				if( el.style.backgroundImage ) o['image'] = el.style.backgroundImage;
				if( el.style.backgroundColor ) o['color'] = el.style.backgroundColor;
				if( el.style.backgroundSize ) o['size'] = el.style.backgroundSize;
				if( el.style.backgroundPosition ) o['position'] = el.style.backgroundPosition;
				if( el.style.backgroundAttachment ) o['attachment'] = el.style.backgroundAttachment;
				if( el.style.backgroundRepeat ) o['repeat'] = el.style.backgroundRepeat;
				if( el.style.backgroundClip ) o['clip'] = el.style.backgroundClip;
				if( el.style.backgroundOrigin ) o['origin'] = el.style.backgroundOrigin;

				arr.push(o);
			});
			return array_return(arr);
		}
	
		if( typeof(bg) === 'string' ) {
			var s = bg.trim().toLowerCase();
			if( (!~bg.indexOf('(') && !~bg.indexOf(' ')) || bg.startsWith('rgb(') || bg.startsWith('rgba(') || bg.startsWith('hsl(') || bg.startsWith('hlsa(') ) bg = {'color':bg};
			else bg = {'image':bg};
		}
	
		if( typeof(bg) !== 'object' ) return this;
	
		return this.each(function() {
			var el = $(this);	
			el.style('background-image', bg['image']);
			el.style('background-color', bg['color']);
			el.style('background-size', bg['size']);
			el.style('background-position', bg['position']);
			el.style('background-attachment', bg['attachment']);
			el.style('background-repeat', bg['repeat']);
			el.style('background-clip', bg['clip']);
			el.style('background-origin', bg['origin']);
		});
	};

	fn.font = function(font) {
		var $ = this.$;
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				var el = this, o = {};
			
				if( el.style.fontFamily ) o['family'] = el.style.fontFamily;
				if( el.style.fontSize ) o['size'] = el.style.fontSize;
				if( el.style.fontStyle ) o['style'] = el.style.fontStyle;
				if( el.style.fontVarient ) o['variant'] = el.style.fontVarient;
				if( el.style.fontWeight ) o['weight'] = el.style.fontWeight;
				if( el.style.fontSizeAdjust ) o['adjust'] = el.style.fontSizeAdjust;
				if( el.style.fontStretch ) o['stretch'] = el.style.fontStretch;
				if( el.style.letterSpacing ) o['spacing'] = el.style.letterSpacing;
				if( el.style.lineHeight ) o['height'] = el.style.lineHeight;
	
				arr.push(o);
			});
			return array_return(arr);			
		}
	
		return this.each(function() {
			var el = $(this);
			if( typeof(font) === 'string' ) {
				el.style('font', font);
			} else if( typeof(font) === 'number' ) {
				el.style('font-size', font + 'px');
			} else if( typeof(font) === 'object' ){
				el.style('font-family', font['family']);
				el.style('font-size', font['size']);
				el.style('font-style', font['style']);
				el.style('font-variant', font['variant']);
				el.style('font-weight', font['weight']);
				el.style('font-size-adjust', font['adjust']);
				el.style('font-stretch', font['stretch']);
				el.style('letter-spacing', font['spacing']);
				el.style('line-height', font['height']);
			}
		});
	};
	
	fn.routes = function(routes) {
		var $ = this.$;
		if( !arguments.length ) {
			var arr = [];
			this.each(function() {
				if( !isElement(this) ) return arr.push(null);
				var routes = $(this).data('routes');
				arr.push(routes);					
			});
			return array_return(arr);
		}
		
		return this.each(function() {
			if( !isElement(this) ) return;
			
			if( routes === false ) {
				routes = $(this).data('routes');
				if( !routes ) return;
			
				for(var k in routes) {
					$(this).route(k, false);
				}
			} else if( typeof(routes) === 'object' ) {
				for(var k in routes) {
					$(this).route(k, routes[k]);
				}
			} else {
				return console.error('illegal arguments', routes);
			}
			return this;
		});
	};
	
	fn.route = function(hash, fn) {
		if( !arguments.length ) return console.error('missing arguments:hash');
		
		var $ = this.$;
		if( arguments.length === 1 ) {
			var arr = [];
			this.each(function() {
				if( !isElement(this) ) return arr.push(null);
				var routes = $(this).data('routes');
				if( !routes ) return arr.push(null);
				var route = routes[hash];
				arr.push(route);
			});
			return array_return(arr);
		}
		
		return this.each(function() {
			if( !isElement(this) ) return;
			
			var el = $(this);
			if( typeof(hash) === 'string' && fn === false ) {
				var routes = el.data('routes');
				if( routes && routes[hash] ) {				
					routes[hash] = null;
					try { delete routes[hash]; } catch(e) {}				
					el.fire('route.removed', {hash:hash});
				}
			} else if( typeof(hash) === 'string' && typeof(fn) === 'function' ) {
				var routes = el.data('routes');
				if( !routes ) {
					routes = {};
					el.data('routes', routes);
				}

				routes[hash] = fn;
				
				var listener = el.data('routes.listener');
				if( !listener ) {
					listener = function(e) {
						var hash = e.detail.hash;
						if( hash === '' ) hash = '@default';
						
						var current = routes[hash];
						var notfound = routes['@notfound'];
						
						if( !current && !notfound ) {
							return;
						}
						
						var init = routes['@init'];
						var changed = routes['@changed'];
						var before = routes['@before'];
						var after = routes['@after'];
						var def = routes['@default'];
						var notfound = routes['@notfound'];
					
						if( !this._router_inited && init ) init.call(this, e);
						else if( changed ) changed.call(this, e);
					
						if( !current ) {
							if( !this._router_inited ) current = current || def;
							else current = current || notfound;
						}
					
						if( hash === '' ) current = def;
					
						this._router_inited = true;
					
						if( before ) before.call(this, e);
						if( current ) current.call(this, e);
						if( after ) after.call(this, e);
					};
					
					el.data('routes.listener', listener);
					el.on('route', listener);
				}
			
				el.fire('routeadded', {hash:hash, handler:fn});
			} else {
				return console.error('illegal arguments', hash, fn);
			}
		});
	};
	
	fn.fireroute = function(hash) {
		if( !arguments.length ) hash = location.hash || '';
		if( hash.startsWith('#') ) hash = hash.substring(1);
		
		var $ = this.$;
		this.visit(function() {
			if( !isElement(this) ) return;
			var el = $(this);
			var e = el.fire('route', {
				hash: hash
			});
			if( e.cancelBubble === true ) return false;
		}, true);
		return this;
	};
	
	// animator
	fn.animate = function(options, callback) {
		return new AnimationGroup(this, options, this, this).run(callback).out();
	};

	fn.animator = function(options, scope) {
		return new AnimationGroup(this, options, scope || this, this);
	};
})();

