/**
 * pbjs JavaScipt Framework v0.3.2
 */

"use strict";

/**
 * Create global namespace
 */
var PB = {

	VERSION: "0.3.2"
};


/**
 * Detect browser
 */
PB.Browser = function ( global ) {

	var ua = navigator.userAgent.toLowerCase();

	return {

		IE: /msie/.test(ua),
		Opera: !!global.opera, // /opera/.test(ua),
		Chrome: /chrome/.test(ua),
		Safari: /safari/.test(ua),
		Firefox: /firefox/.test(ua)
	};
}( this );

/*
http://www.ics.uci.edu/pub/ietf/uri/#Related

results in the following subexpression matches:

   $1 = http:
   $2 = http
   $3 = //www.ics.uci.edu
   $4 = www.ics.uci.edu
   $5 = /pub/ietf/uri/
   $6 = <undefined>
   $7 = <undefined>
   $8 = #Related
   $9 = Related
*/

/**
 * Viewport
 */
(function(){

	PB.Viewport = {

		width: function () {

			return document.documentElement.clientWidth;
		},

		height: function () {

			return document.documentElement.offsetHeight;
		},

		scrollWidth: function () {

			return Math.max(

				document.documentElement.clientWidth,
				document.body.scrollWidth, document.documentElement.scrollWidth,
				document.body.offsetWidth, document.documentElement.offsetWidth
			);
		},

		scrollHeight: function () {

			return Math.max(

				document.documentElement.clientHeight,
				document.body.scrollHeight, document.documentElement.scrollHeight,
				document.body.offsetHeight, document.documentElement.offsetHeight
			);
		},

		scrollLeft: function () {

			return document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset || 0;
		},

		scrollTop: function () {

			return document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset || 0;
		}
	};
})();

/**
 * Language extensions
 */
(function(){

var objectProto = Object.prototype,
	arrayProto = Array.prototype,
	functionProto = Function.prototype,
	stringProto = String.prototype,
	dateProto = Date.prototype,
	slice = arrayProto.slice;
/**
 * Object methods
 */

/**
 *
 */
Object.extend = function ( dest, original ) {

	var prop;

	for( prop in original ) {

		if( original.hasOwnProperty( prop ) ) {

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

	return dest;
};

/**
 *
 */
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
	 */
	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
	 */
	getPrototypeOf: function ( object ) {

		var constructor = object.constructor,
			oldConstructor;

		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;	// IE?

	}
});

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

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

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

	/**
	 *
	 * Should be possible to do a deep clone
	 */
	clone: function ( object ) {

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

	/**
	 * Will transform any primite into an array, string excepted
	 *
	 * Could be removed!
	 */
	toArray: function ( object ) {

		var property,
			result;

		if( !object ) {

			return [];
		}

		if( (result = Array.prototype.slice.call( object, 0 )).length !== 0 ) {

			return result;
		}

		result = [];

		if( Object.isObject( object ) === true ) {

			for( property in object ) {

				if( object.hasOwnProperty(property) ) {

					result.push( object[property] );
				}
			}

			return result;
		}

		return result;
	},

	/**
	 * Implementation to forEach an object
	 */
	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
*/

/**
 * Ecma5
 */
if( Array.isArray === undefined ) {

	/**
	 * Implementation of checking if object like is an array
	 */
	Array.isArray = function ( object ) {

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

			return false;
		}

		return true;
	};
}

Object.extendIfNotExists(arrayProto,{

	/**
	 * forEach method
	 */
	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++;
		}
	},

	/**
	 *
	 */
	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;
	},

	/**
	 *
	 */
	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;
	},

	/**
	 *
	 */
	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;
	},

	/**
	 *
	 */
	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;
	},

	/**
	 *
	 */
	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 ) {

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

			i++;
		}

		return result;
	},

	/**
	 *
	 */
	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(arrayProto, {

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

		return this.slice(0);
	},

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

		this.length = 0;
		return this;
	},

	/**
	 * Remove given value from array
	 */
	remove: function ( value ) {

		var i = this.indexOf(value);

		if( i !== -1 ) {

			this.splice( i, 1 );
		}

		return this;
	}
});

/**
* Function methods
*/

/**
 * Ecma 5
 */
