/**
 * pbjs JavaScript Framework v0.4.4
 * 2011 Niek Saarberg
 */

(function ( window ){

"use strict";

var local = {},
	slice = Array.prototype.slice,
	doc = document,
	docElement = doc.documentElement,
	body = document.body;

local.UID = 0;

/**
 * Object methods
 */

/**
 * Extend object with another
 *
 * @param object dest
 * @param object original
 * @return object
 */
Object.extend = function ( dest, original ) {

	var prop;

	for( prop in original ) {

		if( original.hasOwnProperty( prop ) ) {

			dest[prop] = original[prop];
		}
	}

	return dest;
};

/**
 * Extend object with another, existing keys are not overwriten
 *
 * @param object dest
 * @param object original
 * @return object
 */
Object.extendIfNotExists = function ( dest, original ) {

	var prop;

	for( prop in original ) {

		if( original.hasOwnProperty( prop ) && typeof dest[prop] === "undefined" ) {

			dest[prop] = original[prop];
		}
	}

	return dest;
};

/**
 * Ecma5
 */
Object.extendIfNotExists(Object, {

	/**
	 * Retrieve keys from object as array
	 *
	 * @param object object
	 * @return array
	 */
	keys: function ( object ) {

		if ( this === null || Object.isObject( object ) === false ) {

			throw new TypeError();
		}

		var result = [],
			key;

		for( key in object ) {

			if( object.hasOwnProperty( key ) ) {

				result.push( key );
			}
		}

		return result;
	},

	/**
	 * Nasty solution to retrieve objectPrototype
	 *
	 * Note: constructor could be overwriten.. so it`s not the best solution
	 * Todo: Test, test, test... fix fix fix! Not working properly
	 */
	getPrototypeOf: function ( object ) {

		var constructor = object.constructor,
			oldConstructor;

		if( typeof object.__proto__ !== "undefined" ) {

			return object.__proto__;
		}

		if( object.hasOwnProperty( object.constructor ) ) {

			oldConstructor = object.constructor;

			if( (delete object.constructor) === false ) {

				return null;
			}

			constructor = object.constructor;
			object.constructor = oldConstructor;
		}

		return constructor ? constructor.prototype : null;
	}
});

/**
 * Custom
 */
Object.extend(Object, {

	/**
	 * Returns true if given var is an object
	 *
	 * @param object object
	 * @return boolean
	 */
	isObject: function ( object ) {

		return Object.prototype.toString.call( object ) === "[object Object]";
	},

	/**
	 * Create a clone from given object
	 *
	 * Note, deeper properties {key:{deep:{}}} are references instead
	 * of clone.
	 *
	 * Todo: Should be possible to do a deep clone
	 *
	 * @param object object
	 * @return boolean
	 */
	clone: function ( object ) {

		return Object.extend( {}, object );
	},

	/**
	 * Implementation to forEach an object
	 *
	 * @param object object -> to iterate
	 * @param function fn
	 * @param mixed scope
	 * @return void
	 */
	forEach: function ( object, fn, scope ) {

		var property;

		if( Object.isObject(object) === false || typeof fn !== "function" ) {

			throw new TypeError();
		}

		for( property in object ) {

			if( object.hasOwnProperty( property ) ) {

				fn.call(scope, object[property], property, object);
			}
		}
	}
});

/**
* Array methods
*/

Object.extendIfNotExists(Array, {

	/**
	 * Implementation to check if object is an array
	 *
	 * @param mixed object
	 * @return boolean
	 */
	isArray: function ( object ) {

		if( typeof object !== "object" || object === null || typeof object.length === "undefined" ) {

			return false;
		}

		return true;
	}
});

Object.extendIfNotExists(Array.prototype,{

	/**
	 * Iterate trough array
	 *
	 * @param function fn
	 * @param mixed scope
	 * @param void
	 */
	forEach: function ( fn, scope ) {

		if ( this === null || typeof fn !== "function" ) {

			throw new TypeError();
		}

		var length = this.length,
			i = 0;

		while ( i < length ) {

			fn.call(scope, this[i], i, this);

			i++;
		}
	},

	/**
	 * Searches the given array for a value and returns the found index or -1 if none found
	 *
	 * Note! Comparsion is done with ===
	 *
	 * @param mixed searchValue
	 * @param integer startIndex
	 * @return integer
	 */
	indexOf: function ( searchValue, startIndex ) {

		if ( this === null ) {

			throw new TypeError();
		}

		var length = this.length;

		startIndex = startIndex || 0;

		if( length <= startIndex || length === 0 ) {

			return -1;
		}

		while( startIndex < length ) {

			if ( this[startIndex] === searchValue ) {

				return startIndex;
			}

			startIndex++;
		}

	    return -1;
	},

	/**
	 * Searches the given array reversed for a value and returns the found index or -1 if none found
	 *
	 * Note! Comparsion is done with ===
	 *
	 * @param mixed searchValue
	 * @param integer stopIndex
	 * @return integer
	 */
	lastIndexOf: function ( searchValue, stopIndex ) {

		if ( this === null ) {

			throw new TypeError();
		}

		var length = this.length;

		stopIndex = stopIndex || 0;

		if( length <= stopIndex || length === 0 ) {

			return -1;
		}

		while( stopIndex <= length ) {

			length--;

			if ( this[length] === searchValue ) {

				return length;
			}
		}

	    return -1;
	},

	/**
	 * Iterate trough array and return new array with filtered values
	 *
	 * @param function fn
	 * @param scope mixed
	 * @return array
	 */
	filter: function ( fn, scope ) {

		if ( this === null || typeof fn !== "function" ) {

			throw new TypeError();
		}

		var result = [],
			i = 0,
			length = this.length;

		while ( i < length ) {

			if( !!fn.call(scope, this[i], i, this) ) {

				result.push( this[i] );
			}

			i++;
		}

		return result;
	},

	/**
	 * Iterate trough array and return true when <b>all</b> values match
	 *
	 * @param function fn
	 * @param mixed scope
	 * @return boolean
	 */
	every: function ( fn, scope ) {

		if ( this === null || typeof fn !== "function" ) {

			throw new TypeError();
		}

		var length = this.length,
			i = 0;

		while ( i < length ) {

			if( fn.call(scope, this[i], i, this) === false ) {

				return false;
			}

			i++;
		}

		return true;
	},

	/**
	 * Return new array with modified values
	 *
	 * @param function fn
	 * @param mixed scope
	 * @return boolean
	 */
	map: function ( fn, scope ) {

		if ( this === null || typeof fn !== "function" ) {

			throw new TypeError();
		}

		var length = this.length,
			result = new Array( length ),
			i = 0;

		while ( i < length ) {

			if( i in this ) {

				result[i] = fn.call(scope, this[i], i, this);
			}

			i++;
		}

		return result;
	},

	/**
	 * Iterate trough array and return true when atleast one value matches
	 *
	 * @param function fn
	 * @param mixed scope
	 * @return boolean
	 */
	some: function ( fn, scope ) {

		if ( this === null || typeof fn !== "function" ) {

			throw new TypeError();
		}

		var length = this.length,
			i = 0;

		while ( i < length ) {

			if( fn.call(scope, this[i], i, this) === true ) {

				return true;
			}

			i++;
		}

		return false;
	},

	reduce: function () {


	},

	reduceRight: function () {


	}


});

/**
 * Custom
 */
Object.extend(Array.prototype, {

	/**
	 * Clone array
	 *
	 * @return array
	 */
	clone: function () {

		return this.slice(0);
	},

	/**
	 * Empty array
	 *
	 * @return array
	 */
	empty: function () {

		this.length = 0;
		return this;
	},

	/**
	 * Remove given value from array
	 *
	 * @param mixed value
	 * @return array
	 */
	remove: function ( value ) {

		var i = this.indexOf(value);

		if( i !== -1 ) {

			this.splice( i, 1 );
		}

		return this;
	}
});

/**
* Function methods
*/

/**
 * Ecma 5
 */
Object.extendIfNotExists(Function.prototype,{

	/**
	 * Created a wrapper function around the `this` object
	 *
	 * @param mixed scope
	 * @param [mixed] additional arguments
	 * @return function
	 */
	bind: function ( scope/*, arg1, argN*/ ) {

		var _args = slice.call( arguments, 1 ),
			fn = this;

		return function () {

			return fn.apply( scope, _args.concat( slice.call( arguments, 0 ) ) );
		};
	}
});

/**
 * Custom
 */
Object.extend(Function.prototype, {

	bindAsEventListener: function ( scope/*, arg1, argN*/ ) {

		var _args = slice.call( arguments, 1 ),
			fn = this;

		return function ( event ) {

			var args = [event || window.event].concat( _args );

			fn.apply( scope, args );
		};
	},

	/**
	 * SLASHED OUT TILL I FIGURE OUT A NAME :)
	 *
	 * /
	bindReversed: function ( scope ) {

		var _args = slice.call( arguments, 1 ),
			fn = this;

		return function () {

			return fn.apply( scope, slice.call( arguments, 0 ).concat( _args ) );
		};
	},*/

	/**
	 * Delay the execution of the function
	 *
	 * @param integer timeout -> in miliseconds
	 * @return timer
	 */
	delay: function ( timeout ) {

		var _args = slice.call( arguments, 1 ),
			fn = this;

		return window.setTimeout(function (){

			return fn.apply( fn, _args );
		}, timeout);
	}
});

/**
* String methods
*/

/**
 * Add reference to native substr
 */
var substr = String.prototype.substr;

/**
 * In the official ecma5 specifications the trim/trimLeft/trimRight methods can't handle
 * an additional arg to trim the string with. So trim methods will be overwriten!
 */
Object.extend(String.prototype,{

	/**
	 * Trim begin and end of string
	 *
	 * @param string char -> default whitespace
	 * @return string
	 */
	trim: function ( chr ) {

		chr = chr || "\\s";

		return this.replace( new RegExp("(^["+chr+"]+|["+chr+"]+$)", "g"), "" );
	},

	/**
	 * Trim begin of string
	 *
	 * @param string char -> default ' '
	 * @return string
	 */
	trimLeft: function ( chr ) {

		return this.replace( new RegExp("(^"+(chr || "\\s")+"+)", "g"), "" );
	},

	/**
	 * Trim end of string
	 *
	 * @param string char -> default ' '
	 * @return string
	 */
	trimRight: function ( chr ) {

		return this.replace( new RegExp("("+(chr || "\\s")+"+$)", "g"), "" );
	},

	/**
	 * Makes the first char of the given string uppercase
	 *
	 * Notice that the string wont be lowercased! Only the first
	 * char is touched.
	 *
	 * Example 1
	 * # 'dennis'.ucfirst();
	 * # // Output: Dennis
	 * # 'DENNIS'.ucfirst();
	 * # // Output: DENNIS
	 *
	 * @return string
	 */
	ucfirst: function () {

		return this.charAt(0).toUpperCase()+this.substr(1);
	},

	/**
	 * Add additional char to string, default before
	 *
	 * Todo: Rename string arg
	 *
	 * Example 1
	 * # '6'.pad(2);
	 * # // Output: 66
	 * # 6'.pad(2, '0');
	 * # // Output: 60
	 * # 6'.pad(2, '0', true);
	 * # // Output: 06
	 * # abc'.pad(2, 'd');
	 * # // Output: abc
	 *
	 * @param integer length
	 * @param string string
	 * @param addAfter boolean
	 * @return string
	 */
	pad: function ( length, chr, addAfter ) {

		var diff = length - this.length;

		chr = String(chr);

		if( typeof chr === "undefined" || chr.length !== 1 ) {

			chr = String(this);
		}

		if( diff <= 0 ) {

			return String(this);
		}

		chr = chr.repeat(diff);

		return addAfter === true ? chr+this : this+chr;
	},

	/**
	 * Repeat string a given times
	 *
	 * Example 1
	 * # 'Allen'.repeat(4);
	 * # // Output: AllenAllenAllenAllen
	 *
	 * @param integer times
	 * @return string
	 */
	repeat: function ( times ) {

		return (new Array( times+1 )).join( this );
	},

	/**
	 * Replaces piece from string with truncation param. Could be splitted from
	 * the center or the end of the string.
	 *
	 * @param integer max_length -> Default 30
	 * @param string truncation char(s) -> Default '...'
	 * @param boolean truncateCenter -> Truncate from center?
	 */
	truncate: function ( max_length, truncation, truncateCenter ) {

		var stringLength = this.length;

		truncateCenter = truncateCenter || false;
		max_length = max_length >>> 0 || 30;
		truncation = typeof truncation === 'undefined' ? '...' : truncation;

		if( stringLength <= max_length ) {

			return String(this);
		}

		if( truncateCenter === true ) {

			var diff = ((stringLength + truncation.length) - max_length) / 2,
				add = (diff % 1) === 0 ? 0 : 1,
				center = Math.round(this.length / 2);

			if( add ) {

				diff = Math.floor( diff );
			}

			return this.substr( 0, center - diff )+truncation+this.substr( center + diff + add );
		}

		return this.slice( 0, max_length-truncation.length )+truncation;
	},

	/**
	 * Replaces piece from string with truncation param. Could be splitted from
	 * the center or the end of the string. Words are kept intact. So string could
	 * be significaly smaller then max_length.
	 *
	 * @param integer max_length -> Default 30
	 * @param string truncation char(s) -> Default '...'
	 * @param boolean truncateCenter -> Truncate from center?
	 */
	word_truncate: function ( max_length, truncation, truncateCenter ) {

		var length = this.length;

		truncateCenter = truncateCenter || false;
		max_length = max_length >>> 0 || 30;
		truncation = typeof truncation === 'undefined' ? '...' : truncation;

		if( length <= max_length ) {

			return this;
		}

		if( truncateCenter === false ) {

			return this.slice( 0, this.lastIndexOf( " ", max_length - truncation.length ) )+truncation;
		} else {

			var diff = ((length + truncation.length) - max_length) / 2,
				add = (diff % 1) === 0 ? 0 : 1,
				center = Math.round(length / 2);

			if( add ) {

				diff = Math.floor( diff );
			}

			return this.substr( 0, this.lastIndexOf( " ", center - diff ) )+truncation+this.substr( this.indexOf( " ", center + diff + add ) );
		}
	},

	substr: '.e'.substr(-1) === 'e' ? substr : function ( start, length ) {

		start = start < 0 ? this.length+start : start;

		return substr.call( this, start, length );
	}
});

/**
 * Date
 */
Object.extendIfNotExists(Date, {

	/**
	 * Retrieve the current timestamp in miliseconds
	 *
	 * EC5
	 *
	 * @return int
	 */
	now: function () {

		return (new Date()).getTime();
	}
});

Object.extendIfNotExists(Date.prototype, {

	/**
	 * Retrieve timestring
	 *
	 * EC5
	 *
	 * @return string
	 */
	toISOString: function () {


		return this.getUTCFullYear()+'-'
			+this.pad(this.getUTCMonth()+1)+'-'
			+this.pad(this.getUTCDate())+'T'
			+this.pad(this.getUTCHours())+':'
			+this.pad(this.getUTCMinutes())+':'
			+this.pad(this.getUTCSeconds())+'.'
			+(this.getMilliseconds()).toString().pad(3, 0, true)+'Z';
	}
});

/**
 * Custom
 */
Object.extend(Date.prototype, {

	/**
	 * Clone a date object
	 *
	 * @return Date
	 */
	clone: function () {

		return new Date( this.getTime() );
	},

	/**
	 * Custom pad function for date like number formating
	 *
	 * Internal usage
	 *
	 * @return string/number
	 */
	pad: function ( number ) {

		return number < 10 ? '0'+number : number;
	}
});

/**
 * PHP like format method
 *
 * See the PHP manual
 *
 * Todo: Translations logic
 */

Date.languages = {

	en: {

		days: 'Sunday Monday Tuesday Wendsday Thursday Friday Saturday'.split(' '),
		daysShort: 'Sun Mon Tue Wen Thu Fri Sat'.split(' '),
		months: 'January February March April May June July August September October November December'.split(' '),
		monthsShort: 'Jan Feb Mar Apr May Apr Mar Jun Jul Oct Nov Dec'.split(' ')
	},
	nl: {

		days: 'zondag maandag dinsdag woensdag donderdag vrijdag zaterdag'.split(' '),
		daysShort: 'zo ma di wo do vr za'.split(' '),
		months: 'januari februari maart april mei juni juli augustus september oktober november december'.split(' '),
		monthsShort: 'jan feb mrt apr mei jun jul aug sep okt nov dec'.split(' ')
	}
};

Date.lang = 'en';

local.DateFormatRegExp = /\\?([a-zA-Z])/g;

Object.extend(Date.prototype, {

	format: function ( format, lang ) {

		if( typeof lang === 'undefined' ) {

			lang = Date.lang;
		}

		return format.replace(local.DateFormatRegExp, this.formatChar.bind(this, lang));
	},

	formatChar: function ( lang, matched, unmatched ) {

		switch( matched ) {


			case 'd':
				return this.pad( this.getDate() );

			case 'D':
				return Date.languages[lang].daysShort[this.getDay()];

			case 'j':
				return this.getDate();

			case 'l':
				return Date.languages[lang].days[this.getDay()];

			case 'N':
				return this.getDay() || 7;

			case 'w':
				return this.getDay();

			case 'z':
				return Math.floor( (this - (new Date( this.getFullYear(), 0, 1, 0, 0, 0 ))) / 86400000 );


			case 'W':

				    var target  = new Date(this.valueOf());

				    var dayNr   = (this.getDay() + 6) % 7;

				    target.setDate(target.getDate() - dayNr + 3);

				    var firstThursday = target.valueOf();

				    target.setMonth(0, 1);
				    if (target.getDay() != 4) {
				        target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7);
				    }

				    return this.pad( 1 + Math.ceil((firstThursday - target) / 604800000) ); // 604800000 = 7 * 24 * 3600 * 1000



			case 'F':
				return Date.languages[lang].months[this.getMonth()];

			case 'm':
				return this.pad( this.getMonth()+1 );

			case 'M':
				return Date.languages[lang].monthsShort[this.getMonth()];

			case 'n':
				return this.getMonth()+1;

			case 't':
				var date = this.clone();
				date.setDate(32);

				return 32 - date.getDate();


			case 'L':
				return (new Date(this.getFullYear(), 1, 29)).getDate() === 29;

			case 'Y':
				return this.getFullYear();

			case 'y':
				return String(this.getFullYear()).substr(2);


			case 'a':
				return Math.floor(this.getHours() / 12) ? 'pm' : 'am';

			case 'A':
				return Math.floor(this.getHours() / 12) ? 'PM' : 'AM';

			case 'B':
				return Math.floor( ((this.getHours() * 3600) + (this.getMinutes() * 60) + this.getSeconds()) / 86.4 );

			case 'g':
				var hour = this.getHours();

				return hour / 12 && hour >= 12 ? hour - 12 : hour;

			case 'G':
				return this.getHours();

			case 'h':
				return this.pad( this.formatChar('g') );

			case 'H':
				return this.pad( this.getHours() );

			case 'i':
				return this.pad( this.getMinutes() );

			case 's':
				return this.pad( this.getSeconds() );

			case 'u':
				return this.getTime();


			case 'O':
				return '+'+this.pad(-this.getTimezoneOffset() / 60)+'00';

			case 'P':
				return '+'+this.pad(-this.getTimezoneOffset() / 60)+':00';

			case 'Z':
				return -this.getTimezoneOffset()*60;


			case 'c':
				return this.format('Y-m-dTH:i:sP');

			case 'r':
				return this.format('D, d M Y H:i:s O');

			case 'U':
				return Math.floor(this.getTime() / 1000);

			default:
				return unmatched;
		}
	}
});

Object.extend(Date, {

	_parse: Date.parse,

	parse: function ( datestring ) {

		var milliseconds = Date._parse( datestring ),
			date,
			match;

		if( isNaN(milliseconds) === false ) {

			return milliseconds;
		}

		date = new Date;

		if( match = datestring.match(/([\d]+)-([\d]+)-([\d]+)[T ]?([\d]+)?:?([\d]+)?:?([\d]+)?\+?([\d: Z]+)?/) ) {

			date.setFullYear( match[1] );
			date.setMonth( parseInt(match[2], 10)-1 );	// Months starting from 0 in js
			date.setDate( match[3] );
			date.setHours( match[4] || 0 );
			date.setMinutes( match[5] || 0 );
			date.setSeconds( match[6] || 0 );
			date.setMilliseconds( 0 );

			if( match[7] ) {

				if( match[7] === 'Z' ) {

					date.setHours( date.getHours() - (date.getTimezoneOffset()/60) );
				} else if( -(parseInt(match[7], 10)*60) !== date.getTimezoneOffset() ) {

					date.setHours( date.getHours() + (( -(parseInt(match[7], 10)*60) - (date.getTimezoneOffset()) ) / 60) );
				}
			} else if( !match[4] ) {

				date.setHours( date.getHours() - (date.getTimezoneOffset()/60) );
			}
		}

		return date.getTime();
	}
});

/**
 * Add JSON support
 */
if ( !window.JSON ) {

	window.JSON = (function(){

		var JSON = function (){


		};

		JSON.prototype = {

			toString: function () {

				return '[object JSON]';
			},

			stringify: function () {


			},

			parse: function ( text ) {

				return eval( '('+text+')' );
			}
		};

		return new JSON();
	})();
}


/**
 * Create PB namespace
 *
 * Method will check if given param could be
 * found in Document Object Model(DOM). Founded
 * elements are cached internaly. If element is
 * not found, returns null.
 *
 * Example 1
 * # All lines return the same reference to PB.Element
 * # PB('element_id');
 * # PB( document.getElementById('element_id') );
 * # PB( PB('element_id') );
 *
 * @param string/node/PB.Element element
 * @return PB.Element/null
 */
window.PB = function ( element ) {

	if( !element ) {

		return null;
	}

	if( element instanceof PB.Element ) {

		return element;
	}

	if( typeof element === "string" ) {

		element = doc.getElementById( element );
	}

	if( (!element || (element.nodeType !== 1 && element.nodeType !== 9)) && element !== window ) {

		return null;
	}

	if( typeof element.__PBID__ === 'number' && local.element_cache.hasOwnProperty(element.__PBID__) === true ) {

		return local.element_cache[element.__PBID__];
	}

	element.__PBID__ = PB.id();

	return local.element_cache[element.__PBID__] = new PB.Element( element );
};

PB.VERSION = '0.4.4';

Object.extend(PB, {

	isStrict: doc.compatMode === 'CSS1Compat',

	/**
	 * Creates an unique id for intern caching
	 *
	 * @return Number
	 */
	id: function () {

		return ++local.UID;
	},

	/**
	 * Add OOP structure to js
	 *
	 * var myClass = PB.Class({
	 *
	 * 	// Set some properties
	 *	propertie: 'value',
	 *
	 * 	construct: function () {
	 *
	 * 	// Class constructor code
	 * }
	 * })
	 * 	.extend( parentClass )
	 */
	Class: function ( proto ) {

		var F = proto.constructor === Object
			? function (){

				if( this.parent && this.parent().constructor !== F ) {

					this.parent().constructor.apply(this, arguments);
				}
			}
			: proto.constructor;

		if( proto.constructor ) {

			delete proto.constructor;
		}

		Object.extend(F.prototype, proto);

		/**
		 * Add extend method to our Class instance
		 */
		F.extend = function ( o ) {

			this.prototype.parent = function () {

				return o.prototype;
			};

			Object.extendIfNotExists(this.prototype, o.prototype);

			return this;
		};

		return F;
	}
});

PB.Pattern = {};

PB.Pattern.Observer = PB.Class({

	constructor: function () {

		this.listeners = {};
	},

	on: function ( type, fn, scope ) {

		if( !this.listeners[type] ) {

			this.listeners[type] = [];
		}

		this.listeners[type].push(fn);

		return this;
	},

	off: function ( type, fn, scope ) {

		this.listeners[type].remove(fn);
	},

	emit: function ( type ) {

		if( !this.listeners[type] ) {

			return;
		}

		var args = slice.call( arguments, 1 );

		this.listeners[type].forEach(function ( fn ){

			fn.apply(null, args || []);
		});
	}
});



/**
 *
 */
PB.browser = {};


var testElement = doc.createElement( body ? 'div' : 'body' ),
	testElementParent = body || docElement,
	div = doc.createElement('div');

testElement.appendChild( div );
testElementParent.insertBefore( testElement, testElementParent.firstChild );

PB.supported = {

	cssOpacity: typeof div.style.opacity !== 'undefined',
	cssFloat: typeof div.style.cssFloat !== 'undefined',
	boxModel: false,
	substractBorder: false
};



div.style.width = div.style.paddingLeft = '1px';

PB.supported.boxModel = (div.offsetWidth === 2);

div.style.borderLeft = '1px solid #000';

PB.supported.substractBorder = (div.offsetWidth === 3);

testElementParent.removeChild( testElement );
testElement = testElementParent = div = null;


/**
 * Element core
 */

/**
 * Holds al cached PB elements
 */
window.PB_EL_CACHE = local.element_cache = {};

/**
 * Build an array of DOM collection
 */
local.toArray = function ( collection ) {

	try {

		collection = slice.call(collection);
	} catch(e) {

		var length,
			arr = [],
			i = 0;

		for( length = collection.length; i < length; i++ ) {

			arr[i] = collection[i];
		}

		collection = arr;
	}

	return collection;
};

/**
 * Element closure
 */
PB.Element = function ( node ) {

	this.node = node;

	if( this.node.nodeName ) {

		this.nodeName = this.node.nodeName.toUpperCase();
	}

	this.__storage = {};
}

/**
 * Element visual
 */
Object.extend(PB.Element.prototype, {

	/**
	 * Check if element has class
	 */
	hasClass: function ( className ) {

		return (new RegExp( "(^|\\s)"+className+"($|\\s)" )).test(this.node.className);
	},

	/**
	 * Add class(es) to element
	 *
	 * It's possible to add an array as arg
	 */
	addClass: function ( className ) {

		if( Array.isArray(className) === true ) {

			className.forEach(this.addClass, this);
			return this;
		}

		var node = this.node;

		if( this.hasClass(className) ){

			return this;
		}

		if( node.className === "" ) {

			node.className = className;
		} else {

			node.className += " "+className;
		}

		return this;
	},

	/**
	 * Remove class(es) to element
	 *
	 * It's possible to add an array as arg
	 */
	removeClass: function ( className ) {

		if( Array.isArray(className) === true ) {

			className.forEach(this.removeClass, this);
			return this;
		}

		var node = this.node;

		node.className = node.className.replace( new RegExp( "(^|\\s+)"+className+"(\\s+|$)" ), ' ' ).trim();

		if( node.className === "" ) {

			this.attr( "class", null );
		}

		return this;
	},

	/**
	 * Retrieve the value of given propertie, its posible to force
	 * the retrievement of an computed value;
	 *
	 * Refers to style class
	 */
	getStyle: function ( propertie, computed ) {

		return PB.Style.setNode( this.node ).get( propertie, computed );
	},

	/**
	 * Set the ste style(s) for an element
	 *
	 * Refers to style class, method will act as an bridge
	 */
	setStyle: function ( propertie, value ) {

		var node = this.node,
			Style = PB.Style.setNode( node ),
			i;

		if( typeof value !== "undefined" ) {

			Style.set( propertie, value );
			return this;
		}

		for( i in propertie ) {

			Style.set( i, propertie[i] );
		}

		return this;
	},

	/**
	 * Display element
	 *
	 * Uses the css propertie display
	 */
	show: function () {

		var store = this.__storage;

		this.setStyle({

			display: store.css_display || "block"
		});

		store.css_display = null;

		return this;
	},

	/**
	 * Hide element
	 *
	 * Uses the css propertie display
	 */
	hide: function () {

		var store = this.__storage,
			display = this.getStyle("display");

		if( display === "none" ) {

			return this;
		}

		store.css_display = display;

		this.setStyle({

			display: "none"
		});

		return this;
	},

	isVisible: function () {

		return this.getStyle("display") !== "none";
	},

	/**
	 * Offsetparent
	 */
	offsetParent: function () {

		var node = this.node.parentNode;

		while ( node ) {

			if( PB(node).getStyle("position") !== "static" ) {

				break;
			}

			node = node.parentNode;
		}

		return PB(node);
	},

	getXY: function () {


	},

	/**
	 * Position from relative offsetParent
	 */
	offset: function () {

		var node = this.node,
			x = 0,
			y = 0;

		do {

			x += node.offsetLeft;
			y += node.offsetTop;

			if( PB(node).getStyle("position") !== "static" ) {

				break;
			}

		} while( node = node.offsetParent );

		return {

			left: x,
			top: y
		};
	},

	/**
	 * Position relative to document
	 */
	position: function () {

		var node = this.node,
			x = 0,
			y = 0;

		while( node ) {

			x += node.offsetLeft;
			y += node.offsetTop;

			node = node.offsetParent;
		}

		return {

			left: x,
			top: y
		};
	},

	/**
	 * Get element position in viewport
	 */
	viewportPosition: function () {

		var position = this.position();

		position.top -= window.pageYOffset || doc.body.scrollTop || doc.documentElement.scrollTop;
		position.left -= window.pageXOffset || doc.body.scrollLeft || doc.documentElement.scrollLeft;

		return position;
	},

	/**
	 * Set or get width
	 */
	width: function ( width ) {

		if( typeof width !== "undefined" ) {

			this.setStyle( "width", width );
			return this;
		}

		var node = this.node,
			visible;

		if( node === window ) {

			var element = doc.compatMode === 'CSS1Compat' ? docElement : doc.body,
				overflow = element.style.overflow;

			if( overflow === '' || overflow === 'hidden' ) {

				element.style.overflow = 'hidden';
			}

			width = element.clientWidth;

			element.style.overflow = overflow;

			return width;
		}
		else if( node.nodeType === 9 ) {

			return Math.max(
				node.documentElement.clientWidth,
				node.body.scrollWidth, node.documentElement.scrollWidth,
				node.body.offsetWidth, node.documentElement.offsetWidth
			);
		}

		width = this.getStyle('width');

		if( width > 0 ) {

			return width;
		}

		visible = this.isVisible()

		if( visible === false ) {

			this.show();
		}

		width = node.offsetWidth;

		if( visible === false ) {

			this.hide();
		}

		if( PB.supported.boxModel ) {

			width -= (parseInt(this.getStyle('paddingLeft'), 10) || 0) + (parseInt(this.getStyle('paddingRight'), 10) || 0);
		}

		if( PB.supported.substractBorder ) {

			width -= (parseInt(this.getStyle('borderLeftWidth'), 10) || 0) + (parseInt(this.getStyle('borderRightWidth'), 10) || 0);
		}

		return width;
	},

	innerWidth: function () {

		return this.width() + (this.getStyle('paddingLeft') + this.getStyle('paddingRight'));
	},

	outerWidth: function () {

		var rightWidth = this.getStyle('borderRightWidth');

		return this.innerWidth() + (this.node.clientLeft + (typeof rightWidth === 'string' ? 0 : rightWidth));
	},

	/**
	 * Set or get height
	 */
	height: function ( height ) {

		if( typeof height !== "undefined" ) {

			this.setStyle( "height", height );
			return this;
		}

		var node = this.node,
			visible;

		if( node === window ) {

			var element = doc.compatMode === 'CSS1Compat' ? doc.documentElement : doc.body,
				overflow = element.style.overflow,
				height;

			if( overflow === '' || overflow === 'hidden' ) {

				element.style.overflow = 'hidden';
			}

			height = element.clientHeight;

			element.style.overflow = overflow;

			return height;
		}
		else if( node.nodeType === 9 ) {

			return Math.max(
				node.documentElement.clientHeight,
				node.body.scrollHeight, node.documentElement.scrollHeight,
				node.body.offsetHeight, node.documentElement.offsetHeight
			);
		}

		height = this.getStyle('height');

		if( height > 0 ) {

			return height;
		}

		visible = this.isVisible()

		if( visible === false ) {

			this.show();
		}

		height = node.offsetHeight;

		if( visible === false ) {

			this.hide();
		}

		if( PB.supported.boxModel ) {

			height -= (parseInt(this.getStyle('paddingTop'), 10) || 0) + (parseInt(this.getStyle('paddingBottom'), 10) || 0);
		}

		if( PB.supported.substractBorder ) {

			height -= (parseInt(this.getStyle('borderTopWidth'), 10) || 0) + (parseInt(this.getStyle('borderBottomWidth'), 10) || 0);
		}

		return height;
	},

	innerHeight: function () {

		return this.height() + (this.getStyle('paddingTop') + this.getStyle('paddingBottom'));
	},

	outerHeight: function () {

		var bottomWidth = this.getStyle('borderBottomWidth');

		return this.height() + (this.node.clientTop + (typeof bottomWidth === 'string' ? 0 : bottomWidth));
	},

	/**
	 * Get scrollheight
	 */
	scrollHeight: function () {

		return this.node.scrollHeight;
	},

	/**
	 * Get scrollwidth
	 */
	scrollWidth: function () {

		return this.node.scrollWidth;
	},

	left: function () {

		return this.offset().left;
	},

	top: function () {

		return this.offset().top;
	},

	/**
	 * Get scroll position of element
	 *
	 * @return { left: number, top: number }
	 */
	getScroll: function () {

		var node = this.node,
			body = doc.body,
			scroll = {};

		if( node === doc || node === body || node === window ) {

			scroll.left = PB.isStrict ? docElement.scrollLeft : window.pageXOffset;
			scroll.top = docElement.scrollTop ? docElement.scrollTop : window.pageYOffset;

			scroll.left = scroll.left || body.scrollLeft;
			scroll.top = scroll.top || body.scrollTop;
		} else {

			scroll.left = node.scrollLeft;
			scroll.top = node.scrollTop;
		}

		return scroll;
	},

	/**
	 * Scroll element to given positions
	 */
	scrollTo: function ( x, y ) {

		this.node.scrollLeft = x;
		this.node.scrollTop = y;

		return this;
	}
});

/**
 * Element insertion
 */
Object.extend(PB.Element.prototype, {

	/**
	 * Append given element to self
	 */
	append: function ( element ) {

		if( (element = PB(element)) === null ) {

			return null;
		}

		this.node.appendChild( element.node );

		return this;
	},

	/**
	 * Append self to given element
	 */
	appendTo: function ( target ) {

		if( (target = PB(target)) === null ) {

			return null;
		}

		target.append( this );

		return this;
	},

	/**
	 * Insert self before given element
	 */
	insertBefore: function ( target ) {

		if( (target = PB(target)) === null ) {

			return null;
		}

		target.parent().node.insertBefore( this.node, target.node );

		return this;
	},

	/**
	 * Insert self after given element
	 */
	insertAfter: function ( target ) {

		var nextElement;

		if( (target = PB(target)) === null ) {

			return null;
		}

		if( ( nextElement = target.next()) instanceof PB.Element ) {

			target.parent().node.insertBefore( this.node, nextElement.node );
		} else {

			target.parent().node.appendChild( this.node );
		}

		return this;
	},

	/**
	 * Insert this as first child of given target
	 */
	insertFirst: function ( target ) {

		if( (target = PB(target)) === null ) {

			return null;
		}

		if( target.first() === null ) {

			target.append( this );
		} else {

			this.insertBefore( target.first() );
		}

		return this;
	},

	/**
	 * Replace element with given target
	 */
	replace: function ( target ) {

		if( (target = PB(target)) === null ) {

			return null;
		}

		this.insertBefore( target );

		target.remove();

		return this;
	},

	wrap: function () {


	},

	unwrap: function () {


	},

	/**
	 * Clone element and return new PB.Element instance
	 */
	clone: function ( deep ) {

		var clone = this.node.cloneNode( deep );


		try {

			delete clone.id;
			delete clone.__PBID__;
		} catch (inline){

			clone.id = null;
			clone.__PBID__ = null;
		}




		return PB(clone);
	},


	/**
	 * Set or get innerHTML of an element
	 *
	 * Script tags could be evaled, globaly
	 */
	html: function ( html, evalJS ) {

		if( typeof html === "undefined" ) {

			if( evalJS ) {


			}

			return this.node.innerHTML;
		}

		this.node.innerHTML = html;

		return this;
	},

	/**
	 * Empty element
	 *
	 * Todo: Reseach about mem leaks
	 *
	 * @return PB.Element
	 */
	empty: function () {

		var childs = this.childs();

		childs.forEach(function ( child ){

			child.remove();
		});

		return this;
	},

	/**
	 * Fix removal of events and in pb cache
	 */
	remove: function () {

		var node = this.node;

		if( typeof this.__storage.morph !== 'undefined' ) {

			this.__storage.morph.stop();
		}

		try {

			delete local.element_cache[node.__PBID__];
		} catch ( e ) {

			alert( e.message );
		}

		local.Event.purge( node.__PBID__ );

		if( node.parentNode ) {

			node.parentNode.removeChild( node );
		}

		this.node = node = null;
	}
});

/**
 * Element visual
 */
Object.extend(PB.Element.prototype, {

	/**
	 * Set or get element attribute
	 * If value === null then attribute will be removed
	 */
	attr: function ( key, value ) {

		var node = this.node;

		if( typeof value === 'undefined' ) {

			return node.getAttribute( key );
		} else if ( value === null ) {

			node.removeAttribute( key );
		} else {

			node.setAttribute( key, value );
		}

		return this;
	}
});

/**
 * Element visual
 */
Object.extend(PB.Element.prototype, {

	/**
	 * Set or get value of form element
	 */
	val: function ( value ) {

		if( typeof value === "undefined" ) {

			return this.node.value;
		}

		this.node.value = value;

		return this;
	},

	/**
	 * Use sizzle for this, and change naming!
	 *
	 * Array like form elements
	 *
	 * TEST: Create html form with all element, see firebug request tab, make sure serialize works the same
	 */
	serializeForm: function () {

		var node = this.node,
			elements = local.toArray(node.elements),	//DOMToArray(node.elements),
			type,
			data = {},
			exclude = /file|undefined|reset|button|submit|fieldset/i,
			groups = /radio|checkbox/i,
			length;

		if( this.nodeName !== 'FORM' ) {

			throw new Error('No form found for serialize');
		}

		elements.forEach(function ( element ){

			type = element.type;

			if( exclude.test(type) === false && !(groups.test(type) === true && !element.checked) ) {

				if( type === 'select-multiple' ) {

					data[element.name] = [];

					local.toArray(element.options).forEach(function ( option ){

						if( option.selected ) {

							data[element.name].push( option.value );
						}
					});
				} else {

					data[element.name] = element.value;
				}
			}
		});

		return data;
	}

	/** WRONG FILE
	 * Create a selection in a text field
	 * If no start and end are given then whole text
	 * is selected. If no end is given, all text behind
	 * starting point is selected.
	 *
	 * Todo: Add nodeType check
	 *
	 * The following features are defined in the DOM Range specification: [DOMRANGE]

	    Range interface
	    deleteContents() method
	    selectNodeContents() method
	    setEnd() method
	    setStart() method
	    collapsed attribute
	    endContainer attribute
	    endOffset attribute
	    startContainer attribute
	    startOffset attribute

	 * /
	selectText: function( start, end ) {

		var node = this.node,
			range;

		if( typeof start === "undefined" ) {

			start = 0;
		}

		if( typeof end === "undefined" ) {

			end = this.val().length;// || this.html().length;
		}

		if( node.createTextRange ) {

			range = node.createTextRange();
			range.collapse( true );
			range.moveStart( "character", start );
			range.moveEnd( "character", end - start );
			range.select();
		} else if ( node.setSelectionRange ) {

			node.setSelectionRange( start, end );
		}

		node.focus();
	},*/
});

/**

TODO:
- mouseenter and mouseleave
- Custo events: like element.on(':some', function(){});

How its cached:
cache = {

	element.__PBUID__: {

		node: node,	// Could be fetched from local.ElementCache
		"click": [],
		"mouseup": [
			{
				handler: fn
				responder: fn
			}
		]
	}
}

 */

local.Event = {

	supports_mouseenter_mouseleave: 'onmouseenter' in doc.documentElement && 'onmouseleave' in doc.documentElement,

	manualExtend: false,

	/**
	 * Event cache
	 */
	cache: {},

	/**
	 * Create the event wrapper
	 *
	 * @return function
	 */
	createResponder: function ( uid, type, handler ) {

		return function ( event ) {

			var cacheEntry = local.Event.cache[uid];

			event = local.Event.extend( event, uid );
			handler.call( cacheEntry.node, event );
		};
	},

	/**
	 * Remove all events from element
	 *
	 * Use when removing an element
	 */
	purge: function ( uid ) {

		var cache = local.Event.cache[uid],
			node,
			keys;

		if( !cache ) {

			return;
		}

		node = cache.node;
		keys = Object.keys(cache);

		keys.forEach(function ( type ){

			if( type === 'node' ) {

				return;
			}

			PB(node).stop( type );
		});

		delete local.Event.cache[uid];

		node = null;

		return;
	},

	/**
	 * Extend event manualy for browsers that dont support
	 * Event.prototype
	 */
	extend: function ( event, uid ) {

		if( local.Event.manualExtend === false ) {

			return event;
		}

		var docEl = doc.documentElement,
			body = doc.body;

		Object.extend( event, local.Event.methods );

		event.target = event.srcElement || local.Event.cache[uid].node;

		switch ( event.type ) {

			case 'mouseover':
			case 'mouseenter':
				event.relatedTarget = event.fromElement;
				break;

			case 'mouseout':
			case 'mouseleave':
				event.relatedTarget = event.toElement;
				break;
		}

		if( !event.pageX || !event.pageY ) {

			event.pageX = event.clientX + (docEl.scrollLeft || body.scrollLeft) - (docEl.clientLeft || 0);
			event.pageY = event.clientY + (docEl.scrollTop || body.scrollTop) - (docEl.clientTop || 0);
		}

		event.which = typeof event.keyCode === 'undefined' ? event.charCode : event.keyCode;

		return event;
	}
};

/**
 *
 */
local.Event.methods = {

	stop: function () {

		this.preventDefault();
	    this.stopPropagation();
	}
};

/**
 * Extend event prototype with DOM level 2 events
 */
if (window.addEventListener) {

	Object.extend(Event.prototype, local.Event.methods);
}

/**
 * Add methods for non DOM level 2 events
 */
if( window.attachEvent && !window.addEventListener ) {

	local.Event.manualExtend = true;

	Object.extend(local.Event.methods, {

		stopPropagation: function () {

			this.cancelBubble = true;
		},

		preventDefault: function () {

			this.returnValue = false;
		}
	});
}

/**
 *
 */
Object.extend(PB.Element.prototype, {

	/**
	 *
	 */
	on: function ( type, handler ) {

		var node = this.node,
			uid = node.__PBID__,
			events = local.Event.cache[uid],
			eventsType,
			i;

		if( type === 'mouseenter' && local.Event.supports_mouseenter_mouseleave === false ) {

			type = 'mouseover';
		} else if ( type === 'mouseleave' && local.Event.supports_mouseenter_mouseleave === false ) {

			type = 'mouseout';
		}

		if( !events ) {

			local.Event.cache[uid] = events = {node: node};
		}

		if( !events[type] ) {

			events[type] = [];
		}

		eventsType = events[type];

		i = eventsType.length;

		while( i-- ) {

			if( eventsType[i].handler === handler ) {

				return this;
			}
		}

		var entry = {

			handler: handler,
			responder: local.Event.createResponder( uid, type, handler )
		};

		eventsType.push( entry );

		if( node.addEventListener ) {

			node.addEventListener( type, entry.responder, false );
		} else {

			node.attachEvent( 'on'+type, entry.responder );
		}

		node = null;

		return this;
	},

	/**
	 *
	 */
	stop: function ( type, handler ) {

		var node = this.node,
			uid = node.__PBID__,
			events = local.Event.cache[uid],
			eventsType,
			entry,
			i;

		if( !events ) {

			return this;
		}

		if( !type ) {

			local.Event.purge( uid );
			return this;
		}

		eventsType = events[type];

		if( !eventsType ) {

			return this;
		}

		i = eventsType.length;

		if( !handler ) {

			while( i-- ) {

				this.stop( type, eventsType[i].handler );
			}

			return this;
		}

		while( i-- ) {

			if( eventsType[i].handler === handler ) {

				entry = eventsType[i];
				eventsType.splice( i, 1 );

				if( !eventsType.length ) {

					delete local.Event.cache[uid][type];
				}
				break;
			}
		}

		if( !entry ) {

			return this;
		}

		if( node.removeEventListener ) {

			node.removeEventListener( type, entry.responder, false );
		} else {

			node.detachEvent( 'on'+type, entry.responder );
		}

		node = null;

		return this;
	},

	/**
	 *
	 */
	fire: function ( type ) {

		if( document.createEvent ) {

			var _event = document.createEvent('MouseEvents');

			_event.initMouseEvent(
				type, true, true, window,		// type, canBubble, cancelable, view,
				0, 0, 0, 0, 0,					// detail, screenX, screenY, clientX, clientY,
				false, false, false, false,		// ctrlKey, altKey, shiftKey, metaKey,
				0, null);						// button, relatedTarget

			this.node.dispatchEvent(_event);
		}
		else {

			var _event = document.createEventObject();
			this.node.fireEvent('on'+type, _event);
		}
	}
});

/**
 *
 */
PB.Style = function ( supported ) {

	var getComputedStyle = doc.defaultView && doc.defaultView.getComputedStyle;

	/**
	 * Initialize class
	 *
	 */
	var CSSStyle = function () {};

	CSSStyle.prototype = {

		unitRegex: /px$/i,
		IEOpacity: /alpha\(opacity=(.*)\)/i,

		/**
		 * Hooks are required for reading some multi value css styles
		 */
		getHooks: {

			"border": "borderLeftWidth borderLeftStyle borderLeftColor",
			"borderColor": "borderLeftColor",
			"borderWidth": "borderLeftWidth",
			"borderStyle": "borderLeftStyle",
			"padding": "paddingTop paddingRight paddingBottom paddingLeft",
			"margin": "marginTop marginRight marginBottom marginLeft",
			"borderRadius": "borderRadiusTopleft",
			"MozBorderRadius": "MozBorderRadiusTopleft"
		},

		noUnits: ['zIndex', 'zoom', 'fontWeight', 'opacity'],

		/**
		 *
		 */
		addUnits: function ( value, propertie ) {

			if( this.noUnits.indexOf(propertie) >= 0 ) {

				return value;
			}

			return typeof value === "string" ? value : value+"px";
		},

		/**
		 *
		 */
		removeUnits: function ( value ) {

			return this.unitRegex.test( value ) ? parseInt( value, 10 ) : value;
		},

		/**
		 * Set node
		 */
		setNode: function ( node ) {

			this.node = node.node || node;
			return this;
		},

		/**
		 * Retieve element style, could be forced to retrieve computed style
		 */
		get: function ( propertie, computed ) {

			if( propertie === "float" ) {

				propertie = supported.cssFloat ? "cssFloat" : "styleFloat";
			}

			if( computed === true ) {

				return this.getComputedStyle( propertie );
			}

			return this.getDOMStyle( propertie ) || this.getComputedStyle( propertie );
		},

		/**
		 * retrieve inline style
		 */
		getDOMStyle: function ( propertie ) {

			var node = this.node,
				value = node.style[propertie];

			if( propertie === "opacity" ) {

				if( supported.cssOpacity === false ) {

					value = this.node.style.filter;

					if( !value ) {

						return null;
					}

					value = value.match(this.IEOpacity);

					if( value && value[1] ) {

						return parseFloat(value[1]) / 100;
					}

					return null;
				}

				return value ? parseFloat(value) : null;
			}

			if( value === null || value === "auto" ) {

				return null;
			}

			return this.removeUnits(value);
		},

		/**
		 * Retrieve computed style
		 */
		getComputedStyle: function () {

			return getComputedStyle
				? function ( propertie ) {

					var CSS = doc.defaultView.getComputedStyle( this.node, null ),
						value;

					if( propertie in this.getHooks ) {

						value = this.getHooks[propertie].split(" ").map(function( value ){

							return CSS[value];
						});

						return value.length === 1
							? this.removeUnits(value[0])
							: value.join(" ");
					}

					value = CSS[propertie];

					if( propertie === "opacity" ) {

						return value ? parseFloat(value) : 1.0;
					}

					return value === "auto" ? 0 : this.removeUnits(value);
				}
				: function ( propertie ) {

					var CSS = this.node.currentStyle,
						value;

					if( propertie in this.getHooks ) {

						value = this.getHooks[propertie].split(" ").map(function( value ){

							return CSS[value];
						});

						return value.length === 1
							? this.removeUnits(value[0])
							: value.join(" ");
					}

					if( propertie === "opacity" ) {

						value = CSS.filter;
						value = value.match(this.IEOpacity);

						if( value && value[1] ) {

							return parseInt(value[1]) / 100;
						}

						return 1.0;
					}

					value = CSS[propertie];

					return typeof value === 'undefined' ? null
						: value === "auto" ? 0 : this.removeUnits(value);
				}
		}(),

		/**
		 * Set CSS style
		 */
		set: function ( propertie, value ) {

			if( propertie === "opacity" ) {

				this.setOpacity( value );
			} else {

				this.node.style[propertie] = this.addUnits(value, propertie);
			}
		},

		/**
		 * Set crossbrowser opacity
		 */
		setOpacity: function () {

			return supported.cssOpacity
				? function ( value ) {

					this.node.style.opacity = value.toString();
				}
				: function ( value ) {

					this.node.style.filter = "alpha(opacity="+(value*100)+")";
				};
		}()
	};

	return new CSSStyle();
}( PB.supported );

/**
 * Element traversal
 */
Object.extend(PB.Element.prototype, {

	/**
	 * Retrieve elements parent node
	 */
	parent: function () {

		return PB( this.node.parentNode );
	},

	/**
	 * Retrieve element childs nodes as array
	 *
	 * @todo: Would it be better to change node && (node = node.nextSibling) to if( !node ) return childs;
	 */
	childs: function () {

		var childs = [],
			node = this.node.firstChild;

		if( !node ) {

			return childs;
		}

		do {

			if( node.nodeType === 1 ) {

				childs.push( PB(node) );
			}

		} while ( node = node.nextSibling );

		return childs;
	},

	/**
	 * Retrieve next node
	 */
	next: function () {

		var sibling = this.node;

		while( sibling = sibling.nextSibling ) {

			if( sibling.nodeType === 1 ) {

				return PB( sibling );
			}
		}

		return null;
	},

	/**
	 * Retrieve previous node
	 */
	prev: function () {

		var sibling = this.node;

		while( sibling = sibling.previousSibling ) {

			if( sibling.nodeType === 1 ) {

				return PB( sibling );
			}
		}

		return null;
	},

	/**
	 * Retrieve first child of self
	 */
	first: function () {

		var child = this.node.firstChild;

		do {

			if( child.nodeType === 1 ) {

				return PB( child );
			}

		} while( child = child.nextSibling );

		return null;
	},

	/**
	 * Retrieve last child of self
	 */
	last: function () {

		var child = this.node.lastChild;

		do {

			if( child.nodeType === 1 ) {

				return PB( child );
			}
		} while ( child = child.previousSibling );

		return null;
	},

	/**
	 * Find out if element is descendants of given element
	 */
	descendantOf: function ( element ) {

		var node = this.node,
			max = 50,
			body = PB(doc.body);

		element = PB(element).node;

		do {

			if( node === element ) {

				return true;
			}

			if( !--max || node === body ) {

				break;
			}

		} while ( node = node.parentNode );

		return false;
	},

	/**
	 * Find elements by CSS expression
	 */
	find: function ( expression ) {

		try {

			return local.toArray( this.node.querySelectorAll( expression ) ).map(PB);
		} catch(e){}

		return window.Sizzle( expression, this.node ).map(PB);
	},

	/**
	 * Tries finding the given parent with a CSS expression
	 */
	closest: function ( expression, iterateCount ) {

		var element = this,
			body = PB(doc.body),
			matches = window.Sizzle.matches,
			set = [],
			result;

		iterateCount = iterateCount || 50;

		do {

			set[0] = element.node;
			result = matches( expression, set );

			if( result.length ) {

				return PB(result[0]);
			}

			if( !--iterateCount ) {

				break;
			}

		} while( element = element.parent() );

		return null;
	},

	/**
	 * Check if node matches the given expression
	 */
	matches: function ( expression ) {

		return window.Sizzle.matches( expression, [this.node] ).length > 0;
	}
});


PB.Animation_Observer = function () {

	this.isRunning = false;

	this.timerID = null;

	this.observers = [];

	this.notifyWrapper = this.notify.bind(this);
};

PB.Animation_Observer.prototype = {

	attach: function ( observer ) {

		if( this.observers.indexOf( observer ) >= 0 ) {

			return;
		}

		this.observers.push( observer );

		if( this.isRunning === false ) {

			this.start();
		}
	},

	detach: function ( observer ) {

		this.observers.remove( observer );

		if( this.isRunning === true && this.observers.length === 0 ) {

			this.stop();
		}
	},

	notify: function () {

		var timestamp = Date.now();

		this.observers.forEach(function( observer ){

			if( !observer ) {

				return;
			}

			observer.update( timestamp );
		});
	},

	start: function () {

		this.isRunning = true;

		this.timerID = setInterval( this.notifyWrapper, 1000 / 40 );
	},

	stop: function () {

		this.isRunning = false;

		clearTimeout( this.timerID );
	}
};

/**
 * Create singleton
 */
PB.Animation_Observer = new PB.Animation_Observer();

/**
 * Element morphing class
 */

var PropertyCache = {};

PB.Morph = function () {

	this.isRunning = false;

	this.cssText = '';

	this.color = new PB.Color();
};

PB.Morph.prototype = {

	colorProperties: ['backgroundColor', 'borderColor', 'color', 'outlineColor'],

	/* Skipping this part for a while
	groupedStyles: {

		border: ['borderWidth', 'borderStyle', 'borderColor'],
		boxShadow: []
	},*/

	decamalizeExp: /([A-Z])/g,

	reset: function ( element, options ) {

		if( this.isRunning === true ) {

			this.stop();
		}

		this.element = element;

		this.after = options.after;

		this.duration = options.duration * 1000;

		this.effect = PB.MorphEffects[options.effect] || PB.MorphEffects['ease'];	// PB.Morph.effects

		this.styleInfo = {};

		this.cssText = (this.element.node.style.cssText || "").toLowerCase();

		if( this.cssText !== "" && this.cssText.charAt(this.cssText.length-1) !== ';' ) {

			this.cssText += ";";
		}

		Object.forEach(options.to, this.setStartEndStyles, this);

		return this;
	},

	setStartEndStyles: function ( value, styleName ) {

		var from = this.element.getStyle( styleName, true ),
			unit,
			diff = 0,
			realName = styleName.replace( this.decamalizeExp, "-$1" ).toLowerCase();

		if( typeof from === "undefined" ) {

			return;
		}

		if( typeof value === "number" ) {

			if( typeof from === "string" && from.charAt(from.length-1) === '%' ) {

				from = this.percentToPixel( styleName );
			}

			diff = value - from;
			unit = styleName === 'opacity' ? 0 : 'px';
		}
		else if ( value.charAt(value.length-1) === '%' ) {

			if( parseFloat(from) === from ) {

				from = this.pixelToPercent( styleName, from );
			} else {

				from = parseFloat( from );
			}

			value = parseFloat( value );

			diff = value - from;
			unit = '%';
		}
		else if ( this.colorProperties.indexOf( styleName ) >= 0 ) {

			if( from === "transparent" ) {

				from = "#FFFFFF";
			}

			value = new PB.Color( value );
			from = new PB.Color( from );
			diff = value.diff( from );

			unit = 'color';
		}

		this.styleInfo[styleName] = {

			to: value,
			from: from,
			diff: diff,
			unit: unit,
			realCssName: realName
		};

		if( this.cssText !== '' ) {

			this.removeFromCssText( realName );
		}
	},

	/**
	 * @todo add right, bottom, more?
	 */
	percentToPixel: function ( styleName ) {

		switch( styleName ) {

				case 'height':

					return this.element.height();

				case 'width':

					return this.element.width();

				case 'left':

					return this.element.offset().left;

				case 'top':

					return this.element.offset().top;
			}

			return 0;
	},

	pixelToPercent: function ( styleName, value ) {

		var element = this.element.parent(),
			body = doc.body,
			percent = 0;

		while( element ) {

			if( element.getStyle("position") === "absolute" || element.node === body ) {

				break;
			}

			element = element.parent();
		}

		if( element.node === body ) {

			element = PB( doc );
		}

		value = parseFloat( value );

		switch( styleName ) {

			case "left":
			case "right":
			case "width":
				return value / (element.width() / 100);

			case "top":
			case "bottom":
			case "height":
				return value / (element.height() / 100);

			default:
				return 0;
		}
	},

	removeFromCssText: function ( property ) {


		if( typeof PropertyCache[property] === "undefined" ) {

			PropertyCache[property] = new RegExp("((^|\\s)"+property+":\\s[a-z0-9\.\(\)=\\s,]+;)");
		}

		this.cssText = this.cssText.replace( PropertyCache[property], '' );
	},

	start: function () {

		this.isRunning = true;

		this.startedAt = Date.now();
		this.endsAt = this.startedAt + this.duration;

		PB.Animation_Observer.attach( this );

		return this;
	},

	/**
	 * Update method, invoked by PB.Animation_Observer
	 */
	update: function ( timestamp ) {

		var position = 1 - ((this.endsAt - timestamp) / this.duration );

		if( timestamp >= this.endsAt ) {

			this.render( 1 );
			this.stop();

			return;
		}

		this.render( position );
	},

	/**
	 * Stop morphing
	 *
	 * Todo: Remove this.element and this.after reference
	 */
	stop: function ( skipEnd ) {

		if( this.isRunning === false ) {

			return this;
		}

		var after = this.after,
			element = this.element;

		PB.Animation_Observer.detach( this );

		if( skipEnd === true ) {

			this.render( 1 );
		}

		this.element = null;
		this.after = null;

		this.isRunning = false;

		if( after ) {

			after( element );
		}

		element = null;

		return this;
	},

	render: function ( position ) {

		var styleNames = Object.keys( this.styleInfo ),		// Could be placed somewhere else
			cssText = this.cssText,
			length = styleNames.length,
			ref,
			t = this.effect( position ),
			value;

		while( length-- ) {

			ref = this.styleInfo[styleNames[length]];

			if( ref.unit === 'color' ) {

				value = ref.from.get();

				this.color.set(
						value.R + ( ref.diff.R * t ), //R
						value.G + ( ref.diff.G * t ), //G
						value.B + ( ref.diff.B * t )  //B
					);

				cssText += ref.realCssName+": "+this.color.toRGB()+"; ";
			} else {

				value = ref.from + (ref.diff * t);

				if( ref.realCssName === 'opacity' && PB.supported.cssOpacity === false ) {

					cssText += "filter: alpha(opacity="+(value*100)+"); ";
				} else {

					cssText += ref.realCssName+": "+(value+ref.unit)+"; ";
				}
			}
		}

		this.element.node.style.cssText = cssText;
	}
};

PB.MorphEffects = {

	linear: function ( t ) {

		return t;
	},

	ease: function ( t ) {

		return t;
	},

	easeIn: function ( t ) {

		return t*t;
	},

	easeOut: function ( t ) {

		return -1*t*(t-2);
	},

	easeInOut: function ( t ) {

		return t;
	},

	bounce: function ( t ) {

		if (t < (1/2.75)) {

		      return (7.5625*t*t);
		  } else if (t < (2/2.75)) {

		      return (7.5625*(t-=(1.5/2.75))*t + .75);
		  } else if (t < (2.5/2.75)) {

		      return (7.5625*(t-=(2.25/2.75))*t + .9375);
		  } else {
		      return (7.5625*(t-=(2.625/2.75))*t + .984375);
		  }
	},

	backIn: function ( t ) {

		return (t)*t*((1.70158+1)*t - 1.70158);
	},

	backOut: function ( t ) {

		return ((t-=1)*t*((1.70158+1)*t + 1.70158) + 1);
	},

	elasticIn: function ( t ) {

		var p = 0.3,
			s = p/4,
			a = 1;

        if (t === 0) {

            return 0;
        }

        if ( t === 1 ) {

            return 1;
        }

        return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t-s)*(2*Math.PI)/p ));
	},

	elasticOut: function ( t ) {

		var p = 0.3,
			s = p/4,
			a = 1;

        if (t === 0) {

            return 0;
        }

        if ( t === 1 ) {

            return 1;
        }

        return a*Math.pow(2,-10*t) * Math.sin( (t-s)*(2*Math.PI)/p ) + 1;
	}
};

/**
 * Element visual
 */
Object.extend(PB.Element.prototype, {

	/**
	 *
	 *
	 * @param object to
	 * @param function after
	 * @param float duration
	 * @param string effect
	 */
	morph: function ( to/* after, duration, effect */ ) {

		var options = {

				to: to,
				duration: .4,	// Default duraton
				effect: "easeOut"
			},
			i = 1;

		if( typeof this.__storage.morph === "undefined" ) {

			this.__storage.morph = new PB.Morph();
		}

		for( ; i < arguments.length; i++ ) {

			switch( typeof arguments[i] ) {

				case 'function':
					options.after = arguments[i];
					break;

				case 'number':
					options.duration = arguments[i];
					break;

				case 'string':
					options.effect = arguments[i];
					break;
			}
		}

		this.__storage.morph
			.reset( this, options )
			.start();
	},

	/**
	 * Stop morphing effect
	 *
	 * @param boolean skipEnd -> Default true
	 * @return PB.Element
	 */
	stopMorph: function ( skipEnd ) {

		if( typeof skipEnd !== 'boolean' ) {

			skipEnd = true;
		}

		if( typeof this.__storage.morph === "undefined" ) {

			return this;
		}

		this.__storage.morph.stop( skipEnd );

		return this;
	}
});




/**
 * Http methods
 */