Object.extendIfNotExists(functionProto,{

	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(functionProto, {

	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 );
		};
	},

	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 int in miliseconds
	 * @return
	 */
	delay: function ( timeout ) {

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

		return window.setTimeout(function (){

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

/**
* String methods
*/


/**
 * Ecma5
 *
 * In the official ecma5 te trim/trimLeft/trimRight methods can't handle
 * an additional arg the char, to trim the string with. So trim methods
 * will be overwriten!
 */

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

	TRUNCATE_CENTER: 0,
	TRUNCATE_RIGHT: 1
});

Object.extend(stringProto,{

	/**
	 *
	 */
	trim: function ( chr ) {

		chr = chr || "\\s";

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

	/**
	 *
	 */
	trimLeft: function ( chr ) {

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

	/**
	 *
	 */
	trimRight: function ( chr ) {

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

	/**
	 *
	 */
	ucfirst: function () {

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

	/**
	 * Pad a string, usefull for dates etc
	 */
	pad: function ( length, string, addRight ) {

		var diff = length - this.length;

		if( diff <= 0 ) {

			return String(this);
		}

		string = ( typeof string === "undefined" ? this : String(string) ).repeat(diff);

		return addRight === true ? string+this : this+string;
	},

	/**
	 * pad and truncate methods should be plitted out, pad_left/pad_right, truncate_left/truncate_center/truncate_right
	 */

	truncate: function ( max_length, truncation, direction ) {

		var stringLength = this.length;

		direction = direction || String.TRUNCATE_RIGHT;
		max_length = max_length >>> 0 || 30;
		truncation = truncation || "...";

		if( stringLength <= max_length ) {

			return String(this);
		}

		if( direction === String.TRUNCATE_CENTER ) {

			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;
	},

	word_truncate: function ( max_length, truncation, direction ) {

		var length = this.length;

		direction = direction || String.TRUNCATE_RIGHT;
		max_length = max_length >>> 0 || 30;
		truncation = truncation || "...";

		if( length <= max_length ) {

			return this;
		}

		if( direction === String.TRUNCATE_RIGHT ) {

			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 ) );
		}
	},

	/**
	 * Repeat string a given times
	 */
	repeat: function ( times ) {

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

/**
 * Extend primitive Date object with ecma5 funtionality and some PHP like methods
 */

(function(){

/**
 * Date
 */

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

	/**
	 * Return the current time in miliseconds
	 */
	now: function () {

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

Object.extendIfNotExists(dateProto, {

	/**
	 * Ecma5 toISOString specification
	 */
	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(dateProto, {

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

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

	/**
	 * Custom pad function for date like number formating
	 * For intern usage
	 */
	pad: function ( number ) {

		return number >>> 0 < 10 ? "0"+number : number;
	}
});

/**
 * PHP like format method
 *
 * See the PHP manual
 */
var dayNames = ["Sunday", "Monday", "Tuesday", "Wendsday", "Thursday", "Friday", "Saturday"],
	dayNamesShort = ["Sun", "Mon", "Tue", "Wen", "Thu", "Fri", "Sat"],
	monthNames = ["Januari", "Februari", "March", "April", "May", "April", "March", "June", "Juli", "October", "November", "December"],
	monthNamesShort = ["Jan", "Feb", "Mar", "Apr", "May", "Apr", "Mar", "Jun", "Jul", "Oct", "Nov", "Dec"];

Object.extend(dateProto, {

	formatRegExp: /\\?([a-z])/gi,

	/**
	 * PHP like format method
	 *
	 * Example:
	 * (new Date()).format("Y-m-d H:i:s");
	 */
	format: function ( string ) {

		return string.replace( this.formatRegExp, function ( match, defaultValue ){

			return typeof this["get_"+match] === "function" ? this["get_"+match]() : defaultValue;
		}.bind(this));
	},


	/**
	 * Returns the day in 01-31 format
	 */
	get_d: function () {

		return this.pad( this.getDate() );
	},

	/**
	 * Returns the short name of the day
	 *
	 * Mon trough Sun
	 */
	get_D: function () {

		return dayNamesShort[this.getDay()];
	},

	/**
	 * Returns the day in 1-31 format
	 */
	get_j: function () {

		return this.getDate();
	},

	/**
	 * Returns the name of the day
	 *
	 * Sunday trough Saturday
	 */
	get_l: function () {

		return dayNames[this.getDay()];
	},

	/**
	 * Returns the day of the week
	 *
	 * 1 (for Monday) trough 7 (for Sunday)
	 */
	get_N: function () {

		return this.getDay() || 7;
	},

	/**
	 * Returns the day of the week
	 *
	 * 0 (for Monday) trough 6 (for Sunday)
	 */
	get_w: function () {

		return this.getDay();
	},

	/**
	 * Returns the day of the year, starting from 0
	 */
	get_z: function () {

		var firstdayThisYear = new Date( this.getFullYear(), 0, 1, 0, 0, 0 );

		return Math.floor( (this - firstdayThisYear) / 86400000 );
	},


	/**
	 * Returns the week number, starting on Monday
	 */
	get_W: function () {

		var dateClone = new Date( this.getTime() );

		dateClone.setTime( dateClone.getTime() + (dateClone.getDay() !== 1 ? 1 - dateClone.format("N") : 0) * 86400000 );

		return this.pad( Math.ceil( Math.ceil( dateClone.format("z") / 7 ) ) );
	},


	/**
	 * Returns the month name, Januari trough December
	 */
	get_F: function () {

		return monthNames[this.getMonth()];
	},

	/**
	 * Returns the month in 01 trough 12 format
	 */
	get_m: function () {

		return this.pad( this.getMonth()+1 );
	},

	/**
	 * Returns the short month name, Jan trough Dec
	 */
	get_M: function () {

		return monthNamesShort[this.getMonth()];
	},

	/**
	 * Returns the month in 1 trough 12 format
	 */
	get_n: function () {

		return this.getMonth()+1;
	},

	/**
	 * Returns the number of days in given month, 28 trough 31
	 */
	get_t: function () {

		var clone = new Date( this.getTime() );

		clone.setDate(32)

		return 32 - clone.getDate();
	},


	/**
	 * Is leap year?
	 */
	get_L: function () {

		return (new Date(this.getFullYear(), 1, 29)).getDate() === 29;
	},

	/**
	 * Returns the year in YYYY format (4 digits)
	 */
	get_Y: function () {

		return this.getFullYear();
	},

	/**
	 * Returns the year in YY format (2 digits)
	 */
	get_y: function () {

		return String(this.getFullYear()).substr(2);
	},


	/**
	 * Returns the year in YY format (2 digits)
	 */
	get_a: function () {

		return Math.floor(this.getHours() / 12) ? "pm" : "am";
	},

	/**
	 * Returns the year in YY format (2 digits)
	 */
	get_A: function () {

		return Math.floor(this.getHours() / 12) ? "PM" : "AM";
	},

	/**
	 * Swatch Internet time	000 trough 999
	 */
	get_B: function () {

		return Math.floor( ((this.getHours() * 3600) + (this.getMinutes() * 60) + this.getSeconds()) / 86.4 );
	},

	/**
	 * Returns hours in 1 trough 12 format
	 */
	get_g: function () {

		return this.getHours() / 12 && this.getHours() >= 12 ? this.getHours() - 12 : this.getHours();
	},

	/**
	 * Returns hours in 0 trough 23 format
	 */
	get_G: function () {

		return this.getHours();
	},

	/**
	 * Returns hours in 01 trough 12 format
	 */
	get_h: function () {

		return this.pad( this.get_g() );
	},

	/**
	 * Returns hours in 00 trough 23 format
	 */
	get_H: function () {

		return this.pad( this.getHours() );
	},

	/**
	 * Returns minutes in 00 trough 59 format
	 */
	get_i: function () {

		return this.pad( this.getMinutes() );
	},

	/**
	 * Returns seconds in 00 trough 59 format
	 */
	get_s: function () {

		return this.pad( this.getSeconds() );
	},

	/**
	 * Returns the current microseconds
	 */
	get_u: function () {

		return this.getTime();
	},


	/**
	 * Returns the difference in greenwich time(GMT) in hours,	Example: +0200
	 */
	get_O: function () {

		return "+"+this.pad(-this.getTimezoneOffset() / 60)+"00";
	},

	/**
	 * Returns the difference in greenwich time(GMT) with colon in hours,	Example: +02:00
	 */
	get_P: function () {

		return "+"+this.pad(-this.getTimezoneOffset() / 60)+":00";
	},

	/**
	 * Returns the timezone offset in seconds, -43200 trough 50400
	 */
	get_Z: function () {

		return -this.getTimezoneOffset()*60;
	},


	/**
	 * Returns the ISO 8601 date, Example: 2010-06-12 T 15:19:21+00:00
	 */
	get_c: function () {

		return this.format("Y-m-dTH:i:sP");
	},

	/**
	 * Returns the RFC 2822 formatted date, Example: Thu, 21 Dec 2010 15:19:00 +0200
	 */
	get_r: function () {

		return this.format("D, d M Y H:i:s O");
	},

	/**
	 * Returns the seconds since the Unix Epoch
	 */
	get_U: function () {

		return Math.floor(this.getTime() / 1000);
	}
});
/*

var regex2 = [
		/([+-]\d+\s(years?|months?|weeks?|days?|hours?|minutes?|seconds?))/gi,
		/((sunday?|monday?|tuesday?|wednesday?|thursday?|friday?|saturday?)\s(next|last)\s(years?|months?|weeks?))/gi,
		/(next|last)\s(years?|months?|weeks?|days?|hours?|minutes?|seconds?)\s?(sunday?|monday?|tuesday?|wednesday?|thursday?|friday?|saturday?)?/gi,
		/((sunday?|monday?|tuesday?|wednesday?|thursday?|friday?|saturday?)\s(last|next)\s(years?|months?|weeks?))/gi,
		/((sunday?|monday?|tuesday?|wednesday?|thursday?|friday?|saturday?))/gi,
		/((last|first)\s(days?|weeks?|months?)\s(.*?)\s?(weeks?|months?|years?))/gi
	];

var modify2 = function ( string ) {

	var matches,
		parts = [];

	console.log( string );

	for( var i = 0, length = regex2.length >>> 0; i < length; i++ ) {

		if( Array.isArray( matches = string.match( regex2[i] ) ) === true ) {

			matches.forEach(function( m ){

				string = string.replace( m, "" );
			});

			console.log(matches);

		}
	};

	return;
	console.log( string );

	matches.forEach(function( value ) {

		var day = -1;

		parts = value.toLowerCase().split(" ");

		if( (day = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].indexOf(parts[0])) !== -1 ) {

			console.log("day: "+day+", "+value);
		} else {//if ( () !== -1 ) {

			console.log(value);
		}

		return "Blep";
	}, this);
};

modify2("monday next month +1 day -1 second friday next year");
modify2("next week monday");
modify2("next year");
modify2("first day of month"
*/

})();
})();

/**
 * Element
 */
(function( global ){

var UID = 0,
	ElementCache = {},
	slice = Array.prototype.slice;

Object.extend(PB, {

	cache: ElementCache,

	/**
	 * Create unique id, troughout the app
	 */
	id: function () {

		return ++UID;
	},

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

		if( element instanceof PB.Element ) {

			return element;
		}

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

			element = document.getElementById( element );
		}

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

			return null;
		}

		if( element.__PBID !== undefined && element.__PBID in ElementCache ) {

			return ElementCache[element.__PBID];
		}

		element.__PBID = PB.id();

		return ElementCache[element.__PBID] = new PB.Element( element );
	},

	/**
	 * Element closure
	 */
	Element: function ( node ) {

		this.node = node;

		this._store = {};
	},

	_removeElement: function ( node ) {

		var element = PB.get(node);

		if( element._store.morph ) {

			element._store.morph.stop();
		}

		try {

			delete ElementCache[node.__PBID];
		} catch ( e ) {

			alert( e.message );
		}
	}
});

/**
 * Set reference to $
 */
global.$ = PB.get;

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

/*	hasAttr: function ( attribute ) {

		return !!this.node.getAttribute( attribute );
	},

	getAttribute: function ( attribute ) {

		return this.node.getAttribute( attribute );
	},

	setAttribute: function ( attribute, value ) {

		this.node.setAttribute( attribute, value );
		return this;
	},

	removeAttribute: function ( attribute ) {

		this.node.removeAttribute( attribute );
		return this;
	},*/

	/**
	 * 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;
	},

	/**
	 *
	 */
	nodeName: function ( nodeName ) {

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

			return this.node.nodeName.toLowerCase() === nodeName;
		}

		return this.node.nodeName.toLowerCase();
	}
});

/**
 * 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;
	},

	/**
	 * 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();
	},

	serialize: function () {


	}
});

/**
 * 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._store;

		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._store,
			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";
	},

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

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

		while( element ) {

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

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

				break;
			}

			element = element.offsetParent;
		}

		return {

			left: x,
			top: y
		};
	},

	/**
	 * Position on page, not viewport
	 */
	position: function () {

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

		while( element ) {

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

			element = element.offsetParent;
		}

		return {

			left: x,
			top: y
		};
	},

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

		var position = this.position();

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

		return position;
	},

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

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

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

		if( this.node.nodeType === 9 ) {

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

		return this.node.offsetWidth;
	},

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

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

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

		if( this.node.nodeType === 9 ) {

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

		return this.node.offsetHeight;
	},

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

		return this.node.scrollHeight;
	},

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

		return this.node.scrollWidth;
	},

	/**
	 * Set or get scrolltop
	 */
	scrollTop: function ( x ) {

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

			return this.node.scrollTop;
		}

		this.node.scrollTop = x;

		return this;
	},

	/**
	 * Set or get scrollleft
	 */
	scrollLeft: function ( y ) {

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

			return this.node.scrollLeft;
		}

		this.node.scrollLeft = y;
	},

	/**
	 * Scroll to element, if no scroll container is given, then window is used
	 */
	scrollTo: function ( container ) {

		var position;

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

			position = this.position();

			window.scrollTo( position.left, position.top );
		} else {


			container = PB.get(container);
			position = container.offset();

			PB.get(container).scrollLeft( position.left );
			PB.get(container).scrollTop( position.top );
		}

		return this;
	},

	/**
	 * 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;
	}
});

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

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

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

			return null;
		}

		this.node.appendChild( element.node );

		return this;
	},

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

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

			return null;
		}

		target.append( this );

		return this;
	},

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

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

			return null;
		}

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

		return this;
	},

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

		var nextNode;

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

			return null;
		}

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

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

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

		return this;
	},

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

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

			return null;
		}

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

			target.append( this );
		} else {

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

		return this;
	},

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

		var node = this.node;

		PB._removeElement( node );

		node.parentNode.removeChild( node );

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

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

	/**
	 * Select elements parent node
	 */
	parent: function ( element ) {

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

	/**
	 * Retrieve element childs nodes
	 */
	childs: function ( element ) {

		var childNodes = this.node.childNodes,
			childArray;

		try {

			childArray = slice.call(childNodes, 0);
		} catch ( ieError ) {

			var length = childNodes.length;
			childArray = Array( length );

			while( length-- ) {

				childArray[length] = childNodes[length];
			}
		}

		return childArray.filter(PB.get).map(PB.get);
	},

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

		var sibling = node || this.node;

		while( sibling = sibling.nextSibling ) {

			if( sibling.nodeType === 1 ) {

				return PB.get( sibling );
			}
		}

		return null;
	},

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

		var sibling = node || this.node;

		while( sibling = sibling.previousSibling ) {

			if( sibling.nodeType === 1 ) {

				return PB.get( sibling );
			}
		}

		return null;
	},

	/**
	 * Goto first child of element
	 */
	first: function () {

		var child = this.node.firstChild;

		if( child.nodeType !== 1 ) {

			child = this.next( child );
		}

		return PB.get( child );
	},

	/**
	 * Goto last child of element
	 */
	last: function () {

		var child = this.node.lastChild;

		if( child.nodeType !== 1 ) {

			child = this.prev( child );
		}

		return PB.get( child );
	},

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

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

		element = PB.get(element);

		do {

			if( node === element ) {

				return true;
			}

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

				break;
			}

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

		return false;
	},

	/**
	 * The getElementsByTagName selector
	 */
	getElementsByTagName: function ( tagName ) {

		var nodes = this.node.getElementsByTagName( tagName ),
			nodesArray;

		try {

			nodesArray = slice.call(nodes, 0).map(PB.get);
		} catch ( ieError ) {

			var length = nodes.length,
				nodesArray = Array( length );

			while ( length-- ) {

				nodesArray[length] = PB.get( nodes[length] );
			}
		}

		return nodesArray;
	},

	/**
	 * Will return an array of matches nodes
	 */
	getElementsByClassName: function () {

		return !!document.getElementsByClassName
			? function ( className ) {

				return slice.call(this.node.getElementsByClassName(className), 0).map(PB.get);
			}
			: function ( className ) {

				var nodes = this.node.getElementsByTagName("*"),
					length = nodes.length,
					result = [],
					i = 0,
					node;

				while( i < length ) {

					if( (node = PB.get(nodes[i++])) && node.hasClass(className) === true ) {

						result.push( node );
					}
				}

				return result;
			};
	}(),

	/**
	 * Simple CSS selector
	 *
	 * Created to select elements be tag name, class name or both tag.className
	 */
	find: function () {

		return !!document.querySelectorAll
		? function ( query ) {

			var nodes = this.node.querySelectorAll( query ),
				result;

			try {

				result = slice.call(nodes, 0);
			} catch ( ieError ) {

				var length = nodes.length;
				result = Array( length );

				while( length-- ) {

					result[length] = nodes[length];
				}
			}

			return result.map(PB.get);
		}
		: function ( query ) {

			var nodes = [];

			if( query.indexOf(",") !== -1) {

				query.split(",").forEach(function( part ) {

					nodes = nodes.concat( this.find( part.trim() ) );
				}, this);

				return nodes;
			}

			if( query.indexOf(".") !== -1 ) {

				query = query.split(".");

				nodes = this.getElementsByClassName( query[1] );

				if( query[0] !== "" ) {

					nodes = nodes.filter(function ( node ){

						return node.node.nodeName.toLowerCase() === query[0];
					});
				}
			} else {

				nodes = this.getElementsByTagName( query );
			}

			return nodes;
		}
	}(),

	/**
	 * Tries finding the given parent with a simple selector
	 *
	 * IDEE: Name findParent to hasParent ??
	 */
	findParent: function ( parentSelector, depth ) {

		var node = this,
			depth = depth || 50,			// TODO, rename depth
			body = PB.get(document.body),
			className = '',
			nodeName = '';

		if( parentSelector.indexOf('.') !== -1 ) {

			parentSelector = parentSelector.split('.');
			className = parentSelector[1];
			parentSelector = parentSelector[0];
		}

		nodeName = parentSelector;

		do {

			if( className === '' && nodeName !== '' && node.nodeName(nodeName) === true ) {

				return node;
			} else if ( nodeName === '' && className !== '' && node.hasClass(className) === true ) {

				return node;
			} else if ( node.nodeName(nodeName) && node.hasClass(className) ) {

				return node;
			}

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

				break;
			}

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

		return null;
	}
});