PB.Http = {

	/**
	 * Create uri string
	 *
	 * Should be optimized
	 *
	 * 	Example:
	 * 	{
	 * 		key1: "value1",
	 *		key2: ["val1", "val2", "val"]
	 *	}
	 *
	 *	Output:
	 *	key1=value1&key2[]=val1&key2[]=val2&key2[]=val3
	 */
	buildQuery: function ( mixed, prefix ) {

		var queryString = '';

		if( typeof mixed === 'string' ) {

			return mixed;
		} else if( Array.isArray(mixed) === true ) {

			mixed.forEach(function ( value, key ) {

				queryString += typeof value === 'object'
					? PB.Http.buildQuery( value, (prefix || key)+'[]' )
					: (prefix || key)+"="+encodeURIComponent(value)+'&';
			});
		} else if( Object.isObject(mixed) === true ) {

			Object.keys(mixed).forEach(function ( key ) {

				queryString += typeof mixed[key] === 'object'
					? PB.Http.buildQuery( mixed[key], key+'[]' )
					: (prefix || key)+"="+encodeURIComponent(mixed[key])+'&';
			});
		}

		return queryString.trimRight('&');
	}
};
/**
 * PB.Request class
 */
PB.Request = PB.Class({

	transports: [
		function (){ return new ActiveXObject("Microsoft.XMLHTTP"); },
		function (){ return new ActiveXObject("Msxml2.XMLHTTP"); },
		function (){ return new ActiveXObject("Msxml3.XMLHTTP"); },
		function (){ return new XMLHttpRequest(); }
	],

	readyStateEvents: 'unsent,opened,headers,loading,end'.split(','),

	constructor: function ( options ) {

		this.parent().constructor.apply(this);

		this.transport = false;

		Object.extend(this, PB.Request.defaults);
		Object.extend(this, options);
	},

	/**
	 * Set option
	 *
	 * data, headers, method, etc..
	 */
	set: function ( key, value ) {

		switch( key ) {

			case 'header':
			case 'headers':
				if( Object.isObject(value) === true ) {

					Object.extend(this.headers, value);
				}
				break;

			default:
				this[key] = value;
				break;
		}

		return this;
	},

	getTransport: function () {

		var i = this.transports.length;

		while ( i-- ) {

			try {

				return this.transport = this.transports[i]();
			} catch (e){}
		}
	},

	send: function () {

		var request = this.transport || this.getTransport(),
			url = this.url,
			method = this.method.toUpperCase(),
			params = this.data ? PB.Http.buildQuery( this.data ) : null;

		if( params !== null && method !== 'POST' && method !== 'PUT' ) {

			url += (url.indexOf("?") === -1 ? '?' : '&')+params;
			params = null;
		}

		request.onreadystatechange = this.onreadystatechange.bind(this);

		request.open( method, url, this.async );

		if( method === 'POST' || method === 'PUT' ) {

			request.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded; charset='+this.charset );
		}

		Object.forEach(this.headers, function( val, name ){

			request.setRequestHeader( name, val );
		});

		request.send( params );

		return this;
	},

	/**
	 * Abort the request
	 */
	abort: function () {

		this.transport.abort();

		this.emit('abort');

		return this;
	},

	/**
	 * Handle state changs
	 */
	onreadystatechange: function () {

		var request = this.transport;

		if( request.readyState === 4 ) {

			request.responseJSON = null;

			if( request.status >= 200 && request.status < 300 ) {

				if( request.getResponseHeader('Content-type').indexOf( 'application/json' ) >= 0 ) {

					request.responseJSON = JSON.parse( request.responseText );
				}

				this.emit( 'success', request, request.status );
			} else {

				this.emit( 'error', request, request.status );
			}
		}

		this.emit( this.readyStateEvents[request.readyState], request, request.readyState > 1 ? request.status : 0 );
	}
}).extend(PB.Pattern.Observer);

/**
 * Default Request values
 */
PB.Request.defaults = {

	url: null,
	data: null,
	method: 'GET',
	contentType: 'application/x-www-form-urlencoded',
	async: true,
	username: null,
	password: null,
	charset: 'UTF-8',
	headers: {
		'X-Requested-With': 'PBJS-'+PB.VERSION,
		'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
	},
	crossDomain: false
};
/**
 *	DEPRECATED since 0.4.4
 *
 * Ajax handler
 */
local.ajaxHandlers = [];

local.ajax = function ( options ) {

	this.options = Object.clone( local.ajax.options );

	this.available = false;

	this.setOptions( options );

	this.xmlHttp = this.createTransport();

	this.readyStateChangeWrapper = this.readyStateChange.bind(this);

	this.send();

	local.ajaxHandlers.push( this );
};

local.ajax.prototype = {

	setOptions: function ( options ) {

		if( options.headers ) {

			options.headers = Object.extend( Object.clone(this.options.headers), options.headers );
		}

		Object.extend( this.options, options );

		return this;
	},

	/**
	 * Create the correct transport
	 *
	 * Should be optimized
	 */
	createTransport: function () {

		try {

			return new XMLHttpRequest();
		} catch (e) {};

		try {

			return new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {}

		try {

			return new ActiveXObject("Msxml3.XMLHTTP");
		} catch (e) {}

		try {

			return new ActiveXObject("Microsoft.XMLHTTP");
		} catch (e) {}

		return null;
	},

	/**
	 * Send the request
	 */
	send: function () {

		var options = this.options,
			url = this.options.url,
			method = options.method.toUpperCase(),
			http = this.xmlHttp,
			params = options.data ? PB.Http.buildQuery( options.data ) : null;

		this.available = false;

		if( options.before ) {

			options.before( this );
		}

		if( params !== null && method !== 'POST' && method !== 'PUT' ) {

			url += (url.indexOf("?") === -1 ? '?' : '&')+params;
			params = null;
		}

		http.open( method, url, options.asynchronous );

		http.onreadystatechange = this.readyStateChangeWrapper;

		if( method === 'POST' || method === 'PUT' ) {

			http.setRequestHeader( "Content-type", "application/x-www-form-urlencoded; charset="+options.charset );
		}

		Object.forEach(options.headers, function( val, name ){

			http.setRequestHeader( name, val );
		});

		try {

			http.send( params );
		} catch (e) {}

		return this;
	},

	/**
	 * Abort the request
	 */
	abort: function () {

		this.xmlHttp.abort();
	},

	readyStateChange: function () {

		var http = this.xmlHttp,
			contentType;

		if( http.readyState === 4 ) {

			if( http.status >= 200 && http.status < 300 ) {

				contentType = http.getResponseHeader('Content-type');

				if( this.options.update ) {

					this.options.update.html( http.responseText, true );
				}

				if( this.options.success ) {

					http.responseJSON = null;

					if( contentType.indexOf( 'application/json' ) !== -1 ) {

						http.responseJSON = JSON.parse( http.responseText );
					}

					this.options.success( http, http.status );
				}
			} else {

				if( this.options.error ) {

					this.options.error( http, http.status );
				}
			}

			if( this.options.done ) {

				this.options.done();
			}

			http.responseJSON = null;

			http.onreadystatechange = null;

			this.available = true;
		}
	}
};

/**
 * Defaults
 */
local.ajax.options = {

	url: null,
	method: 'GET',
	contentType: 'application/x-www-form-urlencoded',
	asynchronous: true,
	charset: 'UTF-8',
	username: null,
	password: null,
	headers: {

		'X-Requested-With': 'PBJS-'+PB.VERSION,
		'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
	}
};

PB.ajax = function ( options ) {

	var handlers = local.ajaxHandlers,
		i = handlers.length;


	while ( i-- ) {

		if( handlers[i].available === true ) {

			return handlers[i].setOptions(options)
				.send();
		}
	}

	return new local.ajax( options );
};

/**
 * Set global ajax options
 */
PB.ajax.options = function ( options ) {

	local.ajax.options = Object.extend(local.ajax.options, options);
};



(function(){

	var RGBValues = /RGB\((\d+),\s?(\d+),\s?(\d+)\)?/i,
		RGBAValues = /RGBA\((\d+),\s?(\d+),\s?(\d+),\s?(\d+.\d+)\)?/i;

	PB.Color = function ( color ) {

		this.color = {

			R: 0,
			G: 0,
			B: 0,
			A: 1
		};

		if( !color ) {

			return;
		}

		this.setColorProperties( color );
	};

	PB.Color.prototype = {

		/**
		 * Retrieve color values from mixed
		 */
		setColorProperties: function ( color ) {

			var match;

			if( Object.isObject(color) ) {

				this.set( color.R, color.G, color.B, color.A || 0 );
			} else if ( color.charAt(0) === "#" ) {

				if( color.length === 4 ) {

					color = "#"+color.charAt(1).pad(2)+color.charAt(2).pad(2)+color.charAt(3).pad(2);
				}

				match = {

					R: parseInt( color.substr(1, 2), 16 ),
					G: parseInt( color.substr(3, 2), 16 ),
					B: parseInt( color.substr(5, 2), 16 )
				};

				this.set( match.R, match.G, match.B );

			} else if ( Array.isArray( match = color.match(RGBValues) ) === true ) {

				this.set( parseInt(match[1]), parseInt(match[2]), parseInt(match[3]) );
			} else if ( Array.isArray( match = color.match(RGBAValues) ) === true ) {

				this.set( parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), parseInt(match[4]) );
			}
		},

		/**
		 *
		 */
		set: function ( R, G, B, A ) {

			this.color.R = Math.round(R);
			this.color.G = Math.round(G);
			this.color.B = Math.round(B);
			this.color.A = A || 1;
		},

		/**
		 * Returns RGBA color values as
		 *	{
		 *		R: int,
		 *		G: int,
		 *		B: int,
		 *		A: float
		 *	}
		 */
		get: function () {

			return this.color;
		},

		/**
		 * Substract color values, A arg is optional
		 */
		sub: function ( R, G, B, A ) {

			this.add( -R, -G, -B, -A || 0 );
		},

		/**
		 * Add color values, A arg is optional
		 */
		add: function ( R, G, B, A ) {

			this.color.R += R;
			this.color.R = this.color.R < 0 ? 0 : this.color.R > 255 ? 255 : this.color.R;
			this.color.G += G;
			this.color.G = this.color.G < 0 ? 0 : this.color.G > 255 ? 255 : this.color.G;
			this.color.B += B;
			this.color.B = this.color.B < 0 ? 0 : this.color.B > 255 ? 255 : this.color.B;
			this.color.A += A || 0;
			this.color.A = this.color.A < 0 ? 0 : this.color.A > 1 ? 1 : this.color.A;
		},

		/**
		 * Returns the difference between two color objects as
		 *	{
		 *		R: int,
		 *		G: int,
		 *		B: int,
		 *		A: float
		 *	}
		 *
		 * Arg must be an instance of Color
		 */
		diff: function ( Color ) {

			var color = this.get(),
				diffColor;

			if( (Color instanceof PB.Color) === false ) {

				return null;
			}

			diffColor = Color.get();

			return {

				R: color.R - diffColor.R,
				G: color.G - diffColor.G,
				B: color.B - diffColor.B,
				A: color.A - diffColor.A
			};
		},

		/**
		 * Return CSS RGB string
		 *	RGB(0, 0, 0)
		 */
		toRGB: function () {

			return "RGB("+this.color.R+", "+this.color.G+", "+this.color.B+")";
		},

		/**
		 * Return CSS RGBA string
		 *	RGB(0, 0, 0, 0)
		 */
		toRGBA: function () {

			return "RGBA("+this.color.R+", "+this.color.G+", "+this.color.B+", "+this.color.A+")";
		},

		/**
		 * Return CSS hex string
		 *	#000000
		 */
		toHex: function () {

			var R = this.color.R.toString(16),
				G = this.color.G.toString(16),
				B = this.color.B.toString(16);

			return '#'
				+(R.length == 1 ? '0'+R : R)
				+(G.length == 1 ? '0'+G : G)
				+(B.length == 1 ? '0'+B : B);
		},

		/**
		 * Clone the current Color object
		 */
		clone: function () {

			return new PB.Color( this.get() );
		}
	};
})();