/*
cache.click = {

	uid: fn,
	uid: fn,
	uid: fn
};

cache.mouseover = {

	uid: fn,
	etc..
}

proberly create a singleton to handle al events
*/

/**
 * TODO
 * - Organize event cache
 * - Naming cleanup, vars
 * - onunload all events removal
 * - event.is("leftClick") ?? Or something
 * - Fix mousebutton codes
 */


PB.Event = {

	cache: {},
	supports_mouse_enter_leave: "onmouseenter" in document.documentElement && "onmouseleave" in document.documentElement,
	translation: {

		mouseenter: "mouseover",
		mouseleave: "mouseout"
	},
	Methods: {

		stopPropagation: function () {

			this.cancelBubble = true;
		},

		preventDefault: function () {

			this.returnValue = false;
		},

		stop: function () {

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

var EVENT_CACHE = [];

Object.extend(PB.Element.prototype, {

	/**
	 * Creates the event handler wrapper
	 */
	createEventHandler: function ( handler ) {

		var node = this.node,
			fn;

		if( typeof handler.UID !== "undefined" ) {

			return EVENT_CACHE[handler.UID];
		}

		fn = function ( event ) {

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

			/**
			 * Add event methods
			 */
			Object.extendIfNotExists(event, PB.Event.Methods);

			/**
			 * Add a target node
			 */
			if( typeof event.target === "undefined" ) {

				event.target = event.srcElement || node;
			}

			/**
			 * Add a currentTarget node, only useable on mouseover/mouseenter/mouseout/mouseleave
			 */
			if( typeof event.currentTarget === "undefined" ) {

				switch ( event.type ) {

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

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

			/**
			 * Add which propertie
			 */
			if( typeof event.which === "undefined" ) {

				event.which = event.keyCode || event.charCode;
			}

			/**
			 * Add mouse position, relative to page
			 */
			if( typeof event.pageX === "undefined" && typeof event.clientX !== "undefined" ) {
				
				event.pageX = event.clientX + (docEl.scrollLeft || body.scrollLeft) - (docEl.clientLeft || 0);
				event.pageY = event.clientY + (docEl.scrollTop || body.scrollTop) - (docEl.clientTop || 0);
			}

			/**
			 * Trigger the 'original' event handler with normalized event object
			 */
			handler.call( node, event );
		};

		fn.PB_EVENT_UID = handler.PB_EVENT_UID = PB.id();

		EVENT_CACHE.push( fn );

		return fn;
	},

	on: function ( eventName, handler ) {

		var node = this.node,
			_handler = this.createEventHandler( handler ),
			eventName = !PB.Event.supports_mouse_enter_leave ? PB.Event.translation[eventName] || eventName : eventName,
			docEl = document.documentElement,
			cache = PB.Event.cache;

		if( node.addEventListener ) {

			node.addEventListener( eventName, _handler, false );
		} else {

			eventName = "on"+eventName;

			if( eventName in docEl ) {
				node.attachEvent( eventName, _handler );
			} else {

				if( typeof PB.Event.cache[eventName] === "undefined" ) {

					PB.Event.cache[eventName] = [];
				}

				PB.Event.cache[eventName].push( _handler );
			}


		}

		return this;
	},

	stop: function ( eventName, handler ) {

		var node = this.node,
			uid,
			i;

		if( typeof handler.PB_EVENT_UID !== "undefined" ) {

			uid = handler.PB_EVENT_UID;
			i = EVENT_CACHE.length;

			while( i-- ) {

				if( EVENT_CACHE[i] && EVENT_CACHE[i].PB_EVENT_UID === uid ) {

					handler = EVENT_CACHE[i];
					break;
				}
			}
		}

		if( node.addEventListener ) {

			node.removeEventListener( eventName, handler, false );
		} else {

			node.detachEvent( "on"+eventName, handler );
		}

		delete EVENT_CACHE[i];

		return this;
	},

	trigger: function ( eventName ) {

		var event;

		if( document.createEvent ) {

			event = document.createEvent( "HTMLEvents" );
			event.initEvent( eventName, true, true );

			event.eventName = eventName;

			this.node.dispatchEvent( event );
		} else if ( document.createEventObject ) {

			eventName = "on"+eventName;

			event = document.createEventObject();
			event.eventType = true ? 'ondataavailable' : 'onlosecapture';

			event.eventName = eventName;

			try {

				this.node.fireEvent( eventName, event );
			} catch ( ignoredError ) {

				if( typeof PB.Event.cache[eventName] !== "undefined" ) {

					PB.Event.cache[eventName].forEach(function( fn ){

						fn({});
					});
				}
			}
		}

		return this;
	},

	/**
	 * Removes all events
	 */
	tearDown: function () {


	}
});

/*
interface MouseEvent : UIEvent {
  readonly attribute long             screenX;
  readonly attribute long             screenY;
  readonly attribute long             clientX;
  readonly attribute long             clientY;
  readonly attribute boolean          ctrlKey;
  readonly attribute boolean          shiftKey;
  readonly attribute boolean          altKey;
  readonly attribute boolean          metaKey;
  readonly attribute unsigned short   button;
  readonly attribute EventTarget      relatedTarget;
  void               initMouseEvent(in DOMString typeArg,
                                    in boolean canBubbleArg,
                                    in boolean cancelableArg,
                                    in views::AbstractView viewArg,
                                    in long detailArg,
                                    in long screenXArg,
                                    in long screenYArg,
                                    in long clientXArg,
                                    in long clientYArg,
                                    in boolean ctrlKeyArg,
                                    in boolean altKeyArg,
                                    in boolean shiftKeyArg,
                                    in boolean metaKeyArg,
                                    in unsigned short buttonArg,
                                    in EventTarget relatedTargetArg);
};

altKey of type boolean, readonly
    Used to indicate whether the 'alt' key was depressed during the firing of the event. On some platforms this key may map to an alternative key name.
button of type unsigned short, readonly
    During mouse events caused by the depression or release of a mouse button, button is used to indicate which mouse button changed state. The values for button range from zero to indicate the left button of the mouse, one to indicate the middle button if present, and two to indicate the right button. For mice configured for left handed use in which the button actions are reversed the values are instead read from right to left.
clientX of type long, readonly
    The horizontal coordinate at which the event occurred relative to the DOM implementation's client area.
clientY of type long, readonly
    The vertical coordinate at which the event occurred relative to the DOM implementation's client area.
ctrlKey of type boolean, readonly
    Used to indicate whether the 'ctrl' key was depressed during the firing of the event.
metaKey of type boolean, readonly
    Used to indicate whether the 'meta' key was depressed during the firing of the event. On some platforms this key may map to an alternative key name.
relatedTarget of type EventTarget, readonly
    Used to identify a secondary EventTarget related to a UI event. Currently this attribute is used with the mouseover event to indicate the EventTarget which the pointing device exited and with the mouseout event to indicate the EventTarget which the pointing device entered.
screenX of type long, readonly
    The horizontal coordinate at which the event occurred relative to the origin of the screen coordinate system.
screenY of type long, readonly
    The vertical coordinate at which the event occurred relative to the origin of the screen coordinate system.
shiftKey of type boolean, readonly
    Used to indicate whether the 'shift' key was depressed during the firing of the event.
*/
PB.Style = function () {

	var getComputedStyle = document.defaultView && document.defaultView.getComputedStyle,
		cssFloat = function () {	// Nasty check to see if cssFloat or styleFloat should be used

			var div = document.createElement("div"),
				cssFloat;

			div.innerHTML = '<div style="float: left"></div>';
			cssFloat = div.childNodes[0].style.cssFloat === "left";

			div = null;

			return cssFloat;
		}();


	/**
	 * 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"
		},

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

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

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

			return this.unitRegex.test( value ) ? parseInt( value ) : 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 = 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( PB.Browser.IE /* && !PB.Browser.IE9 */ ) {

					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 = document.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 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);
			}
		},

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

			return PB.Browser.IE /* && !PB.Browser.IE9 */
				? function ( value ) {
					
					this.node.style.filter = "alpha(opacity="+(value*100)+")";
				}
				: function ( value ) {

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

	return new CSSStyle();
}();


})( this );
/**
 * Ajax
 */

(function(){

	/**
	 * Default headers
	 */
	var PBRequestHeaders = {

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

	/**
	 * Handles xmlHttp request
	 */
	PB.Request = function ( options ) {

		this.setOptions( options );
		this.xmlHttp = this.createTransport();

		this.onStateChangeWrapper = this.onStateChange.bind(this);
	};

	PB.Request.prototype = {

		/**
		 * 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;
		},

		/**
		 * Set all options to default and overide with given options
		 */
		setOptions: function ( options ) {

			this.free = false;

			/**
			 * update should be an element
			 */
			if( options.update ) {

				options.update = PB.get(options.update);

				if( options.update === null ) {

					throw new TypeError();
				}
			}

			this.options = Object.extend({

				method: "GET",
				asynchronous: true,
				charset: "UTF-8"
			}, options);

			return this;
		},

		/**
		 * Used to reuse an request
		 */
		setOption: function ( key, value ) {

			this.options[key] = value;

			return this;
		},

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

			var options = this.options,
				url = options.url,
				method = options.method.toUpperCase(),
				http = this.xmlHttp,
				headers = {},
				params = options.data ? this.buildQueryString( options.data ) : null;

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

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

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

			if( typeof http.addEventListener === "function xxx" ) {

				http.addEventListener( "readystatechange", this.onStateChangeWrapper, false );
			} else {

				http.onreadystatechange = this.onStateChangeWrapper;
			}

			Object.extend( headers, PBRequestHeaders );

			if( method === "POST" ) {

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

			if( options.headers ) {

				Object.extend( headers, options.headers );
			}

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

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

			http.send( params );
		},

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

			this.xmlHttp.abort();
		},

		/**
		 * Handle the onstatechange event
		 */
		onStateChange: 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 = eval( "(" + http.responseText + ")" );
						}

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

					if( this.options.error ) {

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

				http.responseJSON = null;

			/*	if( typeof http.addEventListener === "function" ) {

					http.removeEventListener( "readystatechange", this.onStateChangeWrapper, false );
				} else {

					http.onreadystatechange = null;
				}*/

				this.free = true;
			}
		},

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

			var queryString = "";

			prefix = typeof prefix === "string" ? prefix : false;

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

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

				mixed.forEach(function( value, key ){

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

						queryString += this.buildQueryString( value, key+"[]" );
					} else {

						queryString += (prefix || key)+"="+value+"&";
					}
				}, this);
			} else if( Object.isObject(mixed) === true ) {

				Object.forEach(mixed, function( value, key ){

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

						queryString += this.buildQueryString( value, key+"[]" );
					} else {

						queryString += (prefix || key)+"="+value+"&";
					}
				}, this);
			}

			return queryString.trimRight("&");
		}
	};

	/**
	 * Default handler to create ajax request
	 *
	 * Singleton
	 */
	var AjaxHandler = function () {

		this.cache = [];
	};

	AjaxHandler.prototype = {

		/**
		 * Do the request with given options
		 *
		 * Will return the used transport
		 */
		request: function ( options ) {

			var transport = this.getFreeTransport();

			if( transport === null ) {

				transport = new PB.Request( options );
				this.cache.push( transport );
			} else {

				transport.setOptions( options );
			}

			transport.send();

			return transport;
		},

		/**
		 * Private method
		 *
		 * Get the first free transport avaible
		 */
		getFreeTransport: function () {

			var cache = this.cache,
				length = cache.length,
				i = 0;

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

				if( cache[i].free ) {

					return cache[i];
				}
			}

			return null;
		}
	};

	/**
	 * To global space
	 */
	PB.Ajax = new AjaxHandler();

})();