/**
 *
 */
PB.ready = (function (){

	var ready = false,
		queue = [],
		eventMethod = doc.addEventListener ? 'addEventListener' : 'attachEvent',
		eventMethodRemove = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
		eventTypePrefix = doc.addEventListener ? '' : 'on';

	/**
	 *
	 */
	function loaded ( e ) {

		var fn;

		if( ready === true ) {

			return;
		}

		if( /*e && e.type === 'readystatechange' &&*/ doc.readyState !== 'complete' ) {

			return;
		}

		ready = true;

		doc[eventMethodRemove](eventTypePrefix+'DOMContentLoaded', loaded, false);
		doc[eventMethodRemove](eventTypePrefix+'readystatechange', loaded, false);
		window[eventMethodRemove](eventTypePrefix+'load', loaded, false);

		while( fn = queue.shift() ) {

			fn();
		}

		queue = null;
	}

	if( doc.readyState === 'complete' ) {

		loaded();
	}
	else {

		doc[eventMethod](eventTypePrefix+'DOMContentLoaded', loaded, false);
		doc[eventMethod](eventTypePrefix+'readystatechange', loaded, false);
		window[eventMethod](eventTypePrefix+'load', loaded, false);

	}

	/**
	 * Register DOMReady event
	 */
	function register ( fn ) {

		if( ready === true ) {

			fn();
		}
		else if( queue.indexOf(fn) === -1 ) {

			queue.push( fn );
		}
	}

	return register;
})();