/**
 * Extra's
 */
/**
 *
 */
PB.DOMReady = function () {

	this.onReadyStateChangeWrapper = this.onReadyStateChange.bind(this);
	this.onDOMContentLoadedWrapper = this.onDOMContentLoaded.bind(this);

	this.fireWrapper = this.fire.bind(this);

	this.addListeners();
};

PB.DOMReady.prototype = {

	ready: false,

	queue: [],

	addListeners: function () {

		if( document.addEventListener ) {

			document.addEventListener( 'DOMContentLoaded', this.onDOMContentLoadedWrapper, false );
		} else {

			PB.get(document).on( 'readystatechange', this.onReadyStateChangeWrapper );
		}

		PB.get(window).on("load", this.fireWrapper)
	},

	/**
	 * Add function to fire
	 */
	add: function ( fn ) {

		if( this.ready === true ) {

			fn();
		}

		this.queue.push( fn );
	},

	onDOMContentLoaded: function () {

		document.removeEventListener( 'DOMContentLoaded', this.fireWrapper, false );

		this.fire();
	},

	onReadyStateChange: function () {

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

			PB.get(document).stop( 'readystatechange', this.onReadyStateChangeWrapper );

			this.fire();
		}
	},

	/**
	 * Executes all listened functions
	 */
	fire: function () {

		this.queue.forEach(function ( fn ){

			fn();
		});

		delete this.queue;
	}
};

PB.DOMReady = new PB.DOMReady();
PB.DOMReady = PB.DOMReady.add.bind(PB.DOMReady);
(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 () {

			return "#"
				+this.color.R.toString(16).pad(2, 0, true)
				+this.color.G.toString(16).pad(2, 0, true)
				+this.color.B.toString(16).pad(2, 0, true);
		},

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

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

/**
 * Animation classes
 */
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'];

		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 = document.body,
			percent = 0;

		while( element ) {

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

				break;
			}

			element = element.parent();
		}

		if( element.node === body ) {

			element = PB.get( document );
		}

		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( property === "opacity" && PB.Browser.IE && typeof PropertyCache.filter === "undefined" ) {

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

		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;

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

		PB.Animation_Observer.detach( this );

		if( skipEnd === true ) {

			this.render( 1 );
		}

		this.isRunning = false;

		if( after ) {

			after( element );
		}

		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.Browser.IE ) {

					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._store.morph === "undefined" ) {

			this._store.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._store.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._store.morph === "undefined" ) {

			return this;
		}

		this._store.morph.stop( skipEnd );

		return this;
	}
});