/**
PB.Template
-----------
+ constructor ( html-chunk )
+ compile						Parse html to function?
+ parse							Should return childs
+ toString

Templating language
-----------
{name}

{item.name}

{for item in items}
	{item.name}
{endfor}

{for item in items.0.subitems}			??
	{item.name}
{endfor}

{if item.name}
	{item.name}
{else}
	No name
{endif}
*/
var Tmpl = PB.Template = PB.Class({

	/**
	 * Cached for loop regexp
	 * Matches {for:1 var in set}{var}{endfor:1} {for var in set}{var.name}{endfor}
	 */
	cachedFor: /\{for:?(\d+)? (\w+) in ([\w\.]+)\}(.*?)\{endfor:?\1+?\}/g,

	/**
	 * Cached var regexp
	 * {name} get value form key or the var from context
	 * {object.name} get value from key
	 * {array.0} first in array
	 */
	cachedVar: /\{([\w\.]+)\|?([\w_-]+)?:?(.*?)\}/g,

	/**
	 * Helds parse output when render method is used
	 */
	rendered: null,

	/**
	 * Populate class with template string
	 *
	 * @param string template
	 * @param boolean do not remove line endings, tabs, extra spaces(2+), html comment
	 */
	constructor: function ( template, plainText ) {

		this.template = plainText
			? template
			: this.clean( template );
	},

	/**
	 * Strip template
	 *
	 * @param string template
	 */
	clean: function ( template ) {

		template = template.replace(/\n+/g, '')
			.replace(/\t+/g, '')
			.replace(/<!--.*?-->/g, '')
			.replace(/[\s]{2,}/g, ' ');

		return template;
	},

	/**
	 * Parse data in template
	 */
	parse: function ( context, template ) {

		var ouput;

		template = template || this.template;

		ouput = template.replace( this.cachedFor, this.parseLoops.bind(this, context) );

		ouput = ouput.replace( this.cachedVar, this.parseVars.bind(this, context) );

		return ouput;
	},

	/**
	 * Parse loops
	 */
	parseLoops: function ( context ) {

		var ouput = '',
			key = arguments[3],
			ns = this._getByNameSpace( context, arguments[4] );

		for( var i in ns ) {

			if( ns.hasOwnProperty(i) === true ) {

				ouput += this.parse( ns[i], arguments[5] );
			}
		}

		return ouput;
	},

	/**
	 * Parse statement
	 *
	 * todo: Add statements
	 *	- if else endif
	 */
	parseStatements: function () {


	},

	/**
	 * Replace var match with value
	 */
	parseVars: function ( context ) {

		var value = this._getByNameSpace( context, arguments[2] ),
			method = arguments[3],
			args = arguments[4];

		if( method && Tmpl.Methods[method] ) {

			if( args ) {

				args = args.split(',');
				args.unshift(value);
			} else {

				args = [value];
			}

			value = Tmpl.Methods[method].apply(null, args);
		}

		return value;
	},

	/**
	 * Returns new context a.k. namespace :)
	 */
	_getByNameSpace: function ( context, ns ) {

		ns = ns.split('.');

		ns.forEach(function ( ns ){

			context = context[ns] || context;
		});

		return context;
	},

	appendTo: function ( element, data ) {

		var fragment = doc.createDocumentFragment(),
			div = doc.createElement('div'),
			child,
			childs;

		element = PB(element).node;

		div.innerHTML = this.parse( data );

		while( child = div.firstChild ) {

			fragment.appendChild( child );
		}

		childs = local.toArray( fragment.childNodes ).map(PB);

		element.appendChild( fragment );

		fragment = div = null;

		return childs;
	}
});

Tmpl.Methods = {};

Tmpl.register = function ( name, method ){

	PB.Template.Methods[name] = method;
};



})( this );

/*!
 * Sizzle CSS Selector Engine
 *  Copyright 2011, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
	done = 0,
	toString = Object.prototype.toString,
	hasDuplicate = false,
	baseHasDuplicate = true,
	rBackslash = /\\/g,
	rNonWord = /\W/;

[0, 0].sort(function() {
	baseHasDuplicate = false;
	return 0;
});

var Sizzle = function( selector, context, results, seed ) {
	results = results || [];
	context = context || document;

	var origContext = context;

	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
		return [];
	}

	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var m, set, checkSet, extra, ret, cur, pop, i,
		prune = true,
		contextXML = Sizzle.isXML( context ),
		parts = [],
		soFar = selector;

	do {
		chunker.exec( "" );
		m = chunker.exec( soFar );

		if ( m ) {
			soFar = m[3];

			parts.push( m[1] );

			if ( m[2] ) {
				extra = m[3];
				break;
			}
		}
	} while ( m );

	if ( parts.length > 1 && origPOS.exec( selector ) ) {

		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );

		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] ) {
					selector += parts.shift();
				}

				set = posProcess( selector, set );
			}
		}

	} else {
		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {

			ret = Sizzle.find( parts.shift(), context, contextXML );
			context = ret.expr ?
				Sizzle.filter( ret.expr, ret.set )[0] :
				ret.set[0];
		}

		if ( context ) {
			ret = seed ?
				{ expr: parts.pop(), set: makeArray(seed) } :
				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );

			set = ret.expr ?
				Sizzle.filter( ret.expr, ret.set ) :
				ret.set;

			if ( parts.length > 0 ) {
				checkSet = makeArray( set );

			} else {
				prune = false;
			}

			while ( parts.length ) {
				cur = parts.pop();
				pop = cur;

				if ( !Expr.relative[ cur ] ) {
					cur = "";
				} else {
					pop = parts.pop();
				}

				if ( pop == null ) {
					pop = context;
				}

				Expr.relative[ cur ]( checkSet, pop, contextXML );
			}

		} else {
			checkSet = parts = [];
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		Sizzle.error( cur || selector );
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );

		} else if ( context && context.nodeType === 1 ) {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}

		} else {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}

	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, origContext, results, seed );
		Sizzle.uniqueSort( results );
	}

	return results;
};

Sizzle.uniqueSort = function( results ) {
	if ( sortOrder ) {
		hasDuplicate = baseHasDuplicate;
		results.sort( sortOrder );

		if ( hasDuplicate ) {
			for ( var i = 1; i < results.length; i++ ) {
				if ( results[i] === results[ i - 1 ] ) {
					results.splice( i--, 1 );
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function( expr, set ) {
	return Sizzle( expr, null, null, set );
};

Sizzle.matchesSelector = function( node, expr ) {
	return Sizzle( expr, null, null, [node] ).length > 0;
};

Sizzle.find = function( expr, context, isXML ) {
	var set;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var match,
			type = Expr.order[i];

		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
			var left = match[1];
			match.splice( 1, 1 );

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace( rBackslash, "" );
				set = Expr.find[ type ]( match, context, isXML );

				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = typeof context.getElementsByTagName !== "undefined" ?
			context.getElementsByTagName( "*" ) :
			[];
	}

	return { set: set, expr: expr };
};

Sizzle.filter = function( expr, set, inplace, not ) {
	var match, anyFound,
		old = expr,
		result = [],
		curLoop = set,
		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
				var found, item,
					filter = Expr.filter[ type ],
					left = match[1];

				anyFound = false;

				match.splice(1,1);

				if ( left.substr( left.length - 1 ) === "\\" ) {
					continue;
				}

				if ( curLoop === result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;

					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;

								} else {
									curLoop[i] = false;
								}

							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		if ( expr === old ) {
			if ( anyFound == null ) {
				Sizzle.error( expr );

			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

Sizzle.error = function( msg ) {
	throw "Syntax error, unrecognized expression: " + msg;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],

	match: {
		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
	},

	leftMatch: {},

	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},

	attrHandle: {
		href: function( elem ) {
			return elem.getAttribute( "href" );
		},
		type: function( elem ) {
			return elem.getAttribute( "type" );
		}
	},

	relative: {
		"+": function(checkSet, part){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !rNonWord.test( part ),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag ) {
				part = part.toLowerCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},

		">": function( checkSet, part ) {
			var elem,
				isPartStr = typeof part === "string",
				i = 0,
				l = checkSet.length;

			if ( isPartStr && !rNonWord.test( part ) ) {
				part = part.toLowerCase();

				for ( ; i < l; i++ ) {
					elem = checkSet[i];

					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
					}
				}

			} else {
				for ( ; i < l; i++ ) {
					elem = checkSet[i];

					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},

		"": function(checkSet, part, isXML){
			var nodeCheck,
				doneName = done++,
				checkFn = dirCheck;

			if ( typeof part === "string" && !rNonWord.test( part ) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
		},

		"~": function( checkSet, part, isXML ) {
			var nodeCheck,
				doneName = done++,
				checkFn = dirCheck;

			if ( typeof part === "string" && !rNonWord.test( part ) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
		}
	},

	find: {
		ID: function( match, context, isXML ) {
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m && m.parentNode ? [m] : [];
			}
		},

		NAME: function( match, context ) {
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [],
					results = context.getElementsByName( match[1] );

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},

		TAG: function( match, context ) {
			if ( typeof context.getElementsByTagName !== "undefined" ) {
				return context.getElementsByTagName( match[1] );
			}
		}
	},
	preFilter: {
		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
			match = " " + match[1].replace( rBackslash, "" ) + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
						if ( !inplace ) {
							result.push( elem );
						}

					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},

		ID: function( match ) {
			return match[1].replace( rBackslash, "" );
		},

		TAG: function( match, curLoop ) {
			return match[1].replace( rBackslash, "" ).toLowerCase();
		},

		CHILD: function( match ) {
			if ( match[1] === "nth" ) {
				if ( !match[2] ) {
					Sizzle.error( match[0] );
				}

				match[2] = match[2].replace(/^\+|\s*/g, '');

				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}
			else if ( match[2] ) {
				Sizzle.error( match[0] );
			}

			match[0] = done++;

			return match;
		},

		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
			var name = match[1] = match[1].replace( rBackslash, "" );

			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},

		PSEUDO: function( match, curLoop, inplace, result, not ) {
			if ( match[1] === "not" ) {
				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);

				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);

					if ( !inplace ) {
						result.push.apply( result, ret );
					}

					return false;
				}

			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}

			return match;
		},

		POS: function( match ) {
			match.unshift( true );

			return match;
		}
	},

	filters: {
		enabled: function( elem ) {
			return elem.disabled === false && elem.type !== "hidden";
		},

		disabled: function( elem ) {
			return elem.disabled === true;
		},

		checked: function( elem ) {
			return elem.checked === true;
		},

		selected: function( elem ) {
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		parent: function( elem ) {
			return !!elem.firstChild;
		},

		empty: function( elem ) {
			return !elem.firstChild;
		},

		has: function( elem, i, match ) {
			return !!Sizzle( match[3], elem ).length;
		},

		header: function( elem ) {
			return (/h\d/i).test( elem.nodeName );
		},

		text: function( elem ) {
			var attr = elem.getAttribute( "type" ), type = elem.type;
			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
		},

		radio: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
		},

		checkbox: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
		},

		file: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
		},

		password: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
		},

		submit: function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return (name === "input" || name === "button") && "submit" === elem.type;
		},

		image: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
		},

		reset: function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return (name === "input" || name === "button") && "reset" === elem.type;
		},

		button: function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && "button" === elem.type || name === "button";
		},

		input: function( elem ) {
			return (/input|select|textarea|button/i).test( elem.nodeName );
		},

		focus: function( elem ) {
			return elem === elem.ownerDocument.activeElement;
		}
	},
	setFilters: {
		first: function( elem, i ) {
			return i === 0;
		},

		last: function( elem, i, match, array ) {
			return i === array.length - 1;
		},

		even: function( elem, i ) {
			return i % 2 === 0;
		},

		odd: function( elem, i ) {
			return i % 2 === 1;
		},

		lt: function( elem, i, match ) {
			return i < match[3] - 0;
		},

		gt: function( elem, i, match ) {
			return i > match[3] - 0;
		},

		nth: function( elem, i, match ) {
			return match[3] - 0 === i;
		},

		eq: function( elem, i, match ) {
			return match[3] - 0 === i;
		}
	},
	filter: {
		PSEUDO: function( elem, match, i, array ) {
			var name = match[1],
				filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );

			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;

			} else if ( name === "not" ) {
				var not = match[3];

				for ( var j = 0, l = not.length; j < l; j++ ) {
					if ( not[j] === elem ) {
						return false;
					}
				}

				return true;

			} else {
				Sizzle.error( name );
			}
		},

		CHILD: function( elem, match ) {
			var type = match[1],
				node = elem;

			switch ( type ) {
				case "only":
				case "first":
					while ( (node = node.previousSibling) )	 {
						if ( node.nodeType === 1 ) {
							return false;
						}
					}

					if ( type === "first" ) {
						return true;
					}

					node = elem;

				case "last":
					while ( (node = node.nextSibling) )	 {
						if ( node.nodeType === 1 ) {
							return false;
						}
					}

					return true;

				case "nth":
					var first = match[2],
						last = match[3];

					if ( first === 1 && last === 0 ) {
						return true;
					}

					var doneName = match[0],
						parent = elem.parentNode;

					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;

						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						}

						parent.sizcache = doneName;
					}

					var diff = elem.nodeIndex - last;

					if ( first === 0 ) {
						return diff === 0;

					} else {
						return ( diff % first === 0 && diff / first >= 0 );
					}
			}
		},

		ID: function( elem, match ) {
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},

		TAG: function( elem, match ) {
			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
		},

		CLASS: function( elem, match ) {
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},

		ATTR: function( elem, match ) {
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value !== check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},

		POS: function( elem, match, i, array ) {
			var name = match[2],
				filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS,
	fescape = function(all, num){
		return "\\" + (num - 0 + 1);
	};

for ( var type in Expr.match ) {
	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
}

var makeArray = function( array, results ) {
	array = Array.prototype.slice.call( array, 0 );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}

	return array;
};

try {
	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;

} catch( e ) {
	makeArray = function( array, results ) {
		var i = 0,
			ret = results || [];

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );

		} else {
			if ( typeof array.length === "number" ) {
				for ( var l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}

			} else {
				for ( ; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder, siblingCheck;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
			return a.compareDocumentPosition ? -1 : 1;
		}

		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
	};

} else {
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
			return 0;

		} else if ( a.sourceIndex && b.sourceIndex ) {
			return a.sourceIndex - b.sourceIndex;
		}

		var al, bl,
			ap = [],
			bp = [],
			aup = a.parentNode,
			bup = b.parentNode,
			cur = aup;

		if ( aup === bup ) {
			return siblingCheck( a, b );

		} else if ( !aup ) {
			return -1;

		} else if ( !bup ) {
			return 1;
		}

		while ( cur ) {
			ap.unshift( cur );
			cur = cur.parentNode;
		}

		cur = bup;

		while ( cur ) {
			bp.unshift( cur );
			cur = cur.parentNode;
		}

		al = ap.length;
		bl = bp.length;

		for ( var i = 0; i < al && i < bl; i++ ) {
			if ( ap[i] !== bp[i] ) {
				return siblingCheck( ap[i], bp[i] );
			}
		}

		return i === al ?
			siblingCheck( a, bp[i], -1 ) :
			siblingCheck( ap[i], b, 1 );
	};

	siblingCheck = function( a, b, ret ) {
		if ( a === b ) {
			return ret;
		}

		var cur = a.nextSibling;

		while ( cur ) {
			if ( cur === b ) {
				return -1;
			}

			cur = cur.nextSibling;
		}

		return 1;
	};
}

Sizzle.getText = function( elems ) {
	var ret = "", elem;

	for ( var i = 0; elems[i]; i++ ) {
		elem = elems[i];

		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
			ret += elem.nodeValue;

		} else if ( elem.nodeType !== 8 ) {
			ret += Sizzle.getText( elem.childNodes );
		}
	}

	return ret;
};

(function(){
	var form = document.createElement("div"),
		id = "script" + (new Date()).getTime(),
		root = document.documentElement;

	form.innerHTML = "<a name='" + id + "'/>";

	root.insertBefore( form, root.firstChild );

	if ( document.getElementById( id ) ) {
		Expr.find.ID = function( match, context, isXML ) {
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);

				return m ?
					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
						[m] :
						undefined :
					[];
			}
		};

		Expr.filter.ID = function( elem, match ) {
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");

			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );

	root = form = null;
})();

(function(){

	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function( match, context ) {
			var results = context.getElementsByTagName( match[1] );

			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	div.innerHTML = "<a href='#'></a>";

	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {

		Expr.attrHandle.href = function( elem ) {
			return elem.getAttribute( "href", 2 );
		};
	}

	div = null;
})();

if ( document.querySelectorAll ) {
	(function(){
		var oldSizzle = Sizzle,
			div = document.createElement("div"),
			id = "__sizzle__";

		div.innerHTML = "<p class='TEST'></p>";

		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
			return;
		}

		Sizzle = function( query, context, extra, seed ) {
			context = context || document;

			if ( !seed && !Sizzle.isXML(context) ) {
				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );

				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
					if ( match[1] ) {
						return makeArray( context.getElementsByTagName( query ), extra );

					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
						return makeArray( context.getElementsByClassName( match[2] ), extra );
					}
				}

				if ( context.nodeType === 9 ) {
					if ( query === "body" && context.body ) {
						return makeArray( [ context.body ], extra );

					} else if ( match && match[3] ) {
						var elem = context.getElementById( match[3] );

						if ( elem && elem.parentNode ) {
							if ( elem.id === match[3] ) {
								return makeArray( [ elem ], extra );
							}

						} else {
							return makeArray( [], extra );
						}
					}

					try {
						return makeArray( context.querySelectorAll(query), extra );
					} catch(qsaError) {}

				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
					var oldContext = context,
						old = context.getAttribute( "id" ),
						nid = old || id,
						hasParent = context.parentNode,
						relativeHierarchySelector = /^\s*[+~]/.test( query );

					if ( !old ) {
						context.setAttribute( "id", nid );
					} else {
						nid = nid.replace( /'/g, "\\$&" );
					}
					if ( relativeHierarchySelector && hasParent ) {
						context = context.parentNode;
					}

					try {
						if ( !relativeHierarchySelector || hasParent ) {
							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
						}

					} catch(pseudoError) {
					} finally {
						if ( !old ) {
							oldContext.removeAttribute( "id" );
						}
					}
				}
			}

			return oldSizzle(query, context, extra, seed);
		};

		for ( var prop in oldSizzle ) {
			Sizzle[ prop ] = oldSizzle[ prop ];
		}

		div = null;
	})();
}

(function(){
	var html = document.documentElement,
		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;

	if ( matches ) {
		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
			pseudoWorks = false;

		try {
			matches.call( document.documentElement, "[test!='']:sizzle" );

		} catch( pseudoError ) {
			pseudoWorks = true;
		}

		Sizzle.matchesSelector = function( node, expr ) {
			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");

			if ( !Sizzle.isXML( node ) ) {
				try {
					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
						var ret = matches.call( node, expr );

						if ( ret || !disconnectedMatch ||
								node.document && node.document.nodeType !== 11 ) {
							return ret;
						}
					}
				} catch(e) {}
			}

			return Sizzle(expr, null, null, [node]).length > 0;
		};
	}
})();

(function(){
	var div = document.createElement("div");

	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
		return;
	}

	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 ) {
		return;
	}

	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function( match, context, isXML ) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};

	div = null;
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];

		if ( elem ) {
			var match = false;

			elem = elem[dir];

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName.toLowerCase() === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];

		if ( elem ) {
			var match = false;

			elem = elem[dir];

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}

					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

if ( document.documentElement.contains ) {
	Sizzle.contains = function( a, b ) {
		return a !== b && (a.contains ? a.contains(b) : true);
	};

} else if ( document.documentElement.compareDocumentPosition ) {
	Sizzle.contains = function( a, b ) {
		return !!(a.compareDocumentPosition(b) & 16);
	};

} else {
	Sizzle.contains = function() {
		return false;
	};
}

Sizzle.isXML = function( elem ) {
	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;

	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

var posProcess = function( selector, context ) {
	var match,
		tmpSet = [],
		later = "",
		root = context.nodeType ? [context] : context;

	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};


window.Sizzle = Sizzle;

})();

