Source: chill.js

/**
 * chillJS - JavaScript 2D library
 * 
 * @version v0.0.1-alpha
 * @link http://bokodi.github.io/chillJS/
 * @license MIT
 * 
 * 
 * Date: Sat Jan 14 2017
 */


(function(window) {
'use strict';

window.requestAnimationFrame =
	window.requestAnimationFrame ||
	window.webkitRequestAnimationFrame ||
	window.mozRequestAnimationFrame ||
	window.msRequestAnimationFrame ||
	window.oRequestAnimationFrame;

window.cancelAnimationFrame =
	window.cancelAnimationFrame ||
	window.webkitCancelAnimationFrame ||
	window.mozCancelAnimationFrame ||
	window.msCancelAnimationFrame ||
	window.oCancelAnimationFrame;

var numRegX = /[+-]?(?:\d*\.|)\d+/;
var lineBreakRegX = /\n/;

/**
 * Returns the first match of the regex in the given string or null
 *
 * @function findFirstRegX
 * @param {RegExp} regX
 * @param {String} str
 * @returns {?String}
**/
function findFirstRegX(regX, str) {
	var val = str.match(regX);
	
	return val === null ? null : val[0];
}

/**
 * Determines whether a value is Array or not
 *
 * @function isArray
 * @param {*} testValue
 * @returns {Boolean}
**/
function isArray(testValue) {
	return Object.prototype.toString.call(testValue) === '[object Array]';
}

/**
 * Determines whether a value is Boolean or not
 *
 * @function isBoolean
 * @param {*} testValue
 * @returns {Boolean}
**/
function isBoolean(testValue) {
	return typeof testValue === 'boolean';
}

/**
 * Determines whether a value is Function or not
 *
 * @function isFunction
 * @param {*} testValue
 * @returns {Boolean}
**/
function isFunction(testValue) {
	return typeof testValue === 'function';
}

/**
 * Determines whether a value is Object or not
 *
 * @function isObject
 * @param {*} testValue
 * @returns {Boolean}
**/
function isObject(testValue) {
	return Object.prototype.toString.call(testValue) === '[object Object]';
}

/**
 * Determines whether a value is String or not
 *
 * @function isString
 * @param {*} testValue
 * @returns {Boolean}
**/
function isString(testValue) {
	return typeof testValue === 'string';
}

/**
 * Determines whether a value is Number or not
 *
 * @function isNumber
 * @param {*} testValue
 * @returns {Boolean}
**/
function isNumber(testValue) {
	return typeof testValue === 'number';
}

/**
 * Determines whether a value is NaN or not
 *
 * @function isNaN
 * @param {*} testValue
 * @returns {Boolean}
**/
function isNaN(testValue) {
	return testValue !== testValue;
}

/**
 * Determines whether a value is undefined or not
 *
 * @function isUndefined
 * @param {*} testValue
 * @returns {Boolean}
**/
function isUndefined(testValue) {
	return testValue === void 0;
}

/**
 * Determines whether a value is null or not
 *
 * @function isNull
 * @param {*} testValue
 * @returns {Boolean}
**/
function isNull(testValue) {
	return testValue === null;
}

/**
 * Determines whether a value is null or undefined
 *
 * @function isNullOrUndefined
 * @param {*} testValue
 * @returns {Boolean}
**/
function isNullOrUndefined(testValue) {
	return testValue === void 0 || testValue === null;
}

/**
 * Determines whether an object is instance of another object
 *
 * @function is
 * @param {Object} o1
 * @param {Object} o2
 * @returns {Boolean}
**/
function is(o1, o2) {
	return o1 instanceof o2;
}

/**
 * Creates a new empty object, and returns it
 *
 * @function stdClass
 * @returns {Object}
**/
function stdClass() {
	return Object.create(null);
}

/**
 * Creates a new object inherits from the given constructor
 *
 * @function inherit
 * @param {Function} constructor
 * @returns {?Object}
**/
function inherit(constructor) {
	var obj = null;
	
	if (typeof constructor === 'function') {
		obj = Object.create(constructor.prototype);
		constructor.call(obj);
	}
	
	return obj;
}

/**
 * Generates a UUID
 *
 * @function generateUUID
 * @see http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript/2117523#2117523
 * @returns {String}
**/
var generateUUID = (function() {
	var regex = /[xy]/g;
	
	function replace(c) {
		var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
		
		return v.toString(16);
	}
	
	return function generateUUID() {
		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(regex, replace);
	};
}());

/**
 * Converts arguments to Array
 *
 * @function getArgs
 * @param {Arguments} args
 * @param {int} [begin = 0]
 * @param {int} [end = args.length - 1]
 * @returns {Array}
**/
function getArgs(args, begin, end) {
	return Array.prototype.slice.call(args, begin, end);
}

/**
 * Creates a new function
 *
 * @function parseMethod
 * @param {String} method
 * @param {Object} [thisArg]
 * @param {Array} [args]
 * @returns {Function}
 * @todo Replace "@" character globally (if not string) to "this."
**/
var parseMethod = (function() {
	var mustache = /^{{(.*)}}$/;
	
	function isArray(testValue) {
		return Object.prototype.toString.call(testValue) === '[object Array]';
	}
	
	return function parseMethod(method, thisArg, args) {
		var match = String(method).match(mustache), fn;
		
		if (isArray(match)) {
			fn = new Function(isArray(args) ? args.join(',') : '', match[1]);
			
			return typeof thisArg === 'object' ? fn.bind(thisArg) : fn;
		}
		
		return null;
	};
}());

/**
 * Parse a parcentage to number
 *
 * @function parsePercent
 * @param {String} val
 * @param {Number} relative
 * @returns {Number}
**/
var parsePercent = (function() {
	var regX = /[+-]?(?:\d*\.|)\d+/;
	
	return function parsePercent(val, relative) {
		var num;
		
		if (typeof relative !== 'number') return 0;
		
		val = String(val);
		
		num = val.match(regX);
		
		if (num === null) return 0;
		
		return Number(num[0]) * relative / 100;
	};
}());

/**
 * Determines if a given value is percent
 *
 * @function isPercent
 * @param {String} val
 * @returns {Boolean}
**/
function isPercent(val) {
	return typeof val === 'string' && val.slice(-1) === '%';
}

/**
 * Parses a string and returns an integer
 *
 * @function getInt
 * @param {String} str
 * @returns {int}
**/
function getInt(str) {
	return window.parseInt(str, 10);
}

/**
 * Parses a string and returns a float
 *
 * @function getFloat
 * @param {String} str
 * @returns {Number}
**/
function getFloat(str) {
	return window.parseFloat(str);
}

/**
 * Generates a number between two values
 *
 * @function rand
 * @param {int} min
 * @param {int} max
 * @returns {int}
**/
function rand(min, max) {
	if (max === undefined) {
		max = min;
		min = 0;
	}
	
	min |= 0;
	max |= 0;
	
	return (Math.random() * (max - min + 1)) + min | 0;
}

/**
 * Checks if the given number is between the given min and max values
 *
 * @function between
 * @param {Number} n
 * @param {Number} min
 * @param {Number} max
 * @returns {Boolean}
**/
function between(n, min, max) {
	return n >= min && n <= max;
}

/**
 * Returns a number in a range
 *
 * @function range
 * @param {Number} min
 * @param {Number} max
 * @param {Number} n
 * @returns {Number}
**/
function range(min, max, n) {
	return Math.max(min, Math.min(max, n));
}

/**
 * Converts degree to radian
 *
 * @function deg2rad
 * @param {Number} deg
 * @returns {Number}
**/
function deg2rad(deg) {
	return deg * Math.PI / 180;
}

/**
 * Repeats a string n times
 *
 * @function repeatChar
 * @param {String} s
 * @param {int} n
 * @returns {String}
**/
function repeatString(s, n) {
	var str = '';
	
	while (n-- > 0) str += s;
	
	return str;
}

/**
 * Outputs an object to the Web Console
 *
 * @function debug
 * @param {*} obj
 * @param {String} message
 * @returns {undefined}
**/
function debug(obj, message) {
	if (message !== undefined) {
		console.info(message);
	}
	
	if (typeof obj === 'object') {
		console.dir(obj);
	} else {
		console.log(obj);
	}
	
	return undefined;
}

/**
 * Outputs a message to the Web Console
 *
 * @function log
 * @param {...String}
 * @returns {undefined}
**/
function log() {
	console.log.apply(console, Array.prototype.slice.call(arguments));
	
	return undefined;
}

/**
 * Outputs a warning to the Web Console
 *
 * @function warning
 * @param {String} message
 * @returns {undefined}
**/
function warning(message) {
	console.warn(message);
	
	return undefined;
}

/**
 * Outputs an error to the Web Console
 *
 * @function error
 * @param {String} message
 * @returns {undefined}
**/
function error(message) {
	console.error(message);
	
	return undefined;
}

/**
 * Throws an error
 *
 * @function die
 * @param {String} message
 * @throws {Error}
**/
function die(message) {
	throw new Error(message);
}

/**
 * Repeats the given function n times
 *
 * @function repeat
 * @param {int} n
 * @param {Function} callback
 * @param {Object} [thisArg]
 * @returns {*} thisArg
**/
function repeat(n, callback, thisArg) {
	var i = -1;
	
	while (++i < n) callback.call(thisArg, i);
	
	return thisArg;
}

/**
 * Modifies an object by complex rules
 *
 * @function use
 * @param {Object} target
 * @param {Object} data
 * @returns {Object} target
**/
var use = (function() {
	var types = ['string', 'number', 'boolean', 'undefined', 'function'],
		toString = Object.prototype.toString,
		hasOwn = Object.prototype.hasOwnProperty;
	
	function inArray(arr, testValue) {
		var i = 0, il = arr.length;
		
		for (; i < il; i++) {
			if (arr[i] === testValue) return true;
		}
		
		return false;
	}
	
	function smartType(testValue) {
		var type = typeof testValue;
		
		if (inArray(types, type)) return type;
		if (testValue === null) return 'null';
		if (toString.call(testValue) === '[object Array]') return 'array';
		
		return 'object';
	}
	
	return function use(target, data) {
		var key, targetVal, dataVal, dataValType, i, il;
		
		for (key in data) {
			if (hasOwn.call(data, key)) {
				targetVal = target[key];
				dataVal = data[key];
				dataValType = smartType(dataVal);
				
				switch (smartType(targetVal)) {
					case 'function':
						if (dataValType === 'array') {
							il = dataVal.length;
							
							if (il === 0) {
								targetVal.call(target);
							} else {
								for (i = 0; i < il; i++) {
									if (smartType(dataVal[i]) === 'array') {
										targetVal.apply(target, dataVal[i]);
									} else {
										targetVal.call(target, dataVal[i]);
									}
								}
							}
						} else {
							targetVal.call(target, dataVal);
						}
						
						break;
					
					case 'string':
					case 'boolean':
					case 'number':
					case 'undefined':
					case 'null':
						target[key] = dataVal;
						
						break;
					
					case 'array':
						if (dataValType === 'array') {
							targetVal.push.apply(targetVal, dataVal);
						} else {
							targetVal.push(dataVal);
						}
						
						break;
					
					case 'object':
						if (dataValType === 'object') {
							use(targetVal, dataVal);
						} else {
							target[key] = dataVal;
						}
						
						break;
				}
			}
		}
		
		return target;
	};
}());

/**
 * Determines whether a string begins with the characters of another string
 *
 * @function startsWith
 * @param {String} str
 * @param {String} searchString
 * @returns {Boolean}
**/
function startsWith(str, searchString) {
	return str.indexOf(searchString) === 0;
}

/**
 * Determines whether a string ends with the characters of another string
 *
 * @function endsWith
 * @param {String} str
 * @param {String} searchString
 * @returns {Boolean}
**/
function endsWith(str, searchString) {
	var lastIndex = str.lastIndexOf(searchString);
	
	return lastIndex !== -1 && lastIndex === str.length - searchString.length;
}

/**
 * Capitalizes a given string
 *
 * @function capitalize
 * @param {String} text
 * @returns {String}
**/
var capitalize = (function() {
	var regex = /(?:^|\s)\S/g;
	
	function toUpper(c) {
		return c.toUpperCase();
	}
	
	return function capitalize(text) {
		return text.replace(regex, toUpper);
	};
}());

/**
 * Removes item(s) from an array
 *
 * @function remove
 * @param {Array} arr
 * @param {*} removeItem
 * @param {int} [limit = 1]
 * @returns {int} removedCount
**/
function remove(arr, removeItem, limit) {
	var removedCount = 0,
		index;
	
	if (limit === undefined) limit = 1;
	
	while ((index = arr.indexOf(removeItem)) !== -1) {
		arr.splice(index, 1);
		
		if (++removedCount >= limit) break;
	}
	
	return removedCount;
}

/**
 * Empties an array
 *
 * @function empty
 * @param {Array} arr
 * @returns {Array} arr
**/
function empty(arr) {
	while (arr.length > 0) arr.pop();
	
	return arr;
}

/**
 * Checks if an item exists in an array
 *
 * @function inArray
 * @param {Array} arr
 * @param {*} searchItem
 * @returns {Boolean}
**/
function inArray(arr, searchItem) {
	return arr.indexOf(searchItem) !== -1;
}

/**
 * Adds an item to the array if the array doesn't contains it
 *
 * @function addUnique
 * @param {Array} arr
 * @param {*} item
 * @returns {Array}
**/
function addUnique(arr, item) {
	if (!inArray(arr, item)) arr.push(item);
	
	return arr;
}

/**
 * Counts how many times an item exists in an array
 *
 * @function count
 * @param {Array} arr
 * @param {*} searchItem
 * @returns {int}
**/
function count(arr, searchItem) {
	var n = 0;
	
	forEach(arr, function(item) {
		if (item === searchItem) ++n;
	});
	
	return n;
}

/**
 * Returns array
 *
 * @function toArray
 * @param {*} item
 * @returns {Array}
**/
function toArray(item) {
	return Object.prototype.toString.call(item) === '[object Array]' ? item : [item];
}

/**
 * Returns the first item of an array
 *
 * @function first
 * @param {Array} arr
 * @returns {*}
**/
function first(arr) {
	return arr[0];
}

/**
 * Returns the last item of an array
 *
 * @function last
 * @param {Array} arr
 * @returns {*}
**/
function last(arr) {
	return arr.slice(-1)[0];
}

/**
 * Executes a provided callback function once per array element
 *
 * @function forEach
 * @param {Array} arr
 * @param {Function} callback
 * @param {Object} [thisArg]
 * @returns {Array}
**/
function forEach(arr, callback, thisArg) {
	var i = 0,
		il = arr.length;
	
	for (; i < il; i++) {
		if (callback.call(thisArg, arr[i], i, arr) === true) break;
	}
	
	return arr;
}

/**
 * Executes a provided callback function once per array element, and sets it's this value to the currentValue
 *
 * @function callEach
 * @param {Array} arr
 * @param {Function} callback
 * @returns {Array}
**/
function callEach(arr, callback) {
	var i = 0,
		il = arr.length;
	
	for (; i < il; i++) {
		if (callback.call(arr[i], i, arr) === true) break;
	}
	
	return arr;
}

/**
 * Executes a provided callback function once per array element with the given arguments, sets it's this value to the currentValue
 *
 * @function applyEach
 * @param {Array} arr
 * @param {Function} callback
 * @param {...*}
 * @returns {Array}
**/
function applyEach(arr, callback) {
	var args = Array.prototype.slice.call(arguments, 2),
		i = 0,
		il = arr.length;
	
	for (; i < il; i++) {
		callback.apply(arr[i], args);
	}
	
	return arr;
}

/**
 * Executes a provided callback function once per array element, returns a value
 *
 * @function execute
 * @param {Array} arr
 * @param {Function} callback
 * @param {Object} [thisArg]
 * @returns {Array}
**/
function execute(arr, callback, thisArg) {
	var i = 0,
		il = arr.length,
		returnValue;
	
	for (; i < il; i++) {
		returnValue = callback.call(thisArg, arr[i], i, arr);
		
		if (returnValue !== undefined) return returnValue;
	}
	
	return null;
}

/**
 * Fills an array
 *
 * @function fillArray
 * @param {Array} arr
 * @param {*} fillValue
 * @returns {Array}
**/
function fillArray(arr, fillValue) {
	if (typeof fillValue === 'function') {
		forEach(arr, function(item, index, arr) {
			arr[index] = fillValue(item, index, arr);
		});
	} else {
		forEach(arr, function(item, index) {
			arr[index] = fillValue;
		});
	}
	
	return arr;
}

/**
 * Creates a new array
 *
 * @function createArray
 * @param {int} length
 * @param {*} fillValue
 * @returns {Array}
**/
function createArray(length, fillValue) {
	return fillArray(new Array(length), fillValue);
}

/**
 * Copies the values of all enumerable own properties from one or more source objects to a target object
 *
 * @function assign
 * @param {Object} target
 * @param {...Object}
 * @returns {Object}
**/
function assign(target) {
	var sources = Array.prototype.slice.call(arguments, 1),
		i = 0, il = sources.length,
		source;
	
	for (; i < il; i++) {
		source = sources[i];
		
		forIn(source, function(key, value) {
			target[key] = value;
		});
	}
	
	return target;
}

/**
 * Returns a boolean indicating whether an object has the specified property
 *
 * @function has
 * @param {Object} obj
 * @param {String} key
 * @returns {Boolean}
**/
function has(obj, key) {
	return Object.prototype.hasOwnProperty.call(obj, key);
}

/**
 * Copies values from one or more source objects to a target object
 *
 * @function addDefault
 * @param {Object} target
 * @param {...Object}
 * @returns {Object}
**/
function addDefault(target) {
	var sources = Array.prototype.slice.call(arguments, 1),
		i = 0, il = sources.length,
		source;
	
	for (; i < il; i++) {
		source = sources[i];
		
		forIn(source, function(key, value) {
			if (!has(target, key)) target[key] = value;
		});
	}
	
	return target;
}

/**
 * Clones an object
 *
 * @function clone
 * @param {Object} obj
 * @returns {Object}
**/
function clone(obj) {
	return assign({}, obj);
}

/**
 * Returns an array of a given object's own enumerable properties
 *
 * @function keys
 * @param {Object} obj
 * @returns {Array}
**/
function keys(obj) {
	var returnValue = [];
	
	forIn(obj, function(key) {
		returnValue.push(key);
	});
	
	return returnValue;
}

/**
 * Returns an array of a given object's own enumerable values
 *
 * @function values
 * @param {Object} obj
 * @returns {Array}
**/
function values(obj) {
	var returnValue = [];
	
	forIn(obj, function(key, value) {
		returnValue.push(value);
	});
	
	return returnValue;
}

/**
 * Iterates over the enumerable properties of an object
 *
 * @function forIn
 * @param {Object} obj
 * @param {Function} callback
 * @param {Object} [thisArg]
 * @returns {Object}
**/
function forIn(obj, callback, thisArg) {
	var key;
	
	for (key in obj) {
		if (has(obj, key)) {
			if (callback.call(thisArg, key, obj[key], obj) === true) break;
		}
	}
	
	return obj;
}

/**
 * Returns an object with the given properties with the source's values
 *
 * @function getProps
 * @param {Object} obj
 * @param {Array} properties
 * @returns {Object}
**/
function getProps(obj, properties) {
	var returnValue = {},
		i = 0, il = properties.length,
		key;
	
	for (; i < il; i++) {
		key = properties[i];
		
		returnValue[key] = obj[key];
	}
	
	return returnValue;
}

/**
 * window.document
 *
 * @global
**/
var doc = window.document,

/**
 * window.document.body
 *
 * @global
**/
body = doc.body;

/**
 * Cancels the event if it is cancelable, without stopping further propagation of the event
 *
 * @function preventDefault
 * @param {Event} e
 * @returns {Boolean} false
**/
function preventDefault(e) {
	e.preventDefault ? e.preventDefault() : e.returnValue = false;
	
	return false;
}

/**
 * Prevents further propagation of the current event
 *
 * @function stopPropagation
 * @param {Event} e
 * @returns {Boolean} false
**/
function stopPropagation(e) {
	e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
	
	return false;
}

/**
 * Checks if the browser support audios
 *
 * @function ifBrowserSupportAudio
 * @returns {Boolean}
**/
function ifBrowserSupportAudio() {
	return window.Audio !== undefined;
}

/**
 * Gets the preferred audio type
 *
 * @function getPreferredAudioType
 * @returns {?String}
**/
function getPreferredAudioType() {
	var audioTypes, i, il, audio;
	
	if (ifBrowserSupportAudio()) {
		audioTypes = ['ogg', 'mp3', 'mpeg', 'wav'];
		i = 0;
		il = audioTypes.length;
		audio = new Audio();
		
		for (; i < il; i++) {
			if (audio.canPlayType('audio/' + audioTypes[i])) {
				return audioTypes[i];
			}
		}
	}
	
	return null;
}

/**
 * Plays an audio
 *
 * @function playAudio
 * @param {Audio} audio
 * @param {int} [volume]
 * @param {Boolean} [reset]
 * @param {Function} [callback]
 * @returns {Audio}
**/
function playAudio(audio, volume, reset, callback) {
	var callbackWrapper;
	
	if (audio.readyState > 0) {
		if (reset === true) {
			audio.pause();
			audio.currentTime = 0;
		}
		
		if (typeof callback === 'function') {
			callbackWrapper = function(e) {
				callback.call(this, e);
				audio.removeEventListener('ended', callbackWrapper);
			};
			
			audio.addEventListener('ended', callbackWrapper);
		}
		
		if (volume !== undefined) {
			audio.volume = Math.min(Math.max(0, volume / 100), 1);
		}
		
		audio.play();
	}
	
	return audio;
}

/**
 * Creates an HTML element
 *
 * @function createElement
 * @param {String} tagName
 * @param {HTMLElement} [parentElement]
 * @param {String} [textContent]
 * @returns {HTMLElement}
**/
function createElement(tagName, parentElement, textContent) {
	var elem = window.document.createElement(tagName);
	
	if (parentElement !== undefined) {
		parentElement.appendChild(elem);
	}
	
	if (textContent !== undefined) {
		elem.textContent = textContent;
	}
	
	return elem;
}

/**
 * HTTP namespace
 *
 * @namespace HTTP
 * @description todoc
**/
var HTTP = Object.create(null);

HTTP.get = (function() {
	function onLoad(callback, e) {
		var xmlHttp = e.target;
		
		if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
			callback(xmlHttp.responseText);
		}
	}

	/**
	 * HTTP get
	 *
	 * @method
	 * @name HTTP.get
	 * @param {String} url
	 * @param {Function} callback
	 * @returns {XMLHttpRequest}
	 * @todo Error handling
	 **/
	return function get(url, callback) {
		var httpRequest = new XMLHttpRequest();
		
		httpRequest.open('GET', url, true);
		httpRequest.onreadystatechange = onLoad.bind(httpRequest, callback);
		httpRequest.send(null);
		
		return httpRequest;
	};
}());

/**
 * HTTP getJSON
 *
 * @memberof HTTP
 * @param {String} url
 * @param {Function} callback
 * @returns {XMLHttpRequest}
 * @todo Invalid JSON handling
**/
HTTP.getJSON = function(url, callback) {
	return HTTP.get(url, function(data) {
		callback(JSON.parse(data));
	});
};

/**
 * HTTP post
 *
 * @memberof HTTP
 * @param {String} url
 * @param {String} data
 * @param {Function} callback
 * @returns {XMLHttpRequest}
 * @todo Error handling
**/
HTTP.post = function(url, data, callback) {
	var httpRequest = new XMLHttpRequest();
	
	httpRequest.open('POST', url, true);
	httpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	httpRequest.onreadystatechange = callback;
	httpRequest.send(null);
	
	return httpRequest;
};

/**
 * Creates a new Enum
 *
 * @class Enum
 * @param {...*}
 * @description todoc
**/
function Enum() {
	enumAssign.apply(this, Array.prototype.slice.call(arguments));
}

/**
 * Loops through the given enum
 *
 * @memberof Enum
 * @static
 * @param {Enum} enumerable
 * @param {Function} callback
 * @returns {Enum}
**/
Enum.each = function(enumerable, callback) {
	var hasOwn = Object.prototype.hasOwnProperty
	, key;
	
	for (key in enumerable) {
		if (hasOwn.call(enumerable, key)) {
			callback(key, enumerable[key]);
		}
	}
	
	return enumerable;
};

/** @lends Enum# **/
var enumProto = Enum.prototype = Object.create(null);
enumProto.constructor = Enum;

function enumAssign() {
	var i = 0
	, il = arguments.length
	, toString = Object.prototype.toString
	, hasOwn = Object.prototype.hasOwnProperty
	, arg
	, key;
	
	for (; i < il; i++) {
		arg = arguments[i];
		
		if (typeof arg === 'string') {
			this[arg] = arg;
		} else if (toString.call(arg) === '[object Array]') {
			enumAssign.apply(this, arg);
		} else if (toString.call(arg) === '[object Object]') {
			for (key in arg) {
				if (hasOwn.call(arg, key)) {
					this[key] = arg[key];
				}
			}
		}
	}
}

/**
 * Creates a new Event
 *
 * @class Event
 * @param {String} type
 * @description todoc
**/
function Event(type) {
	this.type = type;
	this.data = {};
	this.timeStamp = new Date().getTime();
}

/** @lends Event# **/
var eventProto = Event.prototype = Object.create(null);
eventProto.constructor = Event;

/**
 * The type of the event
 *
 * @type String
 * @default null
 * @readonly
**/
eventProto.type = null;

/**
 * The original event
 *
 * @type Event
 * @default null
 * @readonly
**/
eventProto.originalEvent = null;

/**
 * The target of the event
 *
 * @type Object
 * @default null
 * @readonly
**/
eventProto.target = null;

/**
 * Custom data of the event
 *
 * @type Object
 * @default null
 * @readonly
**/
eventProto.data = null;

/**
 * The time (in milliseconds since the epoch) at which the event was created
 *
 * @type Number
 * @default 0
 * @readonly
*/
eventProto.timeStamp = 0;

/**
 * Indicates whether the event is cancelable or not
 *
 * @type Boolean
 * @default true
*/
eventProto.cancelable = true;

/**
 * Returns a boolean indicating whether or not eventProto.preventDefault() was called on the event
 *
 * @type Boolean
 * @default false
*/
eventProto.defaultPrevented = false;

/**
 * Returns a boolean indicating whether or not eventProto.stopPropagation() was called on the event
 *
 * @type Boolean
 * @default false
*/
eventProto.propagationStopped = false;

/**
 * The return value of the event
 *
 * @type *
 * @default null
*/
eventProto.returnValue = null;

/**
 * Cancels the event if it is cancelable, without stopping further propagation of the event
 *
 * @returns {Event} this
*/
eventProto.preventDefault = function() {
	if (this.cancelable === true) {
		this.defaultPrevented = true;
	}
	
	return this;
};

/**
 * Prevents further propagation of the current event
 *
 * @returns {Event} this
*/
eventProto.stopPropagation = function() {
	this.propagationStopped = true;
	
	return this;
};

/**
 * Returns the string value of the event
 *
 * @returns {String}
**/
eventProto.toString = function() {
	return this.type + ' Event';
};

/**
 * Creates a new EventTarget
 * 
 * @class EventTarget
 * @description todoc
**/
function EventTarget() {
	this.eventList = {};
}

/**
 * Transmits an event handler from an eventTarget to an other
 * 
 * @memberof EventTarget
 * @static
 * @param {String} type
 * @param {EventTarget} source
 * @param {EventTarget} target
 * @returns {EventTarget}
**/
EventTarget.transmit = function(type, source, target) {
	source.on(type, function() {
		target.dispatchEvent(type);
	});
	
	return target;
};

/** @lends EventTarget# **/
var eventTargetProto = EventTarget.prototype = Object.create(null);
eventTargetProto.constructor = EventTarget;

/**
 * Adds a listener to the specified event
 * 
 * @param {String} type
 * @param {Function} listener
 * @returns {EventTarget} this
**/
eventTargetProto.addEventListener = function(type, listener) {
	var events = this.eventList[type] || (this.eventList[type] = []);
	
	events.push(listener);
	
	return this;
};

/**
 * Determines if the eventTarget has eventListeners of the given type
 * 
 * @param {String} type
 * @returns {Boolean}
**/
eventTargetProto.hasEventListener = function(type) {
	var events = this.eventList[type];
	
	return !!events && events.length > 0;
};

/**
 * Removes a listener from the specified event
 * 
 * @param {String} type
 * @param {Function} listener
 * @returns {EventTarget} this
**/
eventTargetProto.removeEventListener = function(type, listener) {
	var events = this.eventList[type],
		index;
	
	if (events !== undefined) {
		index = events.indexOf(listener);
		
		if (index !== -1) events.splice(index, 1);
	}
	
	return this;
};

/**
 * Removes all listeners
 * 
 * @returns {EventTarget} this
**/
eventTargetProto.removeAllEventListeners = function() {
	var eventList = this.eventList,
		hasOwn = Object.prototype.hasOwnProperty,
		key;
	
	for (key in eventList) {
		if (hasOwn.call(eventList, key)) delete eventList[key];
	}
	
	return this;
};

/**
 * Dispatches an Event at the specified EventTarget
 * 
 * @param {String} type
 * @returns {EventTarget} this
**/
eventTargetProto.dispatchEvent = function(type) {
	var events = this.eventList[type],
		args, i, il;
	
	if (events !== undefined) {
		args = Array.prototype.slice.call(arguments, 1);
		
		for (i = 0, il = events.length; i < il; i++) {
			events[i].apply(this, args);
		}
	}
	
	return this;
};

/**
 * Executes a provided function once per event listener
 * 
 * @param {String} type
 * @param {Function} callback
 * @returns {EventTarget} this
**/
eventTargetProto.eachEventListener = function(type, callback) {
	var events, i, il;
	
	if (this.hasEventListener(type)) {
		events = this.eventList[type];
		i = 0;
		il = events.length;
		
		for (; i < il; i++) {
			callback(events[i], i);
		}
	}
	
	return this;
};

/**
 * Adds a listener to the specified event
 * 
 * @method
 * @name EventTarget#on
 * @param {String} type
 * @param {Function} listener
 * @returns {EventTarget} this
 * @see EventTarget#addEventListener
**/
eventTargetProto.on = eventTargetProto.addEventListener;

/**
 * Removes a listener from the specified event
 * 
 * @method
 * @name EventTarget#off
 * @param {String} type
 * @param {Function} listener
 * @returns {EventTarget} this
 * @see EventTarget#removeEventListener
**/
eventTargetProto.off = eventTargetProto.removeEventListener;

/**
 * Removes all listeners
 * 
 * @method
 * @name EventTarget#offAll
 * @returns {EventTarget} this
 * @see EventTarget#removeAllEventListeners
**/
eventTargetProto.offAll = eventTargetProto.removeAllEventListeners;

/**
 * Dispatches an Event at the specified EventTarget
 * 
 * @method
 * @name EventTarget#emit
 * @param {String} type
 * @returns {EventTarget} this
 * @see EventTarget#dispatchEvent
**/
eventTargetProto.emit = eventTargetProto.dispatchEvent;

/**
 * Returns the EventTarget string value
 * 
 * @returns {String}
**/
eventTargetProto.toString = function() {
	return '[object EventTarget]';
};

/**
 * Creates a new OrderedList
 *
 * @class OrderedList
 * @description todoc
**/
function OrderedList() {
	var list = Object.create(null)
	, isDirty = true;
	
	/**
	 * The number of items in the OrderedList
	 *
	 * @alias OrderedList#count
	 * @type int
	 * @readonly
	**/
	this.count = 0;
	
	/**
	 * Adds an item to the OrderedList
	 *
	 * @alias OrderedList#add
	 * @param {*} item
	 * @param {int} [orderID]
	 * @returns {OrderedList} this
	**/
	this.add = function(item, orderID) {
		if (!(typeof orderID === 'number' && orderID === orderID | 0 && isFinite(orderID))) {
			orderID = this.lastID() || 0;
		}
		
		if (list[orderID] === undefined) {
			list[orderID] = [];
			isDirty = true;
		}
		
		list[orderID].push(item);
		
		++this.count;
		
		return this;
	};
	
	/**
	 * Adds an item to the OrderedList exactly before the given reference item
	 *
	 * @alias OrderedList#addBefore
	 * @param {*} item
	 * @param {*} reference
	 * @returns {OrderedList} this
	**/
	this.addBefore = function(item, reference) {
		var orderID = this.getOrderOf(reference)
		, index;
		
		if (orderID !== null) {
			index = list[orderID].indexOf(reference);
			
			if (index !== -1) {
				list[orderID].splice(index, 0, item);
				
				++this.count;
			}
		}
		
		return this;
	};
	
	/**
	 * Adds an item to the OrderedList exactly after the given reference item
	 *
	 * @alias OrderedList#addAfter
	 * @param {*} item
	 * @param {*} reference
	 * @returns {OrderedList} this
	**/
	this.addAfter = function(item, reference) {
		var orderID = this.getOrderOf(reference)
		, index;
		
		if (orderID !== null) {
			index = list[orderID].indexOf(reference);
			
			if (index !== -1) {
				list[orderID].splice(index + 1, 0, item);
				
				++this.count;
			}
		}
		
		return this;
	};
	
	/**
	 * Removes an item from the OrderedList
	 *
	 * @alias OrderedList#remove
	 * @param {*} item
	 * @returns {?*} removed
	**/
	this.remove = function(item) {
		var orderID = this.getOrderOf(item)
		, removed = null
		, index;
		
		if (orderID !== null) {
			index = list[orderID].indexOf(item);
			
			if (index !== -1) {
				removed = list[orderID].splice(index, 1)[0];
				
				--this.count;
			}
		}
		
		return removed;
	};
	
	/**
	 * Checks if the OrderedList contains a specific item
	 *
	 * @alias OrderedList#has
	 * @param {*} item
	 * @returns {Boolean}
	**/
	this.has = function(item) {
		return this.getOrderOf(item) !== null;
	};
	
	/**
	 * Changes the place of the given item to the given order
	 *
	 * @alias OrderedList#reOrder
	 * @param {*} item
	 * @param {int} orderID
	 * @returns {OrderedList} this
	**/
	this.reOrder = function(item, orderID) {
		return this.remove(item).add(item, orderID);
	};
	
	/**
	 * Removes all items from the OrderedList
	 *
	 * @alias OrderedList#clear
	 * @returns {OrderedList} this
	**/
	this.clear = function() {
		this.order = {};
		
		this.count = 0;
		isDirty = true;
		
		return this;
	};
	
	/**
	 * Executes a provided callback function once per item in ascending order
	 *
	 * @alias OrderedList#each
	 * @param {Function} callback
	 * @param {*} [thisArg]
	 * @returns {OrderedList} this
	**/
	this.each = function(callback, thisArg) {
		var i = 0;
		
		this.getOrders().forEach(function(orderID) {
			list[orderID].forEach(function(item) {
				callback.call(thisArg, item, i++);
			});
		});
		
		return this;
	};
	
	/**
	 * Executes a provided callback function once per item in descending, stops when the callback's return value is not undefined and returns
	 *
	 * @alias OrderedList#execute
	 * @param {Function} callback
	 * @param {*} [thisArg]
	 * @returns {?*}
	**/
	this.execute = function(callback, thisArg) {
		var order = this.getOrders()
		, i = 0
		, j = order.length - 1
		, k, items, returnValue;
		
		for (; j >= 0; --j) {
			items = list[order[j]];
			k = items.length - 1;
			
			for (; k >= 0; --k) {
				returnValue = callback.call(thisArg, items[k], i++);
				
				if (returnValue !== undefined) return returnValue;
			}
		}
		
		return null;
	};
	
	/**
	 * Returns the lowest orderID of the OrderedList
	 *
	 * @alias OrderedList#firstID
	 * @returns {?int}
	**/
	this.firstID = function() {
		var id = this.getOrders()[0];
		
		return id === undefined ? null : id;
	};
	
	/**
	 * Returns the highest orderID of the OrderedList
	 *
	 * @alias OrderedList#lastID
	 * @returns {?int}
	**/
	this.lastID = function() {
		var id = this.getOrders().slice(-1)[0];
		
		return id === undefined ? null : id;
	};
	
	/**
	 * Returns the first element of the lowest orderID
	 *
	 * @alias OrderedList#first
	 * @returns {?*}
	**/
	this.first = function() {
		var items = list[this.firstID()];
		
		return items !== undefined && items.length > 0 ? items[0] : null;
	};
	
	/**
	 * Returns the last element of the highest orderID
	 *
	 * @alias OrderedList#last
	 * @returns {?*}
	**/
	this.last = function() {
		var items = list[this.lastID()];
		
		return items !== undefined && items.length > 0 ? items.slice(-1)[0] : null;
	};
	
	/**
	 * Returns an array of all the orderIDs in ascending order
	 *
	 * @method
	 * @name OrderedList#getOrders
	 * @returns {Array}
	**/
	this.getOrders = (function() {
		var cache = null;
		
		function sortFn(a, b) {
			return a - b;
		}
		
		return function getOrders() {
			if (isDirty) {
				cache = Object.keys(list).sort(sortFn);
				isDirty = false;
			}
			
			return cache;
		};
	}());
	
	/**
	 * Returns an array of all the items
	 *
	 * @alias OrderedList#all
	 * @returns {Array}
	**/
	this.all = function() {
		var items = [];
		
		this.each(function(item) {
			items.push(item);
		});
		
		return items;
	};
	
	/**
	 * Returns the orderID of the given item
	 *
	 * @alias OrderedList#getOrderOf
	 * @param {*} item
	 * @returns {?int}
	**/
	this.getOrderOf = function(item) {
		var returnValue = null,
			hasOwn = Object.prototype.hasOwnProperty,
			orderID;
		
		for (orderID in list) {
			if (hasOwn.call(list, orderID)) {
				if (list[orderID].indexOf(item) !== -1) {
					returnValue = orderID;
					
					break;
				}
			}
		}
		
		return returnValue;
	};
}

/** @lends OrderedList# **/
var orderedListProto = OrderedList.prototype = Object.create(null);
orderedListProto.constructor = OrderedList;

/**
 * Returns the string value of the orderedList
 *
 * @returns {String}
**/
orderedListProto.toString = function() {
	return '[object OrderedList]';
};

/**
 * Creates a new PublicStorage
 *
 * @class PublicStorage
 * @description todoc
**/
function PublicStorage() {
	/**
	 * The data object of the storage
	 *
	 * @alias PublicStorage#data
	 * @type Object
	**/
	this.data = {};
}

/** @lends PublicStorage# **/
var publicStorageProto = PublicStorage.prototype = Object.create(null);
publicStorageProto.constructor = PublicStorage;

/**
 * Gets data
 *
 * @param {String} key
 * @returns {*}
**/
publicStorageProto.get = function(key) {
	return Object.prototype.hasOwnProperty.call(this.data, key) ? this.data[key] : null;
};

/**
 * Sets data
 *
 * @param {String} key
 * @param {*} value
 * @returns {*}
**/
publicStorageProto.set = function(key, value) {
	return this.data[key] = value;
};

/**
 * Edits data
 *
 * @param {Object} source
 * @returns {PublicStorage} this
**/
publicStorageProto.edit = function(source) {
	var hasOwn = Object.prototype.hasOwnProperty,
		data = this.data,
		key;
	
	for (key in source) {
		if (hasOwn.call(source, key)) {
			data[key] = source[key];
		}
	}
	
	return this;
};



/**
 * Creates a new PrivateStorage
 *
 * @class PrivateStorage
 * @description todoc
**/
function PrivateStorage() {
	var hasOwn = Object.prototype.hasOwnProperty,
		data = {};
	
	/**
	 * Returns an item of the storage or null
	 *
	 * @alias PrivateStorage#get
	 * @param {String} key
	 * @returns {?*}
	**/
	this.get = function(key) {
		return hasOwn.call(data, key) ? data[key] : null;
	};
	
	/**
	 * Sets a value of the given key
	 *
	 * @alias PrivateStorage#set
	 * @param {String} key
	 * @param {*} value
	 * @returns {*} value
	**/
	this.set = function(key, value) {
		return data[key] = value;
	};
	
	/**
	 * Sets multiple values of the storage
	 *
	 * @alias PrivateStorage#edit
	 * @param {Object} source
	 * @returns {PrivateStorage} this
	**/
	this.edit = function(source) {
		var key;
		
		for (key in source) {
			if (hasOwn.call(source, key)) {
				data[key] = source[key];
			}
		}
		
		return this;
	};
	
	/**
	 * Returns all data
	 *
	 * @alias PrivateStorage#getAll
	 * @returns {Object}
	**/
	this.getAll = function() {
		var returnValue = {},
			key;
		
		for (key in data) {
			if (hasOwn.call(data, key)) {
				returnValue[key] = data[key];
			}
		}
		
		return returnValue;
	};
}

/** @lends PrivateStorage# **/
var privateStorageProto = PrivateStorage.prototype = Object.create(null);
privateStorageProto.constructor = PrivateStorage;

/**
 * Creates a new Storage
 *
 * @class Storage
 * @description todoc
**/
function Storage() {
	var hasOwn = Object.prototype.hasOwnProperty,
		data = {};
	
	/**
	 * Returns an item of the storage or null
	 *
	 * @alias Storage#get
	 * @param {String} key
	 * @returns {?*}
	**/
	this.get = function(key) {
		return hasOwn.call(data, key) ? data[key] : null;
	};
	
	/**
	 * Sets a value of the given key
	 *
	 * @alias Storage#set
	 * @param {String} key
	 * @param {*} value
	 * @returns {*} value
	**/
	this.set = function(key, value) {
		return data[key] = value;
	};
}

/** @lends Storage# **/
var storageProto = Storage.prototype = Object.create(null);
storageProto.constructor = Storage;

/**
 * Creates a Vector2
 *
 * @class Vector2
 * @param {Number} [x = 0]
 * @param {Number} [y = 0]
 * @description todoc
**/
function Vector2(x, y) {
	this.x = x || 0;
	this.y = y || 0;
}

/** @lends Vector2# **/
var vector2Proto = Vector2.prototype = Object.create(null);
vector2Proto.constructor = Vector2;

/**
 * The x position of the vector
 *
 * @type Number
 * @default 0
**/
vector2Proto.x = 0;

/**
 * The y position of the vector
 *
 * @type Number
 * @default 0
**/
vector2Proto.y = 0;

/**
 * Sets the x and y values of the vector
 *
 * @param {Number} x
 * @param {Number} y
 * @returns {Vector2} this
**/
vector2Proto.set = function(x, y) {
	this.x = x;
	this.y = y;
	
	return this;
};

/**
 * Sets the x value of the vector
 *
 * @param {Number} x
 * @returns {Vector2} this
**/
vector2Proto.setX = function(x) {
	this.x = x;
	
	return this;
};

/**
 * Sets the y value of the vector
 *
 * @param {Number} y
 * @returns {Vector2} this
**/
vector2Proto.setY = function(y) {
	this.y = y;
	
	return this;
};

/**
 * Modifies the x and y values of the vector
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.add = function(v) {
	this.x += v.x;
	this.y += v.y;
	
	return this;
};

/**
 * Sets the x and y values of the vector to the sum of the two given vectors
 *
 * @param {Vector2} v1
 * @param {Vector2} v2
 * @returns {Vector2} this
**/
vector2Proto.addVectors = function(v1, v2) {
	this.x = v1.x + v2.x;
	this.y = v1.y + v2.y;
	
	return this;
};

/**
 * Modifies the x and y values of the vector by the given scalar value
 *
 * @param {Number} s
 * @returns {Vector2} this
**/
vector2Proto.addScalar = function(s) {
	this.x += s;
	this.y += s;
	
	return this;
};

/**
 * Modifies the x and y values of the vector by the given vector
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.sub = function(v) {
	this.x -= v.x;
	this.y -= v.y;
	
	return this;
};

/**
 * Sets the x and y values of the vector to the subtraction of the two given vectors
 *
 * @param {Vector2} v1
 * @param {Vector2} v2
 * @returns {Vector2} this
**/
vector2Proto.subVectors = function(v1, v2) {
	this.x = v1.x - v2.x;
	this.y = v1.y - v2.y;
	
	return this;
};

/**
 * Modifies the x and y values of the vector by the given scalar value
 *
 * @param {Number} s
 * @returns {Vector2} this
**/
vector2Proto.subScalar = function(s) {
	this.x -= s;
	this.y -= s;
	
	return this;
};

/**
 * Multiplies the x and y values of the vector by the given vector
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.multiply = function(v) {
	this.x *= v.x;
	this.y *= v.y;
	
	return this;
};

/**
 * Multiplies the x and y values of the vector by the given scalar value
 *
 * @param {Number} s
 * @returns {Vector2} this
**/
vector2Proto.multiplyScalar = function(s) {
	this.x *= s;
	this.y *= s;
	
	return this;
};

/**
 * Divides the x and y values of the vector by the given vector
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.divide = function(v) {
	this.x = v.x === 0 ? 0 : this.x / v.x;
	this.y = v.y === 0 ? 0 : this.y / v.y;
	
	return this;
};

/**
 * Divides the x and y values of the vector by the given scalar value
 *
 * @param {Number} s
 * @returns {Vector2} this
**/
vector2Proto.divideScalar = function(s) {
	this.x = s === 0 ? 0 : this.x / s;
	this.y = s === 0 ? 0 : this.y / s;
	
	return this;
};

/**
 * Sets the vector x and y values to the minimum from it's value and the given vector's value
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.min = function(v) {
	if (this.x > v.x) this.x = v.x;
	if (this.y > v.y) this.y = v.y;
	
	return this;
};

/**
 * Sets the vector x and y values to the maximum from it's value and the given vector's value
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.max = function(v) {
	if (this.x < v.x) this.x = v.x;
	if (this.y < v.y) this.y = v.y;
	
	return this;
};

/**
 * Sets the vector x and y values to the largest integer less than or equal to it's value
 *
 * @returns {Vector2} this
**/
vector2Proto.floor = function() {
	this.x = Math.floor(this.x);
	this.y = Math.floor(this.y);
	
	return this;
};

/**
 * Sets the vector x and y values to the smallest integer greater than or equal to it's value
 *
 * @returns {Vector2} this
**/
vector2Proto.ceil = function() {
	this.x = Math.ceil(this.x);
	this.y = Math.ceil(this.y);
	
	return this;
};

/**
 * Sets the vector x and y values to the nearest integer to it's value
 *
 * @returns {Vector2} this
**/
vector2Proto.round = function() {
	this.x = Math.round(this.x);
	this.y = Math.round(this.y);
	
	return this;
};

/**
 * Negates the vectors x and y values
 *
 * @returns {Vector2} this
**/
vector2Proto.negate = function() {
	this.x = -this.x;
	this.y = -this.y;
	
	return this;
};

/**
 * Rotates the vector by a given radian
 *
 * @param {Number} rad
 * @returns {Vector2} this
**/
vector2Proto.rotate = function(rad) {
	var cos = Math.cos(rad);
	var sin = Math.sin(rad);
	
	var x = this.x * cos - this.y * sin;
	var y = this.x * sin + this.y * cos;
	
	this.x = x;
	this.y = y;
	
	return this;
};

/**
 * Sets the length of the vector to the length of the given vector
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.setLength = function(v) {
	this.normalize().multiplyScalar(v.length());
	
	return this;
};


/**
 * Returns the dot product between this and the given vector
 *
 * @param {Vector2} v
 * @returns {Number}
**/
vector2Proto.dot = function(v) {
	return this.x * v.x + this.y * v.y;
};

/**
 * Returns the 2D cross product between this and the given vector
 *
 * @param {Vector2} v
 * @returns {Number}
**/
vector2Proto.cross = function(v) {
	return this.x * v.y - this.y * v.x;
};

/**
 * Returns the length of the vector
 *
 * @returns {Number}
**/
vector2Proto.length = function() {
	return Math.sqrt(this.x * this.x + this.y * this.y);
};

/**
 * Normalizes the vector
 *
 * @returns {Vector2} this
**/
vector2Proto.normalize = function() {
	return this.divideScalar(this.length());
};

/**
 * Rotates the vector to a given angle
 *
 * @param {Number} rad
 * @returns {Vector2} this
**/
vector2Proto.toAngle = function(rad) {
	this.rotate(2 * Math.PI - Math.atan2(this.y, this.x) + rad);
	
	return this;
};

/**
 * Returns the distance between this and the given vector
 *
 * @param {Vector2} v
 * @returns {Number}
**/
vector2Proto.distanceTo = function(v) {
	var dx = this.x - v.x;
	var dy = this.y - v.y;
	
	return Math.sqrt(dx * dx + dy * dy);
};

/**
 * Returns the angle between this and the given vector
 *
 * @param {Vector2} v
 * @returns {Number}
**/
vector2Proto.angleTo = function(v) {
	return Math.atan2(v.cross(this), v.dot(this));
};

/**
 * Determines if the given vector is equal to this vector
 *
 * @param {Vector2} v
 * @returns {Boolean}
**/
vector2Proto.equals = function(v) {
	return this.x === v.x && this.y === v.y;
};

/**
 * Copies the given vector
 *
 * @param {Vector2} v
 * @returns {Vector2} this
**/
vector2Proto.copy = function(v) {
	this.x = v.x;
	this.y = v.y;
	
	return this;
};

/**
 * Clones the vector
 *
 * @returns {Vector2}
**/
vector2Proto.clone = function() {
	return new this.constructor(this.x, this.y);
};

/**
 * Returns the string value of the vector
 *
 * @returns {String}
**/
vector2Proto.toString = function() {
	return 'Vector2 at ' + this.x + ' ' + this.y;
};

/**
 * Creates a new Screen
 *
 * @class Screen
 * @description todoc
**/
function Screen() {
	this.pos = new Vector2();
}

/** @lends Screen# **/
var screenProto = Screen.prototype = Object.create(null);
screenProto.constructor = Screen;

/**
 * The position of the screen
 *
 * @type Vector2
 * @default null
 * @readonly
**/
screenProto.pos = null;

/**
 * The width of the screen
 *
 * @type Number
 * @default 0
**/
screenProto.width = 0;

/**
 * The height of the screen
 *
 * @type Number
 * @default 0
**/
screenProto.height = 0;

/**
 * Returns the string value of the screen
 *
 * @returns {String}
**/
screenProto.toString = function() {
	return 'Screen[' + this.width + ', ' + this.height + '] at ' + this.pos.x + ', ' + this.pos.y;
};

/**
 * Creates a new Cursor
 *
 * @class Cursor
 * @extends Vector2
 * @param {String} type
 * @description todoc
**/
function Cursor(type) {
	Vector2.call(this);
	
	this.type = type;
}

/** @lends Cursor# **/
var cursorProto = Cursor.prototype = Object.create(Vector2.prototype);
cursorProto.constructor = Cursor;

/**
 * The type of the cursor
 *
 * @type String
 * @default 'mouse'
 * @readonly
**/
cursorProto.type = 'mouse';

/**
 * The state of the cursor
 *
 * @type Boolean
 * @default false
 * @readonly
**/
cursorProto.onScreen = false;

/**
 * Returns the string value of the cursor
 *
 * @returns {String}
**/
cursorProto.toString = function() {
	return 'Cursor at ' + this.x + ', ' + this.y;
};

/**
 * Creates a new ClassList
 *
 * @class ClassList
 * @extends OrderedList
 * @description todoc
**/
function ClassList() {
	OrderedList.call(this);
}

/** @lends ClassList# **/
var classListProto = ClassList.prototype = Object.create(OrderedList.prototype);
classListProto.constructor = ClassList;

/**
 * Same as OrderedList's add method, but makes sure that every item can appear only once
 *
 * @param {*} item
 * @param {int} [orderID]
 * @returns {ClassList} this
**/
classListProto.add = function(item, orderID) {
	item = String(item);

	this.reOrder(item, orderID);

	return this;
};

/**
 * Toggles an item
 *
 * @param {String} item
 * @param {Number} [orderID]
 * @returns {ClassList} this
**/
classListProto.toggle = function(item, orderID) {
	return this.has(item) ? this.remove(item) : this.add(item, orderID);
};

/**
 * Returns the string value of the classList
 *
 * @returns {String}
**/
classListProto.toString = function() {
	return '[object ClassList]';
};

/**
 * Creates a new Loader
 *
 * @class Loader
 * @extends EventTarget
 * @description todoc
 * @param {String} [basePath = '/']
**/
function Loader(basePath) {
	EventTarget.call(this);

	if (basePath) {
		this.basePath = basePath;
	}
	
	this.reset();
}

/**
 * The status of the loader
 *
 * @memberof Loader
 * @property IMG
 * @type String
 * @static
 * @const
**/
Loader.IMG = 'Image';

/**
 * The status of the loader
 *
 * @memberof Loader
 * @property AUDIO
 * @type String
 * @static
 * @const
**/
Loader.AUDIO = 'Audio';

/** @lends Loader# **/
var loaderProto = Loader.prototype = Object.create(EventTarget.prototype);
loaderProto.constructor = Loader;

/**
 * The base path of the loader
 *
 * @type String
 * @default ''
 * @todo implement base path
 **/
loaderProto.basePath = '';

/**
 * The status of the loader
 *
 * @type String
 * @default 'pending'
 * @readonly
**/
loaderProto.status = 'pending';

/**
 * The loading queue of the loader
 *
 * @type Array
 * @default null
 * @readonly
**/
loaderProto.loadQueue = null;

/**
 * List of functions to set the queued element's src
 *
 * @type Array
 * @default null
 * @readonly
**/
loaderProto.setPathList = null;

/**
 * Starts the loader
 *
 * @returns {Loader} this
**/
loaderProto.start = function() {
	var setPath;
	
	this.emit('start');
	
	while (setPath = this.setPathList.pop()) setPath();
	
	if (this.loadQueue.length === 0) {
		this.status = 'complete';
		this.emit('complete');
	} else {
		this.status = 'processing';
	}
	
	return this;
};

/**
 * Clears the loader
 *
 * @returns {Loader} this
**/
loaderProto.clear = function() {
	this.loadQueue = [];
	this.setPathList = [];
	
	return this;
};

/**
 * Resets the loader
 *
 * @returns {Loader} this
**/
loaderProto.reset = function() {
	this.status = 'pending';
	
	this.clear();
	this.offAll();
	
	return this;
};

/**
 * Loads an item
 *
 * @param {String} type
 * @param {String} path
 * @param {Function} [callback]
 * @returns {?*}
**/
loaderProto.load = function(type, path, callback) {
	var method = this['load' + type];
	
	if (typeof method === 'function') {
		return method.call(this, path, callback, true);
	}
	
	return null;
};

/**
 * Loads an image
 *
 * @param {String} path
 * @param {Function} [callback]
 * @param {Boolean} [autoLoad]
 * @returns {Image}
**/
loaderProto.loadImage = function(path, callback, autoLoad) {
	var scope = this
	, img = new Image()
	, listener = function(e) {
		var loadEvent = new LoadEvent(e.type, img);
		
		loadEvent.originalEvent = e;
		loadEvent.data.type = Loader.IMG;
		loadEvent.data.src = path;
		
		scope.remove(img);
		
		if (!autoLoad) scope.emit(e.type, loadEvent);
		if (typeof callback === 'function') callback.call(img, e);
	};
	
	img.addEventListener('load', listener);
	img.addEventListener('error', listener);
	
	if (autoLoad === true) img.src = path;
	
	return img;
};

/**
 * Loads an audio
 *
 * @param {String} path
 * @param {Function} [callback]
 * @param {Boolean} [autoLoad]
 * @returns {Audio}
**/
loaderProto.loadAudio = function(path, callback, autoLoad) {
	var scope = this
	, audio = new Audio()
	, listener = function(e) {
		var loadEvent = new LoadEvent(e.type, audio);
		
		loadEvent.originalEvent = e;
		loadEvent.data.type = Loader.AUDIO;
		loadEvent.data.src = path;
		
		scope.remove(audio);
		
		if (!autoLoad) scope.emit(e.type, loadEvent);
		if (typeof callback === 'function') callback.call(audio, e);
	};
	
	audio.addEventListener('canplaythrough', listener);
	audio.addEventListener('error', listener);
	
	if (autoLoad) audio.src = path;
	
	return audio;
};

/**
 * Adds an item to the loading queue
 *
 * @param {String} type
 * @param {String} path
 * @returns {?*}
**/
loaderProto.add = function(type, path) {
	var method = this['add' + type];
	
	if (typeof method === 'function') {
		return method.call(this, path);
	}
	
	return null;
};

/**
 * Adds an image to the loading queue
 *
 * @param {String} path
 * @returns {Image}
**/
loaderProto.addImage = function(path) {
	var img = this.loadImage(path, null, false);
	
	this.setPathList.push(function() { img.src = path; });
	this.loadQueue.push(img);
	
	return img;
};

/**
 * Adds an audio to the loading queue
 *
 * @param {String} path
 * @returns {Audio}
**/
loaderProto.addAudio = function(path) {
	var audio = this.loadAudio(path, null, false);
	
	this.setPathList.push(function() { audio.src = path; });
	this.loadQueue.push(audio);
	
	return audio;
};

/**
 * Removes an item from the loading queue
 *
 * @param {*} item
 * @returns {?*}
 * @todo Remove function from setPathList
**/
loaderProto.remove = function(item) {
	var index = this.loadQueue.indexOf(item)
	, removed;
	
	if (index !== -1) {
		removed = this.loadQueue.splice(index, 1)[0];
		
		if (this.loadQueue.length === 0) this.emit('complete');
		
		return removed;
	}
	
	return null;
};

/**
 * Creates a new Queue
 *
 * @class Queue
 * @description todoc
**/
function Queue() {
	this.taskList = [];
}

/** @lends Queue# **/
var queueProto = Queue.prototype = Object.create(null);
queueProto.constructor = Queue;

/**
 * Adds a task to the taskList
 *
 * @param {Task} task
 * @param {Boolean} [autoReset = true]
 * @returns {Queue} this
**/
queueProto.add = function(task, autoReset) {
	this.taskList.push(task);
	
	if (autoReset !== false) task.reset();
	
	return this;
};

/**
 * Removes a task from the taskList
 *
 * @param {Task} task
 * @returns {Queue} this
**/
queueProto.remove = function(task) {
	var index = this.taskList.indexOf(task);
	
	if (index !== -1) this.taskList.splice(index, 1);
	
	return this;
};

/**
 * Removes all the tasks from the taskList
 *
 * @returns {Queue} this
**/
queueProto.clear = function() {
	var taskList = this.taskList;
	
	while (taskList.length) taskList.pop();
	
	return this;
};

/**
 * Restores a task
 *
 * @param {Task} task
 * @returns {Queue} this
**/
queueProto.restoreTask = function(task) {
	this.remove(task).add(task, true);
	
	return this;
};

/**
 * Executes the provided callback function once per element of taskList
 *
 * @param {Function} callback
 * @returns {Queue} this
**/
queueProto.each = function(callback) {
	this.taskList.forEach(callback, this);
	
	return this;
};

/**
 * Executes all the tasks from the taskList in order
 *
 * @returns {Queue} this
**/
queueProto.perform = (function() {
	function execute(task) {
		if (task.execute() === true) this.remove(task);
	}
	
	return function perform() {
		this.each(execute);
		
		return this;
	};
}());

/**
 * Returns the string value of the queue
 *
 * @returns {String}
**/
queueProto.toString = function() {
	return '[object Queue]';
};

/**
 * Creates a new Task
 *
 * @class Task
 * @description todoc
 * @param {Function} listener
 * @param {int} [interval = 0]
 * @param {Object} [thisArg = this]
**/
function Task(listener, interval, thisArg) {
	this.listener = listener;
	this.interval = interval || 0;
	this.thisArg = thisArg || this;
	
	this.reset();
}

/** @lends Task# **/
var taskProto = Task.prototype = Object.create(null);
taskProto.constructor = Task;

/**
 * Call the listener
 *
 * @returns {Boolean}
**/
taskProto.execute = function() {
	var now = new Date();
	
	if (now - this.lastTime >= this.interval) {
		this.lastTime = now;
		
		return this.listener.call(this.thisArg, this.tick++, now - this.startTime, this);
	}
	
	return false;
};

/**
 * Resets the task
 *
 * @returns {Task} this
**/
taskProto.reset = function() {
	var now = new Date();
	
	this.startTime = now;
	this.lastTime = now;
	this.tick = 0;
	
	return this;
};

/**
 * Returns the string value of the task
 *
 * @returns {String}
**/
taskProto.toString = function() {
	return '[object Task]';
};

/**
 * Creates a new Collection
 *
 * @class Collection
 * @description todoc
**/
function Collection() {
	this.items = Object.create(null);
}

/** @lends Collection# **/
var collectionProto = Collection.prototype = Object.create(null);
collectionProto.constructor = Collection;

/**
 * List of items
 *
 * @type Object
 * @default null
 * @readonly
**/
collectionProto.items = null;

/**
 * Returns the count of items
 *
 * @returns {int}
**/
collectionProto.count = function() {
	return this.keys().length;
};

/**
 * Sets the value for the key
 *
 * @param {String} key
 * @param {*} item
 * @returns {Collection} this
**/
collectionProto.set = function(key, item) {
	this.items[key] = item;
	
	return this;
};

/**
 * Sets the value for the key, if it doen't exists already
 *
 * @param {String} key
 * @param {*} item
 * @returns {Collection} this
**/
collectionProto.setSafe = function(key, item) {
	if (!this.has(key)) this.items[key] = item;
	
	return this;
};

/**
 * Gets an item
 *
 * @param {String} key
 * @returns {?*}
**/
collectionProto.get = function(key) {
	return this.has(key) ? this.items[key] : null;
};

/**
 * Checks if items contains a specific item
 *
 * @param {String} key
 * @returns {Boolean}
**/
collectionProto.has = function(key) {
	return key in this.items;
};

/**
 * Removes an item
 *
 * @param {String} key
 * @returns {Collection} this
**/
collectionProto.remove = function(key) {
	delete this.items[key];
	
	return this;
};

/**
 * Removes all items
 *
 * @returns {Collection} this
**/
collectionProto.clear = function() {
	this.items = Object.create(null);
	
	return this;
};

/**
 * Executes a provided callback function once per item
 *
 * @param {Function} callback
 * @param {Object} [thisArg]
 * @returns {Collection} this
**/
collectionProto.each = function(callback, thisArg) {
	var i = 0, key;
	
	for (key in this.items) {
		callback.call(thisArg, key, this.items[key], i++);
	}
	
	return this;
};

/**
 * Returns an array of keys
 *
 * @returns {Array}
**/
collectionProto.keys = function() {
	var keys = [], key;
	
	for (key in this.items) keys.push(key);
	
	return keys;
};

/**
 * Returns an array of values
 *
 * @returns {Array}
**/
collectionProto.values = function() {
	var values = [], key;
	
	for (key in this.items) values.push(this.items[key]);
	
	return values;
};

/**
 * Returns all
 *
 * @returns {Object}
**/
collectionProto.all = function() {
	var all = {}, key;
	
	for (key in this.items) all[key] = this.items[key];
	
	return all;
};

/**
 * Creates a new MouseEvent
 *
 * @class MouseEvent
 * @extends Event
 * @param {String} eventType
 * @param {Number} x
 * @param {Number} y
 * @param {String} button
 * @description todoc
**/
function MouseEvent(eventType, x, y, button) {
	Event.call(this, eventType);
	
	this.x = x;
	this.y = y;
	this.button = button;
}

/** @lends MouseEvent# **/
var mouseEventProto = MouseEvent.prototype = Object.create(Event.prototype);
mouseEventProto.constructor = MouseEvent;

/**
 * The x position
 *
 * @type Number
 * @default 0
 * @readonly
**/
mouseEventProto.x = 0;

/**
 * The y position
 *
 * @type Number
 * @default 0
 * @readonly
**/
mouseEventProto.y = 0;

/**
 * The button of the event
 *
 * @type String
 * @default null
 * @readonly
**/
mouseEventProto.button = null;

/**
 * Creates a new KeyboardEvent
 *
 * @class KeyboardEvent
 * @extends Event
 * @param {String} eventType
 * @param {int} keyCode
 * @description todoc
**/
function KeyboardEvent(eventType, keyCode) {
	Event.call(this, eventType);
	
	this.keyCode = keyCode;
	this.character = String.fromCharCode(keyCode).toLowerCase();
}

/** @lends KeyboardEvent# **/
var keyboardEventProto = KeyboardEvent.prototype = Object.create(Event.prototype);
keyboardEventProto.constructor = KeyboardEvent;

/**
 * The keyCode of the event
 *
 * @type int
 * @default null
 * @readonly
**/
keyboardEventProto.keyCode = null;

/**
 * The character of the event
 *
 * @type String
 * @default null
 * @readonly
**/
keyboardEventProto.character = null;

/**
 * Creates a new DragEvent
 *
 * @class DragEvent
 * @extends Event
 * @param {String} eventType
 * @param {Number} x
 * @param {Number} y
 * @param {*} dragged
 * @description todoc
**/
function DragEvent(eventType, x, y, dragged) {
	Event.call(this, eventType);
	
	this.dragStartX = x;
	this.dragStartY = y;
	
	this.dragged = Array.prototype.concat(dragged);
}

/** @lends DragEvent# **/
var dragEventProto = DragEvent.prototype = Object.create(Event.prototype);
dragEventProto.constructor = DragEvent;

/**
 * The dragStartX of the event
 *
 * @type Number
 * @default null
 * @readonly
**/
dragEventProto.dragStartX = null;

/**
 * The dragStartY of the event
 *
 * @type Number
 * @default null
 * @readonly
**/
dragEventProto.dragStartY = null;

/**
 * The list of dragged items
 *
 * @type Array
 * @default null
 * @readonly
**/
dragEventProto.dragged = null;

/**
 * Creates a new LoadEvent
 *
 * @class LoadEvent
 * @extends Event
 * @param {String} eventType
 * @param {*} target
 * @description todoc
**/
function LoadEvent(eventType, target) {
	Event.call(this, eventType);
	
	this.target = target;
}

/** @lends LoadEvent# **/
var loadEventProto = LoadEvent.prototype = Object.create(Event.prototype);
loadEventProto.constructor = LoadEvent;

/**
 * Creates a new CollisionEvent
 *
 * @class CollisionEvent
 * @extends Event
 * @param {String} eventType
 * @param {*} objectives
 * @description todoc
**/
function CollisionEvent(eventType, objectives) {
	Event.call(this, eventType);
	
	this.objectives = Array.prototype.concat(objectives);
}

/** @lends CollisionEvent# **/
var collisionEventProto = CollisionEvent.prototype = Object.create(Event.prototype);
collisionEventProto.constructor = CollisionEvent;

/**
 * The objectives of the event
 *
 * @type Array
 * @default null
 * @readonly
**/
collisionEventProto.objectives = null;

/**
 * $methods namespace
 *
 * @namespace $methods
 * @description todoc
**/
var $methods = stdClass();

/**
 * $enums namespace
 *
 * @namespace $enums
 * @description todoc
**/
var $enums = stdClass();

/**
 * Enumeration of the element flow types
 *
 * @memberof $enums
 * @type Enum
 * @enum {String}
 * @readonly
**/
$enums.ELEMENT_FLOWS = new Enum('none', 'horizontal', 'vertical');

/**
 * $elements namespace
 *
 * @namespace $elements
 * @description todoc
**/
var $elements = stdClass();

/**
 * List of types
 *
 * @memberof $elements
 * @type Object
**/
$elements.types = stdClass();

/**
 * Adds a type
 *
 * @memberof $elements
 * @param {String} type
 * @param {Function} elementConstructor
 * @param {Boolean} [instantiatable = true]
 * @param {Boolean} [extendable = true]
 * @returns {Object} $elements
**/
$elements.addType = function(type, elementConstructor, instantiatable, extendable) {
	this.types[type] = assign(stdClass(), {
		constructor: elementConstructor,
		instantiatable: instantiatable !== false,
		extendable: extendable !== false
	});
	
	return this;
};

/**
 * Checks if $elements contains a specific element type
 *
 * @memberof $elements
 * @param {String} type
 * @returns {Boolean}
**/
$elements.hasType = function(type) {
	return type in this.types;
};

/**
 * Gets an element type
 *
 * @memberof $elements
 * @param {String} type
 * @returns {?Object}
**/
$elements.getType = function(type) {
	return this.hasType(type) ? this.types[type] : null;
};

/**
 * Gets an instantiatable element type
 *
 * @memberof $elements
 * @param {String} type
 * @returns {?Function}
**/
$elements.getInstantiatable = function(type) {
	var typeDescriptor = this.getType(type);
	
	return (!isNull(typeDescriptor) && typeDescriptor.instantiatable === true) ? typeDescriptor.constructor : null;
};

/**
 * Gets an extendable element type
 *
 * @memberof $elements
 * @param {String} type
 * @returns {?Function}
**/
$elements.getExtendable = function(type) {
	var typeDescriptor = this.getType(type);
	
	return (!isNull(typeDescriptor) && typeDescriptor.extendable === true) ? typeDescriptor.constructor : null;
};

/**
 * Removes an element type
 *
 * @memberof $elements
 * @param {String} type
 * @returns {Object} $elements
**/
$elements.removeType = function(type) {
	delete this.types[type];
	
	return this;
};

/**
 * $abstracts namespace
 *
 * @namespace $abstracts
 * @extends Collection
 * @description todoc
**/
var $abstracts = inherit(Collection);

$methods.createAbstractElement = function(elemID, elementType, elementUse, args) {
	$abstracts.set(elemID, new AbstractElement(elementType, elementUse, args));
	
	return this;
};

/**
 * $classes namespace
 *
 * @namespace $classes
 * @extends Collection
 * @description todoc
**/
var $classes = inherit(Collection);

/**
 * Creates a new class from a prototype
 *
 * @memberof $classes
 * @param {Object} proto
 * @param {Array} [ignore]
 * @returns {Object}
**/
$classes.fromPrototype = function(proto, ignore) {
	var returnValue = stdClass();
	
	if (!isArray(ignore)) ignore = [];
	
	forIn(proto, function(propertyKey, propertyVal) {
		var desc = Object.getOwnPropertyDescriptor(proto, propertyKey);
		
		if (
			isNull(propertyVal) ||
			isFunction(desc.get) ||
			isFunction(desc.set) ||
			isFunction(propertyVal) ||
			inArray(ignore, propertyKey)
		) return undefined;
		
		returnValue[propertyKey] = propertyVal;
	});
	
	return returnValue;
};

$methods.createClass = function(className, classData) {
	$classes.set(className, classData);
	
	return this;
};

/**
 * $masks namespace
 *
 * @namespace $masks
 * @extends Collection
 * @description todoc
**/
var $masks = inherit(Collection);

/**
 * $plugins namespace
 *
 * @namespace $plugins
 * @extends Collection
 * @description todoc
**/
var $plugins = inherit(Collection);

/**
 * $assets namespace
 *
 * @namespace $assets
 * @description todoc
**/
var $assets = stdClass();

/**
 * List of types
 *
 * @memberof $assets
 * @type Object
 * @readonly
**/
$assets.types = stdClass();

/**
 * List of loaders
 *
 * @memberof $assets
 * @type Array
 * @readonly
**/
$assets.loaders = [];

/**
 * Gets a "free" loader
 *
 * @memberof $assets
 * @returns {Loader}
**/
$assets.getLoader = function() {
	var i = 0
	, il = this.loaders.length
	, loader;
	
	for (; i < il; i++) {
		loader = this.loaders[i];
		
		if (loader.status === 'pending') return loader;
	}
	
	loader = new Loader();
	
	this.loaders.push(loader);
	
	return loader;
};

/**
 * Adds a type
 *
 * @memberof $assets
 * @param {String} type
 * @returns {Object} $assets
**/
$assets.addType = function(type) {
	var addType;

	if (!this.hasType(type)) {
		addType = stdClass();
		
		addType.sourceMap = stdClass();
		addType.IDMap = stdClass();

		this.types[type] = addType;
	}
	
	return this;
};

/**
 * Determines if has type
 *
 * @memberof $assets
 * @param {String} type
 * @returns {Object} $assets
**/
$assets.hasType = function(type) {
	return !isUndefined(this.types[type]);
};

/**
 * Gets an item by ID
 *
 * @memberof $assets
 * @param {String} type
 * @param {String} ID
 * @returns {*}
**/
$assets.getByID = function(type, ID) {
	return this.hasType(type) ? this.types[type].IDMap[ID] || null : null;
};

/**
 * Gets an item by source
 *
 * @memberof $assets
 * @param {String} type
 * @param {String} src
 * @returns {*}
**/
$assets.getBySrc = function(type, src) {
	return this.hasType(type) ? this.types[type].sourceMap[src] || null : null;
};

/**
 * Gets all item of type
 *
 * @memberof $assets
 * @param {String} type
 * @returns {Array}
**/
$assets.getAll = function(type) {
	var returnValue = [];
	
	if (this.hasType(type)) {
		forIn(this.types[type].sourceMap, function(src, item) {
			addUnique(returnValue, item);
		});
		
		forIn(this.types[type].IDMap, function(ID, item) {
			addUnique(returnValue, item);
		});
	}
	
	return returnValue;
};

/**
 * Deletes all type
 *
 * @memberof $assets
 * @returns {Object} $assets
**/
$assets.clear = function() {
	this.types = stdClass();
	
	return this;
};

/**
 * Adds an item
 *
 * @memberof $assets
 * @param {String} type
 * @param {*} item
 * @param {String} src
 * @param {String} [id]
 * @returns {Object} $assets
**/
$assets.addItem = function(type, item, src, id) {
	var items = this.addType(type).types[type];
	
	items.sourceMap[src] = item;
	if (!isUndefined(id)) items.IDMap[id] = item;
	
	return this;
};

$assets.addType(Loader.IMG);
$assets.addType(Loader.AUDIO);

/**
 * $canvas namespace
 *
 * @namespace $canvas
 * @description todoc
**/
var $canvas = stdClass();

/**
 * The canvas DOM element
 *
 * @memberof $canvas
 * @type HTMLCanvasElement
**/
$canvas.DOMElement = createElement('canvas');

/**
 * The 2d context of the canvas
 *
 * @memberof $canvas
 * @type CanvasRenderingContext2D
**/
$canvas.ctx = $canvas.DOMElement.getContext('2d');

/**
 * Creates a new Element
 *
 * @class Element
 * @extends EventTarget
 * @description todoc
 * @property {ClassList} classList
 * @property {Vector2} velocity
**/
function Element() {
	EventTarget.call(this);
	Storage.call(this);
	addElementPropertyHandlers.call(this);
	
	this.uuid = generateUUID();
	this.velocity = new Vector2(0, 0);
	
	this.classList = new ClassList();
	this.classList.add(elementProto.elementType, 0);
	
	forIn(elementClass, this.addProp, this);
}

/** @lends Element# **/
var elementProto = Element.prototype = Object.create(EventTarget.prototype);
elementProto.constructor = Element;

var addElementPropertyHandlers = (function() {
	/**
	 * Creates a new ElementProperty
	 *
	 * @class ElementProperty
	 * @param {String} propertyKey
	 * @param {*} primitiveValue
	 * @param {Boolean} inheritable
	 * @description todoc
	**/
	function ElementProperty(propertyKey, primitiveValue, inheritable) {
		this.key = propertyKey;
		this.edit(primitiveValue, inheritable);
		this.initialValue = this.value;
		
		this.value = 'inherit';
	}
	
	/** @lends ElementProperty# **/
	var elementPropertyProto = ElementProperty.prototype = stdClass();
	elementPropertyProto.constructor = ElementProperty;
	
	/**
	 * Edits the property, sets it's primitiveValue and inheritable properties
	 *
	 * @param {*} primitiveValue
	 * @param {Boolean} [inheritable]
	 * @returns {ElementProperty} this
	**/
	elementPropertyProto.edit = function(primitiveValue, inheritable) {
		var parsed = parseMethod(primitiveValue);
		
		this.primitiveValue = primitiveValue;
		this.value = isNull(parsed) ? primitiveValue : parsed;
		this.type = isFunction(this.value) ? 'method' : 'property';
		
		if (isBoolean(inheritable)) this.inheritable = inheritable;
		
		return this;
	};
	
	return function propertyHandlers() {
		var _properties = stdClass();
		
		/**
		 * Adds a property to the Element
		 *
		 * @alias Element#addProp
		 * @param {String} propertyKey
		 * @param {*} primitiveValue
		 * @param {Boolean} [inheritable]
		 * @returns {Element} this
		 * @todo Need to rewrite the property handler methods
		**/
		this.addProp = function(propertyKey, primitiveValue, inheritable) {
			if (!isBoolean(inheritable)) inheritable = true;
			
			if (this.hasProp(propertyKey)) {
				warning(this.toString() + ' already has property ' + propertyKey);
			}
			
			_properties[propertyKey] = new ElementProperty(propertyKey, primitiveValue, inheritable);
			
			Object.defineProperty(this, propertyKey, {
				configurable: true,
				enumerable: false,
				get: function () {
					var val = _properties[propertyKey].value;
					
					if (val === 'inherit') val = this.classList.execute(function(className) {
						var classData = $classes.get(className);
						
						if (
							!isNull(classData) &&
							!isUndefined(classData[propertyKey]) &&
							classData[propertyKey] !== 'inherit'
						) return classData[propertyKey] === 'initial' ? _properties[propertyKey].initialValue : classData[propertyKey];
					}, this);
					
					return isFunction(val) ? val.call(this) : val;
				},
				set: function (propertyVal) {
					return _properties[propertyKey].edit(propertyVal).value;
				}
			});
			
			return this;
		};
		
		/**
		 * Adds a property to the Element if it doesn't has it already
		 *
		 * @alias Element#addPropSafe
		 * @param {String} propertyKey
		 * @param {*} primitiveValue
		 * @param {Boolean} [inheritable]
		 * @returns {Element} this
		**/
		this.addPropSafe = function(propertyKey, primitiveValue, inheritable) {
			if (!this.hasProp(propertyKey)) this.addProp(propertyKey, primitiveValue, inheritable);
			
			return this;
		};
		
		/**
		 * Checks if the Element has a specific property
		 *
		 * @alias Element#hasProp
		 * @param {String} propertyKey
		 * @returns {Boolean}
		**/
		this.hasProp = function(propertyKey) {
			return has(_properties, propertyKey);
		};
		
		/**
		 * Removes a property from the Element
		 *
		 * @alias Element#removeProp
		 * @param {String} propertyKey
		 * @returns {Element} this
		**/
		this.removeProp = function(propertyKey) {
			if (has(_properties, propertyKey)) {
				delete _properties[propertyKey];
				delete this[propertyKey];
			}
			
			return this;
		};
		
		/**
		 * Edits a property of the Element
		 *
		 * @alias Element#setProp
		 * @param {String} propertyKey
		 * @param {*} primitiveValue
		 * @param {Boolean} [inheritable]
		 * @returns {Element} this
		**/
		this.setProp = function(propertyKey, primitiveValue, inheritable) {
			if (has(_properties, propertyKey)) {
				_properties[propertyKey].edit(primitiveValue, inheritable);
			}
			
			return this;
		};
		
		/**
		 * Returns an ElementProperty of the Element by key
		 *
		 * @alias Element#getProp
		 * @param {String} propertyKey
		 * @returns {?ElementProperty}
		**/
		this.getProp = function(propertyKey) {
			return _properties[propertyKey] || null;
		};
		
		/**
		 * Returns all ElementProperty from the Element (reduced by the given filter)
		 *
		 * @alias Element#getProps
		 * @param {Function} [filter]
		 * @returns {Array}
		**/
		this.getProps = function(filter) {
			var props = [];
			
			forIn(_properties, function(propertyKey, property) {
				if (isUndefined(filter) || filter.call(this, propertyKey, property) === true) props.push(property);
				
				return false;
			}, this);
			
			return props;
		};
		
		return this;
	};
}());

/**
 * The type of the element
 *
 * @type String
 * @default 'Element'
 * @readonly
**/
elementProto.elementType = 'Element';

/**
 * The name of the element
 *
 * @type String
 * @default 'Element'
 * @readonly
**/
elementProto.elementName = 'Element';

/**
 * The id of the element
 *
 * @type String
 * @default null
 * @readonly
**/
elementProto.id = null;

/**
 * The uuid of the element
 *
 * @type String
 * @default null
 * @readonly
**/
elementProto.uuid = null;

Object.defineProperty(elementProto, 'className', {
	/**
	 * The classes of the element
	 *
	 * @name Element#className
	 * @type String
	 * @readonly
	 **/
	get: function() {
		return this.classList.all().join(' ');
	}
});

/**
 * The parentLayer of the element
 *
 * @type Layer
 * @default null
 * @readonly
**/
elementProto.parentLayer = null;

/**
 * The parentElement of the element
 *
 * @type ContainerElement
 * @default null
 * @readonly
**/
elementProto.parentElement = null;

Object.defineProperty(elementProto, 'vX', {
	/**
	 * The horizontal velocity of the element
	 *
	 * @name Element#vX
	 * @type Number
	 **/
	get: function() {
		return this.velocity.x;
	},
	set: function(xVal) {
		return this.velocity.setX(xVal).x;
	}
});

Object.defineProperty(elementProto, 'vY', {
	/**
	 * The vertical velocity of the element
	 *
	 * @name Element#vY
	 * @type Number
	 **/
	get: function() {
		return this.velocity.y;
	},
	set: function(yVal) {
		return this.velocity.setY(yVal).y;
	}
});

/**
 * The x position of the element
 *
 * @type Number|String
 * @default 0
**/
elementProto.x = 0;

/**
 * The y position of the element
 *
 * @type Number|String
 * @default 0
**/
elementProto.y = 0;

/**
 * The maximum x position of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.maxX = 'none';

/**
 * The maximum y position of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.maxY = 'none';

/**
 * The minimum x position of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.minX = 'none';

/**
 * The minimum y position of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.minY = 'none';

/**
 * The horizontal offset of the element
 *
 * @type Number|String
 * @default 0
**/
elementProto.offsetX = 0;

/**
 * The vertical offset of the element
 *
 * @type Number|String
 * @default 0
**/
elementProto.offsetY = 0;

/**
 * The width of the element
 *
 * @type Number|String
 * @default 0
**/
elementProto.width = 0;

/**
 * The height of the element
 *
 * @type Number|String
 * @default 0
**/
elementProto.height = 0;

/**
 * The maximum width of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.maxWidth = 'none';

/**
 * The maximum height of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.maxHeight = 'none';

/**
 * The minimum width of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.minWidth = 'none';

/**
 * The minimum height of the element
 *
 * @type Number|String
 * @default 'none'
**/
elementProto.minHeight = 'none';

/**
 * The top position of the element on the screen, including margin
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.top = 0;

/**
 * The right position of the element on the screen, including margin
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.right = 0;

/**
 * The bottom position of the element on the screen, including margin
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.bottom = 0;

/**
 * The left position of the element on the screen, including margin
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.left = 0;

/**
 * The x position of the element, relative to the scene
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.screenX = 0;

/**
 * The y position of the element, relative to the scene
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.screenY = 0;

/**
 * The x position of the element, relative to it's parent
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.parentX = 0;

/**
 * The y position of the element, relative to it's parent
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.parentY = 0;

/**
 * The layout width of the element
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.renderWidth = 0;

/**
 * The layout height of the element
 *
 * @type Number
 * @default 0
 * @readonly
**/
elementProto.renderHeight = 0;

Object.defineProperties(elementProto, {
	screenXC: {
		/**
		 * The horizontal center position of the element, relative to the scene
		 *
		 * @name Element#screenXC
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.screenX + this.renderWidth / 2 }
	},
	screenYC: {
		/**
		 * The vertical center position of the element, relative to the scene
		 *
		 * @name Element#screenYC
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.screenY + this.renderHeight / 2 }
	},
	
	screenXE: {
		/**
		 * The horizontal end position of the element, relative to the scene
		 *
		 * @name Element#screenXE
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.screenX + this.renderWidth }
	},
	screenYE: {
		/**
		 * The vertical end position of the element, relative to the scene
		 *
		 * @name Element#screenYE
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.screenY + this.renderHeight }
	},
	
	
	parentXC: {
		/**
		 * The horizontal center position of the element, relative to it's parent
		 *
		 * @name Element#parentXC
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.parentX + this.renderWidth / 2 }
	},
	parentYC: {
		/**
		 * The vertical center position of the element, relative to it's parent
		 *
		 * @name Element#parentYC
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.parentY + this.renderHeight / 2 }
	},
	
	parentXE: {
		/**
		 * The horizontal end position of the element, relative to it's parent
		 *
		 * @name Element#parentXE
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.parentX + this.renderWidth }
	},
	parentYE: {
		/**
		 * The vertical end position of the element, relative to it's parent
		 *
		 * @name Element#parentYE
		 * @type Number
		 * @readonly
		 **/
		get: function() { return this.parentY + this.renderHeight }
	}
});

/**
 * The width ratio of the element
 *
 * @type Number
 * @default 1
**/
elementProto.scaleX = 1;

/**
 * The height ratio of the element
 *
 * @type Number
 * @default 1
**/
elementProto.scaleY = 1;

/**
 * The clockwise rotation of the entry
 *
 * @type Number
 * @default 0
**/
elementProto.angle = 0;

/**
 * If the element should be mirrored horizontally
 *
 * @type Boolean
 * @default false
**/
elementProto.flipX = false;

/**
 * If the element should be mirrored vertically
 *
 * @type Boolean
 * @default false
**/
elementProto.flipY = false;

/**
 * The flow of the element
 *
 * @type ('none'|'horizontal'|'vertical')
 * @default 'none'
**/
elementProto.flow = 'none';

/**
 * The wrap of the element
 *
 * @type Boolean
 * @default true
**/
elementProto.wrap = true;

/**
 * The dragging status of the element
 *
 * @type Boolean
 * @default false
 * @todo Implement dragState
**/
elementProto.dragState = false;

/**
 * The focus status of the element
 *
 * @type Boolean
 * @default false
 * @todo Implement onFocus
**/
elementProto.onFocus = false;

/**
 * If the element allows mouse events through transparent pixel
 *
 * @type Boolean
 * @default true
 * @todo Implement isBlock
**/
elementProto.isBlock = true;

/**
 * The cursor type of the element
 *
 * @type String
 * @default 'default'
 * @todo Implement cursor
**/
elementProto.cursor = 'default';

/**
 * If the element allows drop on it
 *
 * @type Boolean
 * @default false
 * @todo Implement allowDrop
**/
elementProto.allowDrop = false;

/**
 * The title of the element
 *
 * @type String
 * @default ''
 * @todo Implement title
**/
elementProto.title = '';

/**
 * The transparency of the element
 *
 * @type Number
 * @default 1
 * @todo Implement opacity
**/
elementProto.opacity = 1;

/**
 * The background of the element
 *
 * @type String
 * @default ''
**/
elementProto.background = '';

/**
 * The mask id of the element
 *
 * @type String
 * @default ''
**/
elementProto.mask = '';

/**
 * The horizontal align of the element
 *
 * @type ('left'|'center'|'right')
 * @default 'left'
**/
elementProto.horizontalAlign = 'left';

/**
 * The vertical align of the element
 *
 * @type ('top'|'middle'|'bottom')
 * @default 'top'
**/
elementProto.verticalAlign = 'top';

/**
 * The top margin of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.marginTop = 0;

/**
 * The right margin of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.marginRight = 0;

/**
 * The bottom margin of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.marginBottom = 0;

/**
 * The left margin of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.marginLeft = 0;

Object.defineProperty(elementProto, 'margin', {
	/**
	 * The margin of the element
	 *
	 * @name Element#margin
	 * @type String
	 **/
	get: function() {
		return this.marginTop + ' ' + this.marginRight + ' ' + this.marginBottom + ' ' + this.marginLeft;
	},
	set: function(margin) {
		var pieces = margin.split(' ')
		, len = pieces.length;
		
		if (len === 1) {
			this.marginTop = pieces[0];
			this.marginRight = pieces[0];
			this.marginBottom = pieces[0];
			this.marginLeft = pieces[0];
		} else if (len === 2) {
			this.marginTop = pieces[0];
			this.marginRight = pieces[1];
			this.marginBottom = pieces[0];
			this.marginLeft = pieces[1];
		} else if (len === 3) {
			this.marginTop = pieces[0];
			this.marginRight = pieces[1];
			this.marginBottom = pieces[2];
			this.marginLeft = pieces[1];
		} else {
			this.marginTop = pieces[0];
			this.marginRight = pieces[1];
			this.marginBottom = pieces[2];
			this.marginLeft = pieces[3];
		}
		
		return this.margin;
	}
});

/**
 * The top padding of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.paddingTop = 0;

/**
 * The right padding of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.paddingRight = 0;

/**
 * The bottom padding of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.paddingBottom = 0;

/**
 * The left padding of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.paddingLeft = 0;

Object.defineProperty(elementProto, 'padding', {
	/**
	 * The padding of the element
	 *
	 * @name Element#padding
	 * @type String
	 **/
	get: function() {
		return this.paddingTop + ' ' + this.paddingRight + ' ' + this.paddingBottom + ' ' + this.paddingLeft;
	},
	set: function(padding) {
		var pieces = padding.split(' ')
		, len = pieces.length;
		
		if (len === 1) {
			this.paddingTop = pieces[0];
			this.paddingRight = pieces[0];
			this.paddingBottom = pieces[0];
			this.paddingLeft = pieces[0];
		} else if (len === 2) {
			this.paddingTop = pieces[0];
			this.paddingRight = pieces[1];
			this.paddingBottom = pieces[0];
			this.paddingLeft = pieces[1];
		} else if (len === 3) {
			this.paddingTop = pieces[0];
			this.paddingRight = pieces[1];
			this.paddingBottom = pieces[2];
			this.paddingLeft = pieces[1];
		} else {
			this.paddingTop = pieces[0];
			this.paddingRight = pieces[1];
			this.paddingBottom = pieces[2];
			this.paddingLeft = pieces[3];
		}
		
		return this.padding;
	}
});

/**
 * The border of the element
 *
 * @name Element#border
 * @type String
**/
Object.defineProperty(elementProto, 'border', {
	get: undefined,
	set: function(border) {
		var pieces = border.split(' ')
		, len = pieces.length;
		
		if (len >= 1) {
			this.borderTopWidth = pieces[0];
			this.borderRightWidth = pieces[0];
			this.borderBottomWidth = pieces[0];
			this.borderLeftWidth = pieces[0];
		}
		
		if (len >= 2) {
			this.borderTopStyle = pieces[1];
			this.borderRightStyle = pieces[1];
			this.borderBottomStyle = pieces[1];
			this.borderLeftStyle = pieces[1];
		}
		
		if (len === 3) {
			this.borderTopColor = pieces[2];
			this.borderRightColor = pieces[2];
			this.borderBottomColor = pieces[2];
			this.borderLeftColor = pieces[2];
		}
		
		return border;
	}
});

/**
 * The top border of the element
 *
 * @name Element#borderTop
 * @type String
**/
Object.defineProperty(elementProto, 'borderTop', {
	get: undefined,
	set: function(borderTop) {
		var pieces = borderTop.split(' ')
		, len = pieces.length;
		
		if (len >= 1) this.borderTopWidth = pieces[0];
		if (len >= 2) this.borderTopStyle = pieces[1];
		if (len === 3) this.borderTopColor = pieces[2];
		
		return borderTop;
	}
});

/**
 * The right border of the element
 *
 * @name Element#borderRight
 * @type String
**/
Object.defineProperty(elementProto, 'borderRight', {
	get: undefined,
	set: function(borderRight) {
		var pieces = borderRight.split(' ')
		, len = pieces.length;
		
		if (len >= 1) this.borderRightWidth = pieces[0];
		if (len >= 2) this.borderRightStyle = pieces[1];
		if (len === 3) this.borderRightColor = pieces[2];
		
		return borderRight;
	}
});

/**
 * The bottom border of the element
 *
 * @name Element#borderBottom
 * @type String
**/
Object.defineProperty(elementProto, 'borderBottom', {
	get: undefined,
	set: function(borderBottom) {
		var pieces = borderBottom.split(' ')
		, len = pieces.length;
		
		if (len >= 1) this.borderBottomWidth = pieces[0];
		if (len >= 2) this.borderBottomStyle = pieces[1];
		if (len === 3) this.borderBottomColor = pieces[2];
		
		return borderBottom;
	}
});

/**
 * The left border of the element
 *
 * @name Element#borderLeft
 * @type String
**/
Object.defineProperty(elementProto, 'borderLeft', {
	get: undefined,
	set: function(borderLeft) {
		var pieces = borderLeft.split(' ')
		, len = pieces.length;
		
		if (len >= 1) this.borderLeftWidth = pieces[0];
		if (len >= 2) this.borderLeftStyle = pieces[1];
		if (len === 3) this.borderLeftColor = pieces[2];
		
		return borderLeft;
	}
});

/**
 * The top border width of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.borderTopWidth = 0;

/**
 * The right border width of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.borderRightWidth = 0;

/**
 * The bottom border width of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.borderBottomWidth = 0;

/**
 * The left border width of the element
 *
 * @type String|Number
 * @default 0
**/
elementProto.borderLeftWidth = 0;

Object.defineProperty(elementProto, 'borderWidth', {
	/**
	 * The width of the element's border
	 *
	 * @name Element#borderWidth
	 * @type String
	 **/
	get: function() {
		return this.borderTopWidth + ' ' + this.borderRightWidth + ' ' + this.borderBottomWidth + ' ' + this.borderLeftWidth;
	},
	set: function(borderWidth) {
		var pieces = borderWidth.split(' ')
		, len = pieces.length;
		
		if (len === 1) {
			this.borderTopWidth = pieces[0];
			this.borderRightWidth = pieces[0];
			this.borderBottomWidth = pieces[0];
			this.borderLeftWidth = pieces[0];
		} else if (len === 2) {
			this.borderTopWidth = pieces[0];
			this.borderRightWidth = pieces[1];
			this.borderBottomWidth = pieces[0];
			this.borderLeftWidth = pieces[1];
		} else if (len === 3) {
			this.borderTopWidth = pieces[0];
			this.borderRightWidth = pieces[1];
			this.borderBottomWidth = pieces[2];
			this.borderLeftWidth = pieces[1];
		} else {
			this.borderTopWidth = pieces[0];
			this.borderRightWidth = pieces[1];
			this.borderBottomWidth = pieces[2];
			this.borderLeftWidth = pieces[3];
		}
		
		return this.borderWidth;
	}
});

/**
 * The top border style of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderTopStyle = 'none';

/**
 * The right border style of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderRightStyle = 'none';

/**
 * The bottom border style of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderBottomStyle = 'none';

/**
 * The left border style of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderLeftStyle = 'none';

Object.defineProperty(elementProto, 'borderStyle', {
	/**
	 * The style of the element's border
	 *
	 * @name Element#borderStyle
	 * @type String
	 **/
	get: function() {
		return this.borderTopStyle + ' ' + this.borderRightStyle + ' ' + this.borderBottomStyle + ' ' + this.borderLeftStyle;
	},
	set: function(borderStyle) {
		var pieces = borderStyle.split(' ')
		, len = pieces.length;
		
		if (len === 1) {
			this.borderTopStyle = pieces[0];
			this.borderRightStyle = pieces[0];
			this.borderBottomStyle = pieces[0];
			this.borderLeftStyle = pieces[0];
		} else if (len === 2) {
			this.borderTopStyle = pieces[0];
			this.borderRightStyle = pieces[1];
			this.borderBottomStyle = pieces[0];
			this.borderLeftStyle = pieces[1];
		} else if (len === 3) {
			this.borderTopStyle = pieces[0];
			this.borderRightStyle = pieces[1];
			this.borderBottomStyle = pieces[2];
			this.borderLeftStyle = pieces[1];
		} else {
			this.borderTopStyle = pieces[0];
			this.borderRightStyle = pieces[1];
			this.borderBottomStyle = pieces[2];
			this.borderLeftStyle = pieces[3];
		}
		
		return this.borderStyle;
	}
});

/**
 * The top border color of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderTopColor = 'none';

/**
 * The right border color of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderRightColor = 'none';

/**
 * The bottom border color of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderBottomColor = 'none';

/**
 * The left border color of the element
 *
 * @type String
 * @default 'none'
**/
elementProto.borderLeftColor = 'none';

Object.defineProperty(elementProto, 'borderColor', {
	/**
	 * The color of the element's border
	 *
	 * @name Element#borderColor
	 * @type String
	 **/
	get: function() {
		return this.borderTopColor + ' ' + this.borderRightColor + ' ' + this.borderBottomColor + ' ' + this.borderLeftColor;
	},
	set: function(borderColor) {
		var pieces = borderColor.split(' ')
		, len = pieces.length;
		
		if (len === 1) {
			this.borderTopColor = pieces[0];
			this.borderRightColor = pieces[0];
			this.borderBottomColor = pieces[0];
			this.borderLeftColor = pieces[0];
		} else if (len === 2) {
			this.borderTopColor = pieces[0];
			this.borderRightColor = pieces[1];
			this.borderBottomColor = pieces[0];
			this.borderLeftColor = pieces[1];
		} else if (len === 3) {
			this.borderTopColor = pieces[0];
			this.borderRightColor = pieces[1];
			this.borderBottomColor = pieces[2];
			this.borderLeftColor = pieces[1];
		} else {
			this.borderTopColor = pieces[0];
			this.borderRightColor = pieces[1];
			this.borderBottomColor = pieces[2];
			this.borderLeftColor = pieces[3];
		}
		
		return this.borderColor;
	}
});

/**
 * Allows the element to be moved using the mouse
 *
 * @type Boolean
 * @default false
**/
elementProto.draggable = false;

/**
 * Registers a "mousemove" event listener on the element
 *
 * @param {String|Function} callback
 * @returns {Element} this
**/
elementProto.mousemove = function(callback) {
	if (isString(callback)) callback = parseMethod(callback);
	if (isFunction(callback)) this.addEventListener('mousemove', callback);
	
	return this;
};

/**
 * Registers a "mouseenter" event listener on the element
 *
 * @param {String|Function} callback
 * @returns {Element} this
**/
elementProto.mouseenter = function(callback) {
	if (isString(callback)) callback = parseMethod(callback);
	if (isFunction(callback)) this.addEventListener('mouseenter', callback);
	
	return this;
};

/**
 * Registers a "mouseleave" event listener on the element
 *
 * @param {String|Function} callback
 * @returns {Element} this
**/
elementProto.mouseleave = function(callback) {
	if (isString(callback)) callback = parseMethod(callback);
	if (isFunction(callback)) this.addEventListener('mouseleave', callback);
	
	return this;
};

/**
 * Registers a "mousedown" event listener on the element
 *
 * @param {String|Function} callback
 * @returns {Element} this
**/
elementProto.mousedown = function(callback) {
	if (isString(callback)) callback = parseMethod(callback);
	if (isFunction(callback)) this.addEventListener('mousedown', callback);
	
	return this;
};

/**
 * Registers a "mouseup" event listener on the element
 *
 * @param {String|Function} callback
 * @returns {Element} this
**/
elementProto.mouseup = function(callback) {
	if (isString(callback)) callback = parseMethod(callback);
	if (isFunction(callback)) this.addEventListener('mouseup', callback);
	
	return this;
};

/**
 * Edits the element
 *
 * @param {Object} source
 * @returns {Element} this
**/
elementProto.edit = function(source) {
	return use(this, source);
};

/**
 * Returns the element's scene or null
 *
 * @returns {?Scene}
 * @todo (?)Check Layer contains Element and Scene contains Layer
**/
elementProto.getScene = function() {
	var layer = this.parentLayer;
	
	return is(layer, Layer) && is(layer.root, Scene) ? layer.root : null;
};

/**
 * Determines if the element is child of a container
 *
 * @param {ContainerElement} container
 * @returns {Boolean}
**/
elementProto.isChildOf = function(container) {
	var current = container;
	
	do {
		if (this.parentElement === current) return true;
		
		current = current.parentElement;
	} while (is(current, ContainerElement));
	
	return false;
};

/**
 * Scales the element
 *
 * @param {int} widthRatio
 * @param {int} heightRatio
 * @returns {Element} this
**/
elementProto.scale = function(widthRatio, heightRatio) {
	this.scaleX = widthRatio;
	this.scaleY = heightRatio;
	
	return this;
};

/**
 * Adds a class to the element's classList
 *
 * @param {String} item
 * @param {int} [orderID]
 * @returns {Element} this
 * @see ClassList#add
**/
elementProto.addClass = function(item, orderID) {
	this.classList.add(item, orderID);
	
	return this;
};

/**
 * Toggles a class
 *
 * @param {String} item
 * @param {int} [orderID]
 * @returns {Element} this
 * @see ClassList#toggle
**/
elementProto.toggleClass = function(item, orderID) {
	this.classList.toggle(item, orderID);
	
	return this;
};

/**
 * Removes a class from the element's classList
 *
 * @param {String} item
 * @returns {Element} this
 * @see OrderedList#remove
**/
elementProto.removeClass = function(item) {
	this.classList.remove(item);
	
	return this;
};

/**
 * Toggles a boolean property of the element
 *
 * @param {String} propertyKey
 * @returns {Element} this
**/
elementProto.toggle = function(propertyKey) {
	if (isBoolean(this[propertyKey])) {
		this[propertyKey] = !this[propertyKey];
	}
	
	return this;
};

/**
 * Applies transformation to the rendering context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {Element} this
**/
elementProto.applyTransform = function(ctx) {
	var r = isNumber(this.angle) ? deg2rad(this.angle % 360) : 0
	, scaleX = isNumber(this.scaleX) ? this.scaleX : 1
	, scaleY = isNumber(this.scaleY) ? this.scaleY : 1
	, tx, ty;
	
	if (this.flipX === true) scaleX *= -1;
	if (this.flipY === true) scaleY *= -1;
	
	if (r !== 0 || scaleX !== 1 || scaleY !== 1) {
		tx = this.screenX + this.renderWidth / 2;
		ty = this.screenY + this.renderHeight / 2;
		
		ctx.translate(tx, ty);
		
		if (r !== 0) ctx.rotate(r);
		if (scaleX !== 1 || scaleY !== 1) ctx.scale(scaleX, scaleY);
		
		ctx.translate(-tx, -ty);
	}
	
	return this;
};

/**
 * Applies a mask to the rendering context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {Element} this
**/
elementProto.applyMask = function(ctx) {
	var mask = $masks.get(this.mask);
	
	if (!isNull(mask)) {
		ctx.beginPath();
		
		mask(ctx, this);
		
		ctx.clip();
	}
	
	return this;
};

/**
 * Draws background and border if necessary
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {Element} this
 * @todo Implement lineDash
**/
elementProto.drawBox = function(ctx) {
	var x = this.screenX
	, y = this.screenY
	, w = this.renderWidth
	, h = this.renderHeight;
	
	if (this.background !== '') {
		ctx.fillStyle = this.background;
		ctx.fillRect(x, y, w, h);
	}
	
	if (this.borderTopWidth > 0) {
		ctx.beginPath();
		ctx.lineWidth = this.borderTopWidth;
		ctx.strokeStyle = this.borderTopColor;
		// ctx.setLineDash();
		ctx.moveTo(x, y);
		ctx.lineTo(x + w, y);
		ctx.stroke();
	}
	
	if (this.borderRightWidth > 0) {
		ctx.beginPath();
		ctx.lineWidth = this.borderRightWidth;
		ctx.strokeStyle = this.borderRightColor;
		// ctx.setLineDash();
		ctx.moveTo(x + w, y);
		ctx.lineTo(x + w, y + h);
		ctx.stroke();
	}
	
	if (this.borderBottomWidth > 0) {
		ctx.beginPath();
		ctx.lineWidth = this.borderBottomWidth;
		ctx.strokeStyle = this.borderBottomColor;
		// ctx.setLineDash();
		ctx.moveTo(x + w, y + h);
		ctx.lineTo(x, y + h);
		ctx.stroke();
	}
	
	if (this.borderLeftWidth > 0) {
		ctx.beginPath();
		ctx.lineWidth = this.borderLeftWidth;
		ctx.strokeStyle = this.borderLeftColor;
		// ctx.setLineDash();
		ctx.moveTo(x, y + h);
		ctx.lineTo(x, y);
		ctx.stroke();
	}
	
	return this;
};

/**
 * Updates the element (move by it's velocity)
 *
 * @returns {Element} this
**/
elementProto.update = function() {
	if (isNumber(this.x) && isNumber(this.vX)) {
		this.x += this.vX;

		if (isNumber(this.minX) && this.x < this.minX) this.x = this.minX;
		if (isNumber(this.maxX) && this.x > this.maxX) this.x = this.maxX;
	}

	if (isNumber(this.y) && isNumber(this.vY)) {
		this.y += this.vY;

		if (isNumber(this.minY) && this.y < this.minY) this.y = this.minY;
		if (isNumber(this.maxY) && this.y > this.maxY) this.y = this.maxY;
	}

	return this;
};

/**
 * Creates an animation task
 *
 * @param {Object} animData
 * @param {int} duration
 * @param {int} [delay]
 * @returns {Task}
 * @todo Handle colors and non-numeric values
**/
elementProto.animation = function(animData, duration, delay) {
	var initial = getProps(this, keys(animData))
	, fn = function(tick, elapsed) {
		var percent = range(0, 1, elapsed / (duration || 0));
		
		forIn(initial, function(propKey) {
			this[propKey] = initial[propKey] + (animData[propKey] - initial[propKey]) * percent;
		}, this);
		
		return percent === 1;
	};
	
	return new Task(fn, delay, this);
};

/**
 * If the element is in a scene, adds an animation task to the scene's queue
 *
 * @param {Object} animData
 * @param {int} duration
 * @param {int} [delay]
 * @returns {?Task}
**/
elementProto.animate = function(animData, duration, delay) {
	var scene = this.getScene()
	, task = null;
	
	if (is(scene, Scene)) {
		task = this.animation(animData, duration, delay);
		
		scene.queue.add(task, true);
	}
	
	return task;
};

/**
 * Returns a clone of the element
 *
 * @returns {Element}
**/
elementProto.clone = function() {
	var constructor = $elements.getInstantiatable(this.elementName)
	, element = null
	, elementUse, properties;
	
	if (!isNull(constructor)) {
		elementUse = stdClass();
		properties = this.getProps();
		
		forEach(properties, function(prop) {
			if (prop.value !== 'inherit') elementUse[prop.key] = prop.value;
		});
		
		element = new constructor(elementUse);
	}
	
	return element;
};

/**
 * Returns the string value of the element
 *
 * @returns {String}
**/
elementProto.toString = function() {
	return 'Chill Element[' + this.elementType + ']';
};

var elementClass = $classes.fromPrototype(
	elementProto,
	[ // ignored properties
	'elementType', 'elementName',
	'top', 'right', 'bottom', 'left',
	'screenX', 'screenY',
	'parentX', 'parentY',
	'renderWidth', 'renderHeight',
	'onFocus', 'dragState'
	]
);

$elements.addType(elementProto.elementName, Element, false, true);
$classes.set(elementProto.elementType, elementClass);

/**
 * Creates a new Layer
 *
 * @class Layer
 * @extends EventTarget
 * @param {Object} [layerUse]
 * @description todoc
 * @property {HTMLCanvasElement|HTMLElement} canvas
 * @property {CanvasRenderingContext2D} ctx
**/
function Layer(layerUse) {
	EventTarget.call(this);
	addLayerElementHandlers.call(this);
	
	this.uuid = generateUUID();
	this.canvas = createElement('canvas');
	this.ctx = this.canvas.getContext('2d');
	
	use(this, layerUse);
}

/** @lends Layer# **/
var layerProto = Layer.prototype = Object.create(EventTarget.prototype);
layerProto.constructor = Layer;

function addLayerElementHandlers() {
	var self = this
	, _containers = stdClass()
	, _body = new ContainerElement('body');
	
	/**
	 * The main ContainerElement of the Layer
	 *
	 * @alias Layer#body
	 * @type ContainerElement
	**/
	this.body = _containers.body = _body;
	
	function validateSelfElement(element) {
		return element.parentLayer === self && is(element.parentElement, ContainerElement) && self.contains(element);
	}
	
	/**
	 * Adds an Element to the Layer
	 *
	 * @alias Layer#add
	 * @param {Element} element
	 * @param {String} container
	 * @param {int} [orderID]
	 * @returns {Layer} this
	**/
	this.add = function(element, container, orderID) {
		if (is(element, Element)) {
			if (is(element, ContainerElement)) {
				if (has(_containers, element.id)) {
					warning('unable to add container#' + element.id + ', "' + element.id + '" already exists in layer#' + this.id);
					
					return this;
				}
				
				_containers[element.id] = element;
			}
			
			if (!has(_containers, container)) {
				if (!isUndefined(container)) warning('"' + container + '" does not exists in layer#' + this.id + ', use #body by default');
				container = 'body';
			}
			
			_containers[container].add(element, orderID);
			
			element.parentLayer = this;
			element.parentElement = _containers[container];
		} else {
			warning('only elements can be added to layer#' + this.id);
		}
		
		return this;
	};
	
	/**
	 * Gets an element by id
	 *
	 * @alias Layer#getElementByID
	 * @param {String} id
	 * @returns {?Element}
	 * @todo Break forEach when find element
	**/
	this.getElementByID = function(id) {
		var returnElement = null;
		
		this.eachElement(function(element) {
			if (element.id === id) returnElement = element;
		});
		
		return returnElement;
	};
	
	/**
	 * Checks if the Layer contains a specific Element
	 *
	 * @alias Layer#contains
	 * @param {Element} searchElement
	 * @returns {Boolean}
	**/
	this.contains = function(searchElement) {
		var returnValue = false;
		
		this.eachContainer(function(container) {
			if (container === searchElement || container.has(searchElement)) return returnValue = true;
		});
		
		return returnValue;
	};
	
	/**
	 * Removes an Element from the Layer
	 *
	 * @alias Layer#remove
	 * @param {Element} removeElement
	 * @returns {?Element} removed
	**/
	this.remove = function(removeElement) {
		var removed = null;
		
		if (removeElement === _body) {
			warning('cannot remove #body from layer#' + this.id);
		} else {
			this.eachContainer(function(container, containerKey) {
				if (container === removeElement) {
					delete _containers[containerKey];
					
					return removed = removeElement;
				} else if (container.has(removeElement)) {
					container.remove(removeElement);
					
					removeElement.parentLayer = null;
					removeElement.parentElement = null;
					
					return removed = removeElement;
				}
			});
		}
		
		return removed;
	};
	
	/**
	 * Creates a new Element and returns it
	 *
	 * @alias Layer#create
	 * @param {String} type
	 * @param {...*}
	 * @returns {?Element}
	**/
	this.create = function(type) {
		var element = null
		, constructor, args, abstractElement;
		
		if (startsWith(type, '#')) {
			type = type.slice(1);
			abstractElement = $abstracts.get(type);
			
			if (!isNull(abstractElement)) {
				element = abstractElement.instantiate();
				
				if (!isNull(element)) {
					element.edit(arguments[1]);
				} else {
					warning('Unable to instantiate AbstractElement. Element "' + abstractElement.type + '" is not instantiatable or does not exists');
				}
			} else {
				warning('AbstractElement "' + type + '" does not exist');
			}
		} else {
			constructor = $elements.getInstantiatable(type);
			
			if (!isNull(constructor)) {
				args = getArgs(arguments, 1);
				args.unshift(null);
				
				element = new (Function.prototype.bind.apply(constructor, args));
			} else {
				warning('Unable to instantiate Element. "' + type + '" is not instantiatable or does not exists');
			}
		}
		
		return element;
	};
	
	/**
	 * Combination of Layer#create and Layer#add
	 *
	 * @alias Layer#insert
	 * @param {String} elementType
	 * @param {Object|Array} [args]
	 * @param {String} [container]
	 * @param {int} [orderID]
	 * @returns {?Element}
	**/
	this.insert = function(elementType, args, container, orderID) {
		var element = isArray(args) ? this.create.apply(this, [elementType].concat(args)) : this.create(elementType, args);
		
		this.add(element, container, orderID);
		
		return element;
	};
	
	/**
	 * Sets the order position of the Element exactly before the reference
	 *
	 * @alias Layer#moveBefore
	 * @param {Element} element
	 * @param {Element} reference
	 * @returns {Layer} this
	**/
	this.moveBefore = function(element, reference) {
		if (validateSelfElement(element) && validateSelfElement(reference)) {
			element.parentElement.remove(element);
			reference.parentElement.addBefore(element, reference);
			element.parentElement = reference.parentElement;
		}
		
		return this;
	};
	
	/**
	 * Sets the order position of the Element exactly after the reference
	 *
	 * @alias Layer#moveAfter
	 * @param {Element} element
	 * @param {Element} reference
	 * @returns {Layer} this
	**/
	this.moveAfter = function(element, reference) {
		if (validateSelfElement(element) && validateSelfElement(reference)) {
			element.parentElement.remove(element);
			reference.parentElement.addAfter(element, reference);
			element.parentElement = reference.parentElement;
		}
		
		return this;
	};
	
	/**
	 * Returns a list of elements by the given query selector
	 *
	 * @alias Layer#select
	 * @param {String} query
	 * @param {ContainerElement} [context = this.body]
	 * @returns {Array}
	 * @todo Implement this method
	**/
	this.select = function(query, context) {
		return [];
	};
	
	/**
	 * Executes a provided callback function recursively once per Element in ascending order
	 *
	 * @alias Layer#eachElement
	 * @param {Function} callback
	 * @param {*} [thisArg]
	 * @returns {Layer} this
	**/
	this.eachElement = function(callback, thisArg) {
		callback.call(thisArg, _body);
		_body.each(callback, thisArg);
		
		return this;
	};
	
	/**
	 * Executes a provided callback function once per ContainerElement
	 *
	 * @alias Layer#eachContainer
	 * @param {Function} callback
	 * @param {*} [thisArg]
	 * @returns {Layer} this
	**/
	this.eachContainer = function(callback, thisArg) {
		var key;
		
		for (key in _containers) {
			if (!isUndefined(callback.call(thisArg, _containers[key], key))) break;
		}
		
		return this;
	};
	
	return this;
}

/**
 * The scene of the layer
 *
 * @type Scene
 * @default null
 * @readonly
**/
layerProto.root = null;

/**
 * The id of the layer
 *
 * @type String
 * @default null
**/
layerProto.id = null;

/**
 * The uuid of the layer
 *
 * @type String
 * @default null
 * @readonly
**/
layerProto.uuid = null;

Object.defineProperty(layerProto, 'x', {
	/**
	 * The x position of the canvas
	 *
	 * @name Layer#x
	 * @type Number
	 * @default 0
	 **/
	get: function() {
		return getInt(this.canvas.style.left) || 0;
	},
	set: function(xVal) {
		return this.canvas.style.left = xVal + 'px';
	}
});

Object.defineProperty(layerProto, 'y', {
	/**
	 * The y position of the canvas
	 *
	 * @name Layer#y
	 * @type Number
	 * @default 0
	 **/
	get: function() {
		return getInt(this.canvas.style.top) || 0;
	},
	set: function(yVal) {
		return this.canvas.style.top = yVal + 'px';
	}
});

Object.defineProperty(layerProto, 'width', {
	/**
	 * The width of the layer
	 *
	 * @name Layer#width
	 * @type Number
	 **/
	get: function() {
		return this.canvas.width;
	},
	set: function(widthVal) {
		return this.canvas.width = widthVal;
	}
});

Object.defineProperty(layerProto, 'height', {
	/**
	 * The height of the layer
	 *
	 * @name Layer#height
	 * @type Number
	 **/
	get: function() {
		return this.canvas.height;
	},
	set: function(heightVal) {
		return this.canvas.height = heightVal;
	}
});

Object.defineProperty(layerProto, 'zIndex', {
	/**
	 * The z-index of the layer's canvas
	 *
	 * @name Layer#zIndex
	 * @type Number
	 * @default 0
	 **/
    get: function() {
		return +this.canvas.style.zIndex;
	},
	set: function(val) {
		return +(this.canvas.style.zIndex = val);
	}
});

Object.defineProperty(layerProto, 'background', {
	/**
	 * The background of the layer's canvas
	 *
	 * @name Layer#background
	 * @type String
	 * @default 'rgba(0, 0, 0, 0)'
	 **/
	get: function() {
		return this.canvas.style.background;
	},
	set: function(bgVal) {
		return this.canvas.style.background = bgVal;
	}
});

Object.defineProperty(layerProto, 'opacity', {
	/**
	 * The alpha level (globalAlpha) of the layer's canvas element
	 *
	 * @name Layer#opacity
	 * @type Number
	 * @default 1
	 **/
	get: function() {
		return this.ctx.globalAlpha;
	},
	set: function(opacity) {
		return this.ctx.globalAlpha = opacity;
	}
});

/**
 * If the renderer should update the layer at every tick
 *
 * @type Boolean
 * @default true
**/
layerProto.live = true;

/**
 * If the layer should be rendered
 *
 * @type Boolean
 * @default true
**/
layerProto.visible = true;

/**
 * Renders the entries of the layer
 *
 * @method
 * @name Layer#render
 * @returns {Layer} this
**/
layerProto.render = (function() {
	function draw(element) {
		var ctx = this.ctx;
		
		if (element.opacity > 0) {
			ctx.save();
			
			ctx.globalAlpha = element.opacity;
			
			element.applyTransform(ctx);
			element.applyMask(ctx);
			element.drawBox(ctx);
			element.draw(ctx);
			
			ctx.restore();
		}
	}
	
	return function render() {
		if (this.visible === true) {
			this.clear();
			this.eachElement(draw, this);
		}
		
		return this;
	};
}());

/**
 * Clears the entire layer
 *
 * @returns {Layer} this
**/
layerProto.clear = function() {
	this.ctx.clearRect(0, 0, this.width, this.height);
	
	return this;
};

/**
 * Gets ImageData from the layer
 *
 * @param {int} x
 * @param {int} y
 * @returns {Uint8ClampedArray}
**/
layerProto.getPixel = function(x, y) {
	return this.ctx.getImageData(x, y, 1, 1).data;
};

/**
 * Scrolls the layer, sets it's x and y to the given values
 *
 * @param {int} x
 * @param {int} y
 * @returns {Layer} this
**/
layerProto.scrollTo = function(x, y) {
	this.x = x;
	this.y = y;
	
	return this;
};

/**
 * Scrolls the layer, edits it's x and y by the given values
 *
 * @param {int} x
 * @param {int} y
 * @returns {Layer} this
**/
layerProto.scrollBy = function(x, y) {
	this.x += x;
	this.y += y;
	
	return this;
};

/**
 * Returns the string value of the layer
 *
 * @returns {String}
**/
layerProto.toString = function() {
	return 'Chill Layer';
};

/**
 * Creates a new Scene
 *
 * @class Scene
 * @extends EventTarget
 * @param {HTMLElement} wrapper
 * @description todoc
 * @property {HTMLElement} wrapper
 * @property {Screen} screen
 * @property {Cursor} cursor
 * @property {PublicStorage} settings
 * @property {Loader} loader
 * @property {Queue} queue
**/
function Scene(wrapper) {
	EventTarget.call(this);
	Storage.call(this);
	
	this.uuid = generateUUID();
	this.wrapper = wrapper;
	this.readyState = 'pending';
	
	this.screen = new Screen();
	this.cursor = new Cursor('mouse');
	this.settings = new PublicStorage();
	this.loader = new Loader();
	this.queue = new Queue();
	
	addSceneLayerHandlerMethods.call(this);
	addSceneEventListeners.call(this, wrapper);
	addSceneAnimationFrameHandlers.call(this);
	addSceneCollisionDetectionHandlers.call(this);
	initScene.call(this);
}

/** @lends Scene# **/
var sceneProto = Scene.prototype = Object.create(EventTarget.prototype);
sceneProto.constructor = Scene;

function addSceneLayerHandlerMethods() {
	var _layers = new OrderedList();
	
	/**
	 * Creates a new Layer and returns it
	 *
	 * @alias Scene#createLayer
	 * @param {Object} [layerUse]
	 * @returns {Layer}
	**/
	this.createLayer = function(layerUse) {
		if (!isObject(layerUse)) layerUse = stdClass();
		
		return new Layer(addDefault(layerUse, {
			width: this.screen.width,
			height: this.screen.height
		}));
	};
	
	/**
	 * Adds a Layer to the Scene
	 *
	 * @alias Scene#addLayer
	 * @param {Layer} layer
	 * @param {int} [orderID]
	 * @returns {Scene} this
	**/
	this.addLayer = function(layer, orderID) {
		_layers.add(layer, orderID);
		layer.root = this;
		this.wrapper.appendChild(layer.canvas);
		
		return this;
	};
	
	/**
	 * Combination of Scene#createLayer and Scene#addLayer
	 *
	 * @alias Scene#insertLayer
	 * @param {Object} [layerUse]
	 * @param {int} [orderID]
	 * @returns {Layer}
	**/
	this.insertLayer = function(layerUse, orderID) {
		var layer = this.createLayer(layerUse);
		
		this.addLayer(layer, orderID);
		
		return layer;
	};
	
	/**
	 * Removes a Layer from the Scene
	 *
	 * @alias Scene#removeLayer
	 * @param {Layer} layer
	 * @returns {Scene} this
	**/
	this.removeLayer = function(layer) {
		_layers.remove(layer);
		layer.root = null;
		this.wrapper.removeChild(layer.canvas);
		
		return this;
	};
	
	/**
	 * Gets a Layer by id
	 *
	 * @alias Scene#getLayer
	 * @param {String} layerId
	 * @returns {?Layer}
	**/
	this.getLayer = function(layerId) {
		var returnValue = null;
		
		_layers.each(function(layer) {
			if (layer.id === layerId) {
				returnValue = layer;
				
				return true;
			}
			
			return false;
		});
		
		return returnValue;
	};
	
	/**
	 * Executes a provided callback function once per Layer in ascending order
	 *
	 * @alias Scene#eachLayer
	 * @param {Function} callback
	 * @returns {Scene} this
	**/
	this.eachLayer = function(callback) {
		_layers.each(callback);
		
		return this;
	};
	
	/**
	 * Executes a provided callback function recursively once per Element in ascending order
	 *
	 * @alias Scene#eachElement
	 * @param {Function} callback
	 * @returns {Scene} this
	**/
	this.eachElement = function(callback) {
		_layers.each(function(layer) {
			layer.eachElement(callback);
		});
		
		return this;
	};
	
	return this;
}

function addSceneEventListeners(wrapper) {
	var scene = this
	, _dragged = []
	, _focused = []
	, doc = window.document;
	
	/**
	 * Returns an Array of currently dragged Elements
	 *
	 * @alias Scene#getDraggedElements
	 * @returns {Array}
	**/
	this.getDraggedElements = function() {
		return _dragged;
	};
	
	/**
	 * Returns an Array of currently hovered Elements
	 *
	 * @alias Scene#getFocusedElements
	 * @returns {Array}
	**/
	this.getFocusedElements = function() {
		return _focused;
	};
	
	/**
	 * Updates the list of focused elements, emits the "mousemove", "mouseenter" and "mouseleave" events if necessary
	 *
	 * @alias Scene#updateFocusedElements
	 * @param {MouseEvent} e
	 * @returns {Scene} this
	 * @todo Break forEach if the propagation is stopped
	 * @todo Consider parent layer offset on collision check
	 * @todo Consider element rotation and scale on collision check
	**/
	this.updateFocusedElements = function(e) {
		var temp = []
		, cx = this.cursor.x
		, cy = this.cursor.y;
		
		if (this.cursor.onScreen) {
			scene.eachElement(function(element) {
				var contains, event;
				
				if (e.propagationStopped === true) return undefined;
				
				contains = inArray(_focused, element);
				
				e.target = element;
				element.dispatchEvent(e.type, e);
				
				if (
					element.screenX <= cx &&
					element.screenXE >= cx &&
					element.screenY <= cy &&
					element.screenYE >= cy
				) {
					temp.push(element);
					
					if (!contains) {
						event = new MouseEvent('mouseenter', e.x, e.y, null);
						event.target = element;
						element.dispatchEvent(event.type, event);
					}
				} else if (contains) {
					event = new MouseEvent('mouseleave', e.x, e.y, null);
					event.target = element;
					element.dispatchEvent(event.type, event);
				}
			});
		}
		
		empty(_focused);
		_focused.push.apply(_focused, temp);
		
		return this;
	};
	
	wrapper.addEventListener('mousemove', function(e) {
		var x = e.layerX
		, y = e.layerY
		, event = new MouseEvent('mousemove', x, y, null);
		
		scene.cursor.set(x, y);
		scene.cursor.onScreen = true;
		
		event.target = scene;
		event.originalEvent = e;
		
		scene.dispatchEvent(e.type, event);
		scene.updateFocusedElements(event);
	});
	
	wrapper.addEventListener('mouseenter', function(e) {
		scene.cursor.set(e.layerX, e.layerY);
		scene.cursor.onScreen = true;
		
		scene.dispatchEvent(e.type, new MouseEvent(e.type, e.layerX, e.layerY, null));
	});
	
	wrapper.addEventListener('mouseleave', function(e) {
		scene.cursor.set(e.layerX, e.layerY);
		scene.cursor.onScreen = false;
		
		forEach(_focused, function(element) {
			var event = new MouseEvent('mouseleave', scene.cursor.x, scene.cursor.y, null);
			event.target = element;
			element.dispatchEvent(event.type, event);
		});
		
		empty(_focused);
		
		scene.dispatchEvent(e.type, new MouseEvent(e.type, e.layerX, e.layerY, null));
	});
	
	wrapper.addEventListener('mousedown', function(e) {
		var x = e.layerX
		, y = e.layerY
		, event = new MouseEvent('mousedown', x, y, null);
		
		scene.cursor.set(x, y);
		scene.cursor.onScreen = true;
		
		event.target = scene;
		event.originalEvent = e;
		
		switch(e.button) {
			case 0: event.button = 'left'; break;
			case 1: event.button = 'middle'; break;
			case 2: event.button = 'right'; break;
		}
		
		scene.dispatchEvent(e.type, event);
		applyEach(_focused, EventTarget.prototype.dispatchEvent, e.type, event);
	});
	
	wrapper.addEventListener('mouseup', function(e) {
		var x = e.layerX
		, y = e.layerY
		, event = new MouseEvent('mouseup', x, y, null);
		
		scene.cursor.set(x, y);
		scene.cursor.onScreen = true;
		
		event.target = scene;
		event.originalEvent = e;
		
		switch(e.button) {
			case 0: event.button = 'left'; break;
			case 1: event.button = 'middle'; break;
			case 2: event.button = 'right'; break;
		}
		
		scene.dispatchEvent(e.type, event);
		applyEach(_focused, EventTarget.prototype.dispatchEvent, e.type, event);
	});
	
	doc.addEventListener('keypress', function(e) {
		var key = e.keyCode || e.which
		, event = new KeyboardEvent('keypress', key);
		
		event.target = scene;
		event.originalEvent = e;
		
		scene.dispatchEvent('keypress', event);
	});
	
	doc.addEventListener('keydown', function(e) {
		var key = e.keyCode || e.which
		, event = new KeyboardEvent('keydown', key);
		
		event.target = scene;
		event.originalEvent = e;
		
		scene.dispatchEvent('keydown', event);
	});
	
	doc.addEventListener('keyup', function(e) {
		var key = e.keyCode || e.which
		, event = new KeyboardEvent('keyup', key);
		
		event.target = scene;
		event.originalEvent = e;
		
		scene.dispatchEvent('keyup', event);
	});
	
	return this;
}

function addSceneAnimationFrameHandlers() {
	var animFrame = null
	, tick = (function animationFrame() {
		var now = new Date();
		
		animFrame = window.requestAnimationFrame(tick);
		
		this.fps = now - this.lastTick;
		this.lastTick = now;
		++this.tickCount;
		
		this.update();
		this.queue.perform();
		this.emit('tick');
		this.reflow();
		this.checkCollisions();
		this.paint();
	}).bind(this);
	
	/**
	 * Starts the animation of the Scene
	 *
	 * @alias Scene#start
	 * @returns {Scene} this
	 * @todo Start scene on browser tab focus
	 * @todo "Play" queue tasks
	**/
	this.start = function() {
		this.stop();
		
		this.run = true;
		
		tick();
		
		return this;
	};
	
	/**
	 * Stops the animation of the Scene
	 *
	 * @alias Scene#stop
	 * @returns {Scene} this
	 * @todo Stop scene on browser tab blur
	 * @todo "Pause" queue tasks
	**/
	this.stop = function() {
		window.cancelAnimationFrame(animFrame);
		
		this.run = false;
		
		return this;
	};
	
	return this;
}

function addSceneCollisionDetectionHandlers() {
	var _watchList = [];
	
	/**
	 * Creates a new CollisionDetector
	 *
	 * @class CollisionDetector
	 * @param {Element} element
	 * @param {Element[]|Element} targets
	 * @param {Boolean} [autoRemove = true]
	 * @description todoc
	**/
	function CollisionDetector(element, targets, autoRemove) {
		this.element = element;
		this.targets = toArray(targets);
		this.autoRemove = autoRemove !== false;
	}
	
	/** @lends CollisionDetector# **/
	var collisionDetectorProto = CollisionDetector.prototype = stdClass();
	collisionDetectorProto.constructor = CollisionDetector;

	/**
	 * Adds one or more targets to check
	 *
	 * @param {Element[]|Element} targets
	 * @returns {CollisionDetector} this
	**/
	collisionDetectorProto.addTarget = function(targets) {
		this.targets = this.targets.concat(targets);
		
		return this;
	};
	
	/**
	 * Removes a target
	 *
	 * @param {Element} target
	 * @returns {CollisionDetector} this
	**/
	collisionDetectorProto.removeTarget = function(target) {
		remove(this.targets, target);
		
		return this;
	};
	
	/**
	 * Checks for collision, emits "collision" event if the elements collides
	 *
	 * @returns {CollisionDetector} this
	**/
	collisionDetectorProto.check = function() {
		var elem = this.element
		, i = 0
		, il = this.targets.length
		, target, event;
		
		for (; i < il; i++) {
			target = this.targets[i];
			
			if (!(elem.screenX > target.screenXE || elem.screenXE < target.screenX || elem.screenY > target.screenYE || elem.screenYE < target.screenY)) {
				event = new CollisionEvent('collision', target);
				event.target = elem;
				
				elem.emit(event.type, event);
				
				if (this.autoRemove === true) {
					this.removeTarget(target);
					
					--i;
					--il;
				}
			}
		}
		
		return this;
	};
	
	/**
	 * Creates a new CollisionDetector and adds it to the watchList
	 *
	 * @alias Scene#watch
	 * @param {Element} element
	 * @param {*} targets
	 * @param {Boolean} [autoRemove]
	 * @returns {CollisionDetector}
	**/
	this.watch = function(element, targets, autoRemove) {
		var detector = new CollisionDetector(element, targets, autoRemove);
		
		_watchList.push(detector);
		
		return detector;
	};
	
	/**
	 * Removes the given CollisionDetector from the watchList
	 *
	 * @alias Scene#neglect
	 * @param {CollisionDetector} detector
	 * @returns {Scene} this
	**/
	this.neglect = function(detector) {
		remove(_watchList, detector);
		
		return this;
	};
	
	/**
	 * Calls each CollisionDetector check method from the watchList
	 *
	 * @alias Scene#checkCollisions
	 * @returns {Scene} this
	**/
	this.checkCollisions = function() {
		callEach(_watchList, collisionDetectorProto.check);
		
		return this;
	};
	
	return this;
}

function initScene() {
	this.settings.edit({
		volume: 40,
		maxDraggedEntries: 1,
		renderDepthLimit: 0
	});
	
	return this;
}

/**
 * The uuid of the scene
 *
 * @type String
 * @default null
 * @readonly
**/
sceneProto.uuid = null;

/**
 * The loading state of the scene
 *
 * @type String
 * @default null
 * @readonly
**/
sceneProto.readyState = null;

/**
 * The running status of the scene
 *
 * @type Boolean
 * @default false
 * @readonly
**/
sceneProto.run = false;

/**
 * The last tick of the scene
 *
 * @type int
 * @default 0
 * @readonly
**/
sceneProto.lastTick = 0;

/**
 * The number of ticks
 *
 * @type int
 * @default 0
 * @readonly
**/
sceneProto.tickCount = 0;

/**
 * The fps of the scene
 *
 * @type int
 * @default 0
 * @readonly
**/
sceneProto.fps = 0;

/**
 * Includes external data
 *
 * @param {String|Object} data
 * @param {Function} callback
 * @returns {Scene} this
 * @todo XML support
**/
sceneProto.include = function(data, callback) {
	var scene = this;
	
	if (isString(data)) {
		HTTP.getJSON(data, function(jsonData) {
			use(scene, jsonData);
			
			if (isFunction(callback)) callback(scene);
		});
	} else {
		if (isObject(data)) use(scene, data);
		if (isFunction(callback)) callback(scene);
	}
	
	return scene;
};

/**
 * Loads components
 *
 * @param {String} path
 * @param {Object} components
 * @returns {Scene} this
**/
sceneProto.preload = function(path, components) {
	var scene = this
	, assetsLoader = $assets.getLoader()
	, onLoad = function() {
		scene.emit('preloadComplete');
		// assetsLoader.off('complete', onLoad);
		assetsLoader.reset();
	};
	
	assetsLoader.on('complete', onLoad);
	
	forIn(components, function(type, items) {
		items = toArray(items);
		
		forEach(items, function(item) {
			var parts = item.split(' as ')
			, source = parts[0].replace(/^@/, path)
			, alias = parts[1]
			, loadItem = assetsLoader.add(type, source, alias);
			
			if (!isNull(loadItem)) $assets.addItem(type, loadItem, source, alias);
		}, this);
	}, this);
	
	assetsLoader.start();
	
	return this;
};

/**
 * Plays an audio
 *
 * @param {Audio|String} audio
 * @param {int} [volume]
 * @param {Boolean} [reset]
 * @param {Function} [callback]
 * @returns {Scene} this
**/
sceneProto.playAudio = function(audio, volume, reset, callback) {
	if (isString(audio) && startsWith(audio, '#')) {
		audio = $assets.getByID(Loader.AUDIO, audio.slice(1));
	}
	
	if (is(audio, Audio)) {
		if (isUndefined(volume)) volume = this.settings.get('volume');
		
		playAudio(audio, volume, reset, callback);
	}
	
	return this;
};

/**
 * Sets the size of the scene
 *
 * @param {int} width
 * @param {int} height
 * @returns {Scene} this
**/
sceneProto.resize = function(width, height) {
	this.setWidth(width);
	this.setHeight(height);
	
	return this;
};

/**
 * Sets the width of the scene
 *
 * @param {int} width
 * @returns {Scene} this
**/
sceneProto.setWidth = function(width) {
	this.screen.width = width;
	this.wrapper.style.width = width + 'px';
	
	return this;
};

/**
 * Sets the height of the scene
 *
 * @param {int} height
 * @returns {Scene} this
**/
sceneProto.setHeight = function(height) {
	this.screen.height = height;
	this.wrapper.style.height = height + 'px';
	
	return this;
};

/**
 * Adds a task to the queue
 *
 * @param {Function} listener
 * @param {int} [delay]
 * @param {Object} [thisArg]
 * @returns {Task}
**/
sceneProto.addTask = function(listener, delay, thisArg) {
	var task = new Task(listener, delay, thisArg);
	
	this.queue.add(task);
	
	return task;
};

/**
 * Removes a task from the queue
 *
 * @param {Task} task
 * @returns {Scene} this
**/
sceneProto.removeTask = function(task) {
	this.queue.remove(task);

	return this;
};

/**
 * Adds a task to the queue
 *
 * @param {Function} listener
 * @param {int} delay
 * @param {Object} [thisArg]
 * @returns {Scene} this
**/
sceneProto.later = function(listener, delay, thisArg) {
	this.queue.add(new Task(function() {
		listener.apply(this, getArgs(arguments));
		
		return true;
	}, delay, thisArg));

	return this;
};

/**
 * Adds a task to the queue
 *
 * @param {Function} listener
 * @param {int} times
 * @param {int} [delay]
 * @param {Object} [thisArg]
 * @returns {Scene} this
**/
sceneProto.repeat = function(listener, times, delay, thisArg) {
	var n = 0; // memory leak
	
	this.queue.add(new Task(function() {
		listener.apply(this, getArgs(arguments));
		
		return ++n >= times;
	}, delay, thisArg));

	return this;
};

/**
 * Determines if a given element is on the screen
 *
 * @param {Element} element
 * @param {Boolean} [partly = true]
 * @returns {Boolean}
 * @todo Consider parent layer offset on collision check
 * @todo Consider element rotation and scale on collision check
**/
sceneProto.onScreen = function(element, partly) {
	var x = this.screen.pos.x
	, y = this.screen.pos.y
	, xe = x + this.screen.width
	, ye = y + this.screen.height;
	
	return partly === false ?
		element.screenX >= x &&
		element.screenY >= y &&
		element.screenXE <= xe &&
		element.screenYE <= ye
	:
		!(
			x > element.screenXE ||
			xe < element.screenX ||
			y > element.screenYE ||
			ye < element.screenY
		);
};

/**
 * Adds a plugin to the scene
 *
 * @param {String} pluginID
 * @param {Object} [pluginConfig]
 * @returns {Scene} this
**/
sceneProto.addPlugin = function(pluginID, pluginConfig) {
	var plugin = $plugins.get(pluginID)
	, config;
	
	if (!isNull(plugin)) {
		if (!(pluginID in this)) {
			config = Object.create(plugin.config);
			if (isObject(pluginConfig)) assign(config, pluginConfig);
			
			this[pluginID] = plugin.constructor.call(this, config);
			this[pluginID].config = config;
		} else {
			warning('cannot add plugin "' + pluginID + '", already exists in scene');
		}
	} else {
		warning('cannot add plugin "' + pluginID + '", plugin does not exists');
	}
	
	return this;
};

/**
 * Creates an AbstractElement
 *
 * @method
 * @name Scene#createAbstractElement
 * @param {String} elemID
 * @param {String} elementType
 * @param {Object} [elementUse]
 * @param {Array} [args]
 * @returns {Scene} this
**/
sceneProto.createAbstractElement = $methods.createAbstractElement;

/**
 * Creates a new class
 *
 * @method
 * @name Scene#createClass
 * @param {String} className
 * @param {Object} classData
 * @returns {Scene} this
**/
sceneProto.createClass = $methods.createClass;

/**
 * Updates each element
 *
 * @method
 * @name Scene#update
 * @returns {Scene} this
**/
sceneProto.update = (function() {
	function updateEach(element) {
		element.update();
	}
	
	return function update() {
		this.eachElement(updateEach);
		
		return this;
	};
}());

/**
 * Determines the elements position and size
 *
 * @method
 * @name Scene#reflow
 * @returns {Scene} this
**/
sceneProto.reflow = (function() {
	function eachLayer(layer) {
		placeElement(layer.body, 0, 0, layer.width, layer.height, 0, 0, 0, 0);
	}
	
	function placeElement(element, parentX, parentY, parentWidth, parentHeight, t, l, r, b) {
		var top, left, right, bottom, x, y
		
		, minW = element.minWidth
		, minH = element.minHeight
		, maxW = element.maxWidth
		, maxH = element.maxHeight
		
		, marginTop = parseValue(element.marginTop, parentHeight)
		, marginRight = parseValue(element.marginRight, parentWidth)
		, marginBottom = parseValue(element.marginBottom, parentHeight)
		, marginLeft = parseValue(element.marginLeft, parentWidth)
		
		, offsetX, offsetY
		
		, w = element.width
		, h = element.height
		
		, flow = element.flow;
		
		
		if (flow === 'horizontal') {
			x = r;
			y = t;
		} else if (flow === 'vertical') {
			x = l;
			y = b;
		} else {
			x = parseValue(element.x, parentWidth) + parentX;
			y = parseValue(element.y, parentHeight) + parentY;
		}
		
		x += marginLeft;
		y += marginTop;
		
		switch(w) {
			case 'fit': w = parentWidth - (x - parentX); break;
			case 'auto': w = isFunction(element.measureWidth) ? element.measureWidth() : 0; break;
			
			default: w = parseValue(w, parentWidth);
		}
		
		switch(h) {
			case 'fit': h = parentHeight - (y - parentY); break;
			case 'auto': h = isFunction(element.measureHeight) ? element.measureHeight() : 0; break;
			
			default: h = parseValue(h, parentHeight);
		}
		
		if (minW !== 'none') w = Math.max(w, parseValue(minW, parentWidth));
		if (maxW !== 'none') w = Math.min(w, parseValue(maxW, parentWidth));
		if (minH !== 'none') h = Math.max(h, parseValue(minH, parentWidth));
		if (maxH !== 'none') h = Math.min(h, parseValue(maxH, parentWidth));
		
		if (flow === 'vertical') {
			switch(element.horizontalAlign) {
				case 'center': x = l + r / 2 - w / 2; break;
				case 'right': x = r - w; break;
			}
		}
		
		left = x;
		right = x + w + marginRight;
		
		if (flow === 'horizontal') {
			switch(element.verticalAlign) {
				case 'middle': y = t + b / 2 - h / 2; break;
				case 'bottom': y = b - h; break;
			}
		}
		
		top = y;
		bottom = y + h + marginBottom;
		
		if (element.wrap === true) {
			if (flow === 'horizontal') {
				if (right > parentX + parentWidth) {
					x = parentX + marginLeft;
					y = b + marginTop;
					
					top = y;
					right = x + w + marginRight;
					bottom = y + h + marginBottom;
					left = x;
				}
			} else if (flow === 'vertical') {
				if (bottom > parentY + parentHeight) {
					x = r + marginLeft;
					y = parentY + marginTop;
					
					top = y;
					right = x + w + marginRight;
					bottom = y + h + marginBottom;
					left = x;
				}
			}
		}
		
		if (is(element, ContainerElement)) {
			bottom = top;
			right = left;
			
			element.each(function(child) {
				var last = placeElement(child, x, y, w, h, top, left, right, bottom);
				
				if (last.flow === 'horizontal' || last.flow === 'vertical') {
					top = range(parentY, top, last.top);
					right = Math.max(right, last.right);
					bottom = Math.max(bottom, last.bottom);
					left = range(parentX, left, last.left);
				}
			}, null, false);
		}
		
		element.top = top;
		element.right = right;
		element.bottom = bottom;
		element.left = left;
		
		offsetX = parseValue(element.offsetX, w);
		offsetY = parseValue(element.offsetY, h);
		
		if (isNumber(offsetX)) x += offsetX;
		if (isNumber(offsetY)) y += offsetY;
		
		element.screenX = x;
		element.screenY = y;
		
		element.parentX = x - parentX;
		element.parentY = y - parentY;
		
		element.renderWidth = w;
		element.renderHeight = h;
		
		return element;
	}
	
	function parseValue(val, relativeTo) {
		var num;
		
		if (isNumber(val)) return val;
		
		val = String(val);
		
		if (!isNumber(relativeTo)) return 0;
		
		switch(val) {
			case 'left': return 0;
			case 'center': return relativeTo / 2;
			case 'right': return relativeTo;
			
			case 'stick left': return relativeTo;
			case 'stick center': return relativeTo * 1.5;
			case 'stick right': return relativeTo * 2;
		}
		
		num = findFirstRegX(numRegX, val);
		
		if (isNull(num)) return 0;
		
		num = Number(num);
		
		if (startsWith(val, 'stick')) {
			if (endsWith(val, '%')) return relativeTo + relativeTo * num / 100;
			
			return relativeTo + num;
		} else {
			if (endsWith(val, '%')) return num * relativeTo / 100;
			
			return num;
		}
	}
	
	return function reflow() {
		this.eachLayer(eachLayer);
		
		return this;
	};
}());

/**
 * Displays the elements
 *
 * @method
 * @name Scene#paint
 * @returns {Scene} this
**/
sceneProto.paint = (function() {
	function render(layer) {
		if (layer.live) layer.render();
	}
	
	return function paint() {
		this.eachLayer(render);
		
		return this;
	};
}());

/**
 * Returns the string value of the scene
 *
 * @returns {String}
**/
sceneProto.toString = function() {
	return 'Chill Scene';
};

/**
 * Creates a new AbstractElement
 *
 * @class AbstractElement
 * @param {String} [type]
 * @param {Object} [use]
 * @param {Array} [args]
 * @description todoc
**/
function AbstractElement(type, use, args) {
	this.type = type;
	this.args = isUndefined(args) ? [] : Array.prototype.concat(args);
	
	if (isObject(use)) this.use = use;
}

/** @lends AbstractElement# **/
var abstractElementProto = AbstractElement.prototype = stdClass();
abstractElementProto.constructor = AbstractElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'AbstractElement'
 * @readonly
**/
abstractElementProto.elementType = 'AbstractElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Abstract'
 * @readonly
**/
abstractElementProto.elementName = 'Abstract';

/**
 * The type of the instance
 *
 * @type String
 * @default null
**/
abstractElementProto.type = null;

/**
 * The default "use"-data of the instance
 *
 * @type Object
 * @default null
**/
abstractElementProto.use = null;

/**
 * The constructor arguments of the instance
 *
 * @type Array
 * @default null
**/
abstractElementProto.args = null;

/**
 * Creates a new element
 *
 * @returns {?Element}
**/
abstractElementProto.instantiate = function() {
	var constructor = $elements.getInstantiatable(this.type)
	, element = null;
	
	if (!isNull(constructor)) {
		element = new (Function.prototype.bind.apply(constructor, this.args));
		element.edit(this.use);
	}
	
	return element;
};

$elements.addType(abstractElementProto.elementName, AbstractElement, false, false);

/**
 * Creates a new ContainerElement
 *
 * @class ContainerElement
 * @extends Element
 * @param {String} id
 * @param {Object} [elementUse]
 * @description todoc
**/
function ContainerElement(id, elementUse) {
	Element.call(this);
	privateContainerElement.call(this);
	
	this.id = String(id);
	
	this.classList.add(containerElementProto.elementType, 1);
	
	this.edit(elementUse);
}

/** @lends ContainerElement# **/
var containerElementProto = ContainerElement.prototype = Object.create(Element.prototype);
containerElementProto.constructor = ContainerElement;

function privateContainerElement() {
	var _elements = new OrderedList();
	
	/**
	 * Adds an element to the ContainerElement
	 *
	 * @alias ContainerElement#add
	 * @param {Element} element
	 * @param {int} [orderID]
	 * @returns {ContainerElement} this
	**/
	this.add = function(element, orderID) {
		_elements.add(element, orderID);
		
		return this;
	};
	
	/**
	 * Adds an element to the ContainerElement exactly before the given reference element
	 *
	 * @alias ContainerElement#addBefore
	 * @param {Element} element
	 * @param {Element} reference
	 * @returns {ContainerElement} this
	**/
	this.addBefore = function(element, reference) {
		_elements.addBefore(element, reference);
		
		return this;
	};
	
	/**
	 * Adds an element to the ContainerElement exactly after the given reference element
	 *
	 * @alias ContainerElement#addAfter
	 * @param {Element} element
	 * @param {Element} reference
	 * @returns {ContainerElement} this
	**/
	this.addAfter = function(element, reference) {
		_elements.addAfter(element, reference);
		
		return this;
	};
	
	/**
	 * Checks if the ContainerElement contains a specific element
	 *
	 * @alias ContainerElement#has
	 * @param {Element} searchElement
	 * @returns {Boolean}
	**/
	this.has = function(searchElement) {
		return _elements.has(searchElement);
	};
	
	/**
	 * Removes an element from the ContainerElement
	 *
	 * @alias ContainerElement#remove
	 * @param {Element} element
	 * @returns {?Element} removed
	**/
	this.remove = function(element) {
		return _elements.remove(element);
	};
	
	/**
	 * Removes all elements from the ContainerElement
	 *
	 * @alias ContainerElement#clear
	 * @returns {ContainerElement} this
	**/
	this.clear = function() {
		_elements.clear();
		
		return this;
	};
	
	/**
	 * Returns the number of elements in the ContainerElement
	 *
	 * @alias ContainerElement#count
	 * @param {Boolean} recursive
	 * @returns {int}
	**/
	this.count = function(recursive) {
		var count = _elements.count;
		
		if (recursive === true) {
			this.each(function(element) {
				if (is(element, ContainerElement)) count += element.count(recursive);
			});
		}
		
		return count;
	};
	
	/**
	 * Returns an array of all the elements in the ContainerElement
	 *
	 * @alias ContainerElement#getElements
	 * @returns {Array}
	**/
	this.getElements = function() {
		return _elements.all();
	};
	
	/**
	 * Returns the first element
	 *
	 * @alias ContainerElement#first
	 * @returns {?Element}
	**/
	this.first = function() {
		return _elements.first();
	};
	
	/**
	 * Returns the last element
	 *
	 * @alias ContainerElement#last
	 * @returns {?Element}
	**/
	this.last = function() {
		return _elements.last();
	};
	
	/**
	 * Executes a provided callback function once per element in ascending order
	 *
	 * @alias ContainerElement#each
	 * @param {Function} callback
	 * @param {*} [thisArg]
	 * @param {Boolean} [ifRecursive]
	 * @returns {ContainerElement} this
	**/
	this.each = function(callback, thisArg, ifRecursive) {
		_elements.each(function(element) {
			callback.call(thisArg, element);
			
			if (ifRecursive !== false && is(element, ContainerElement)) {
				element.each(callback, thisArg);
			}
		});
		
		return this;
	};
	
	return this;
}

/**
 * The type of the element
 *
 * @type String
 * @default 'ContainerElement'
 * @readonly
**/
containerElementProto.elementType = 'ContainerElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Container'
 * @readonly
**/
containerElementProto.elementName = 'Container';

/**
 * Draws the container background to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {ContainerElement} this
**/
containerElementProto.draw = function(ctx) {
	var bg = this.background;
	
	if (isString(bg) && bg.length > 0) {
		ctx.beginPath();
		ctx.fillStyle = bg;
		ctx.fillRect(this.screenX, this.screenY, this.renderWidth, this.renderHeight);
	}
	
	return this;
};

var containerElementClass = stdClass();
containerElementClass.width = 'fit';
containerElementClass.height = 'fit';

$elements.addType(containerElementProto.elementName, ContainerElement, true, true);
$classes.set(containerElementProto.elementType, containerElementClass);

/**
 * Creates a new PolygonElement
 *
 * @class PolygonElement
 * @extends Element
 * @description todoc
**/
function PolygonElement() {
	Element.call(this);
	
	this.classList.add(polygonElementProto.elementType, 1);
	
	forIn(polygonElementClass, this.addProp, this);
}

/** @lends PolygonElement# **/
var polygonElementProto = PolygonElement.prototype = Object.create(Element.prototype);
polygonElementProto.constructor = PolygonElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'PolygonElement'
 * @readonly
**/
polygonElementProto.elementType = 'PolygonElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Polygon'
 * @readonly
**/
polygonElementProto.elementName = 'Polygon';

/**
 * The vertices of the element
 *
 * @type Array
 * @default null
**/
polygonElementProto.vertices = null;

/**
 * The fill color of the polygon
 *
 * @type String
 * @default ''
**/
polygonElementProto.fill = '';

/**
 * The stroke width of the polygon
 *
 * @type Number
 * @default 1
**/
polygonElementProto.strokeWidth = 1;

/**
 * The stroke color of the polygon
 *
 * @type String
 * @default '#000000'
**/
polygonElementProto.strokeColor = '#000000';

/**
 * Draws the element to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {PolygonElement} this
**/
polygonElementProto.draw = function(ctx) {
	var vs = this.vertices
	, i = 0
	, il = vs.length
	, x = this.screenX
	, y = this.screenY
	, w = this.renderWidth
	, h = this.renderHeight;
	
	if (il > 0) {
		ctx.beginPath();
		ctx.lineWidth = this.strokeWidth;
		ctx.strokeStyle = this.strokeColor;
		
		ctx.moveTo(
			x + vs[i][0] * w,
			y + vs[i][1] * h
		);
		
		for (++i; i < il; i++) {
			ctx.lineTo(
				x + vs[i][0] * w,
				y + vs[i][1] * h
			);
		}
		
		if (il > 2) {
			ctx.closePath();
			
			if (this.fill) {
				ctx.fillStyle = this.fill;
				ctx.fill();
			}
		}
		
		ctx.stroke();
	}
	
	return this;
};

var polygonElementClass = $classes.fromPrototype(polygonElementProto, ['elementType', 'elementName']);

$elements.addType(polygonElementProto.elementName, PolygonElement, false, true);
$classes.set(polygonElementProto.elementType, polygonElementClass);

/**
 * Creates a new LineElement
 *
 * @class LineElement
 * @extends PolygonElement
 * @param {Object} [elementUse]
 * @description todoc
**/
function LineElement(elementUse) {
	this.vertices = [[0, 0], [1, 1]];
	
	PolygonElement.call(this);
	
	this.classList.add(lineElementProto.elementType, 2);
	
	this.edit(elementUse);
}

/** @lends LineElement# **/
var lineElementProto = LineElement.prototype = Object.create(PolygonElement.prototype);
lineElementProto.constructor = LineElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'LineElement'
 * @readonly
**/
lineElementProto.elementType = 'LineElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Line'
 * @readonly
**/
lineElementProto.elementName = 'Line';

$elements.addType(lineElementProto.elementName, LineElement, true, true);

/**
 * Creates a new TriangleElement
 *
 * @class TriangleElement
 * @extends PolygonElement
 * @param {Object} [elementUse]
 * @description todoc
**/
function TriangleElement(elementUse) {
	this.vertices = [[0.5, 0], [1, 1], [0, 1]];
	
	PolygonElement.call(this);
	
	this.classList.add(triangleElementProto.elementType, 2);
	
	this.edit(elementUse);
}

/** @lends TriangleElement# **/
var triangleElementProto = TriangleElement.prototype = Object.create(PolygonElement.prototype);
triangleElementProto.constructor = TriangleElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'TriangleElement'
 * @readonly
**/
triangleElementProto.elementType = 'TriangleElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Triangle'
 * @readonly
**/
triangleElementProto.elementName = 'Triangle';

$elements.addType(triangleElementProto.elementName, TriangleElement, true, true);

/**
 * Creates a new RectangleElement
 *
 * @class RectangleElement
 * @extends PolygonElement
 * @param {Object} [elementUse]
 * @description todoc
**/
function RectangleElement(elementUse) {
	this.vertices = [[0, 0], [1, 0], [1, 1], [0, 1]];
	
	PolygonElement.call(this);
	
	this.classList.add(rectangleElementProto.elementType, 2);
	
	this.edit(elementUse);
}

/** @lends RectangleElement# **/
var rectangleElementProto = RectangleElement.prototype = Object.create(PolygonElement.prototype);
rectangleElementProto.constructor = RectangleElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'RectangleElement'
 * @readonly
**/
rectangleElementProto.elementType = 'RectangleElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Rectangle'
 * @readonly
**/
rectangleElementProto.elementName = 'Rectangle';

$elements.addType(rectangleElementProto.elementName, RectangleElement, true, true);

/**
 * Creates a new PentagonElement
 *
 * @class PentagonElement
 * @extends PolygonElement
 * @param {Object} [elementUse]
 * @description todoc
**/
function PentagonElement(elementUse) {
	this.vertices = [[0.5, 0], [1, 0.4], [0.8, 1], [0.2, 1], [0, 0.4]];
	
	PolygonElement.call(this);
	
	this.classList.add(pentagonElementProto.elementType, 2);
	
	this.edit(elementUse);
}

/** @lends PentagonElement# **/
var pentagonElementProto = PentagonElement.prototype = Object.create(PolygonElement.prototype);
pentagonElementProto.constructor = PentagonElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'PentagonElement'
 * @readonly
**/
pentagonElementProto.elementType = 'PentagonElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Pentagon'
 * @readonly
**/
pentagonElementProto.elementName = 'Pentagon';

$elements.addType(pentagonElementProto.elementName, PentagonElement, true, true);

/**
 * Creates a new HexagonElement
 *
 * @class HexagonElement
 * @extends PolygonElement
 * @param {Object} [elementUse]
 * @description todoc
**/
function HexagonElement(elementUse) {
	this.vertices = [[0.25, 0], [0.75, 0], [1, 0.5], [0.75, 1], [0.25, 1], [0, 0.5]];
	
	PolygonElement.call(this);
	
	this.classList.add(hexagonElementProto.elementType, 2);
	
	this.edit(elementUse);
}

/** @lends HexagonElement# **/
var hexagonElementProto = HexagonElement.prototype = Object.create(PolygonElement.prototype);
hexagonElementProto.constructor = HexagonElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'HexagonElement'
 * @readonly
**/
hexagonElementProto.elementType = 'HexagonElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Hexagon'
 * @readonly
**/
hexagonElementProto.elementName = 'Hexagon';

$elements.addType(hexagonElementProto.elementName, HexagonElement, true, true);

/**
 * Creates a new StarElement
 *
 * @class StarElement
 * @extends PolygonElement
 * @param {Object} [elementUse]
 * @description todoc
**/
function StarElement(elementUse) {
	this.vertices = [[0, 1], [0.5, 0], [1, 1], [0, 0.3], [1, 0.3]];
	
	PolygonElement.call(this);
	
	this.classList.add(starElementProto.elementType, 2);
	
	this.edit(elementUse);
}

/** @lends StarElement# **/
var starElementProto = StarElement.prototype = Object.create(PolygonElement.prototype);
starElementProto.constructor = StarElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'StarElement'
 * @readonly
**/
starElementProto.elementType = 'StarElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Star'
 * @readonly
**/
starElementProto.elementName = 'Star';

$elements.addType(starElementProto.elementName, StarElement, true, true);

/**
 * Creates a new CircleElement
 *
 * @class CircleElement
 * @extends Element
 * @param {Object} [elementUse]
 * @description todoc
**/
function CircleElement(elementUse) {
	Element.call(this);
	
	this.classList.add(circleElementProto.elementType, 1);
	
	forIn(circleElementClass, this.addPropSafe, this);
	
	this.edit(elementUse);
}

/** @lends CircleElement# **/
var circleElementProto = CircleElement.prototype = Object.create(Element.prototype);
circleElementProto.constructor = CircleElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'CircleElement'
 * @readonly
**/
circleElementProto.elementType = 'CircleElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Circle'
 * @readonly
**/
circleElementProto.elementName = 'Circle';

/**
 * The radius of the circle
 *
 * @type Number
 * @default 10
**/
circleElementProto.r = 10;

/**
 * The fill color of the circle
 *
 * @type String
 * @default ''
**/
circleElementProto.fill = '';

/**
 * The stroke width of the circle
 *
 * @type Number
 * @default 1
**/
circleElementProto.strokeWidth = 1;

/**
 * The stroke color of the circle
 *
 * @type String
 * @default '#000000'
**/
circleElementProto.strokeColor = '#000000';

/**
 * Returns the auto width of the circle element
 *
 * @returns {Number}
**/
circleElementProto.measureWidth = function() {
	return this.r * 2;
};

/**
 * Returns the auto height of the circle element
 *
 * @returns {Number}
**/
circleElementProto.measureHeight = function() {
	return this.r * 2;
};

/**
 * Draws the element to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {CircleElement} this
**/
circleElementProto.draw = function(ctx) {
	ctx.beginPath();
	ctx.lineWidth = this.strokeWidth;
	ctx.strokeStyle = this.strokeColor;
	
	ctx.arc(
		this.screenX + this.r,
		this.screenY + this.r,
		this.r,
		0,
		Math.PI * 2,
		true
	);
	
	ctx.stroke();
	
	if (this.fill !== '') {
		ctx.fillStyle = this.fill;
		ctx.fill();
	}
	
	return this;
};

var circleElementClass = $classes.fromPrototype(circleElementProto, ['elementType', 'elementName']);
circleElementClass.width = 'auto';
circleElementClass.height = 'auto';

$elements.addType(circleElementProto.elementName, CircleElement, true, true);
$classes.set(circleElementProto.elementType, circleElementClass);

/**
 * Creates a new EllipseElement
 *
 * @class EllipseElement
 * @extends Element
 * @param {Object} [elementUse]
 * @description todoc
**/
function EllipseElement(elementUse) {
	Element.call(this);
	
	this.classList.add(ellipseElementProto.elementType, 1);
	
	forIn(ellipseElementClass, this.addProp, this);
	
	this.edit(elementUse);
}

/** @lends EllipseElement# **/
var ellipseElementProto = EllipseElement.prototype = Object.create(Element.prototype);
ellipseElementProto.constructor = EllipseElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'EllipseElement'
 * @readonly
**/
ellipseElementProto.elementType = 'EllipseElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Ellipse'
 * @readonly
**/
ellipseElementProto.elementName = 'Ellipse';

/**
 * The fill color of the ellipse
 *
 * @type String
 * @default ''
**/
ellipseElementProto.fill = '';

/**
 * The stroke width of the ellipse
 *
 * @type Number
 * @default 1
**/
ellipseElementProto.strokeWidth = 1;

/**
 * The stroke color of the ellipse
 *
 * @type String
 * @default '#000000'
**/
ellipseElementProto.strokeColor = '#000000';

/**
 * Draws the element to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {EllipseElement} this
**/
ellipseElementProto.draw = function(ctx) {
	var kappa = 0.5522848
	
	, w = this.renderWidth / 2
	, h = this.renderHeight / 2
	, x = this.screenX
	, y = this.screenY
	
	, ox = w * kappa
	, oy = h * kappa
	, xm = x + w
	, ym = y + h
	, xe = xm + w
	, ye = ym + h;
	
	ctx.beginPath();
	ctx.lineWidth = this.strokeWidth;
	ctx.strokeStyle = this.strokeColor;
	
	ctx.moveTo(x, ym);
	
	ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
	ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
	ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
	ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
	
	ctx.stroke();
	
	if (this.fill !== '') {
		ctx.fillStyle = this.fill;
		ctx.fill();
	}
	
	return this;
};

var ellipseElementClass = $classes.fromPrototype(ellipseElementProto, ['elementType', 'elementName']);

$elements.addType(ellipseElementProto.elementName, EllipseElement, true, true);
$classes.set(ellipseElementProto.elementType, ellipseElementClass);

/**
 * Creates a new TextElement
 *
 * @class TextElement
 * @extends Element
 * @param {Object} [elementUse]
 * @description todoc
**/
function TextElement(elementUse) {
	Element.call(this);
	
	this.classList.add(textElementProto.elementType, 1);
	
	forIn(textElementClass, this.addPropSafe, this);
	
	this.edit(elementUse);
}

/** @lends TextElement# **/
var textElementProto = TextElement.prototype = Object.create(Element.prototype);
textElementProto.constructor = TextElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'TextElement'
 * @readonly
**/
textElementProto.elementType = 'TextElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Text'
 * @readonly
**/
textElementProto.elementName = 'Text';

/**
 * The text of the element
 *
 * @type String
 * @default null
**/
textElementProto.text = null;

/**
 * The color of the text
 *
 * @type String
 * @default '#000000'
**/
textElementProto.textColor = '#000000';

/**
 * Indents the first line of the text
 *
 * @type Number
 * @default 0
**/
textElementProto.textIndent = 0;

/**
 * Specifies the decoration added to the text
 *
 * @type String
 * @default 'none'
 * @todo Implement text decoration
**/
textElementProto.textDecoration = 'none';

/**
 * Controls the capitalization of text
 *
 * @type String
 * @default 'none'
**/
textElementProto.textTransform = 'none';

/**
 * Adds shadow to the text
 *
 * @type String
 * @default 'none'
 * @todo Implement text shadow
**/
textElementProto.textShadow = 'none';

/**
 * Specifies the font for the element
 *
 * @type String
 * @default 'sans-serif'
**/
textElementProto.fontFamily = 'sans-serif';

/**
 * Specifies the size of the font in px
 *
 * @type Number
 * @default 12
**/
textElementProto.fontSize = 12;

/**
 * Specifies the weight or boldness of the font
 *
 * @type String
 * @default 'normal'
**/
textElementProto.fontWeight = 'normal';

/**
 * Specifies the font style for a text
 *
 * @type String
 * @default 'normal'
**/
textElementProto.fontStyle = 'normal';

/**
 * Increases or decreases the space between characters in the text
 *
 * @type Number
 * @default 0
**/
textElementProto.letterSpacing = 0;

/**
 * The horizontal align of the text
 *
 * @type String
 * @default 'left'
**/
textElementProto.textAlign = 'left';

/**
 * The vertical align of the text
 *
 * @type String
 * @default 'top'
 * @todo Implement text baseline
**/
textElementProto.textBaseline = 'top';

/**
 * The height of each line
 *
 * @type String
 * @default 'auto'
**/
textElementProto.lineHeight = 'auto';

/**
 * Returns the auto width of the text element
 *
 * @returns {Number}
**/
textElementProto.measureWidth = function() {
	var ctx = $canvas.ctx
	, max = 0
	, lines = this.getLines()
	, i = 0
	, il = lines.length
	, width;
	
	ctx.font = this.getFontDescriptor();
	
	for (; i < il; i++) {
		width = ctx.measureText(lines[i]).width;
		
		if (width > max) max = width;
	}
	
	return max;
};

/**
 * Returns the auto height of the text element
 *
 * @returns {Number}
**/
textElementProto.measureHeight = function() {
	return this.getLines().length * this.getLineHeight();
};

/**
 * Draws the element to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {TextElement} this
**/
textElementProto.draw = function(ctx) {
	var lines = this.getLines()
	, i = 1
	, il = lines.length
	, lh = this.getLineHeight()
	, x = this.screenX
	, y = this.screenY + ((lh - this.fontSize) / 2)
	, align = this.textAlign;
	
	ctx.beginPath();
	
	ctx.font = this.getFontDescriptor();
	ctx.textAlign = align;
	ctx.textBaseline = 'top'; /* this.textBaseline */
	ctx.fillStyle = this.textColor;
	
	if (align === 'center') x += this.renderWidth / 2;
	if (align === 'right') x += this.renderWidth;
	
	ctx.fillText(lines[0], x + this.textIndent, y);
	
	for (; i < il; i++) {
		ctx.fillText(lines[i], x, y += lh);
	}
	
	return this;
};

/**
 * Returns the font data
 *
 * @returns {String}
**/
textElementProto.getFontDescriptor = function() {
	var font = '';
	
	if (this.fontWeight === 'bold') font += 'bold ';
	if (this.fontStyle === 'italic') font += 'italic ';
	
	font += this.fontSize + 'px ';
	font += this.fontFamily;
	
	return font;
};

/**
 * Returns the transformed text
 *
 * @returns {String}
**/
textElementProto.getTransformedText = function() {
	var text = String(this.text);
	
	switch(this.textTransform) {
		case 'lowercase': text = text.toLowerCase(); break;
		case 'uppercase': text = text.toUpperCase(); break;
		case 'capitalize': text = capitalize(text); break;
	}
	
	if (this.letterSpacing > 0) text = text.split('').join(repeatString(' ', this.letterSpacing));
	
	return text;
};

/**
 * Returns the line height
 *
 * @returns {Number}
**/
textElementProto.getLineHeight = function() {
	var lh = this.lineHeight;
	
	if (lh === 'auto') return this.fontSize;
	if (isPercent(lh)) return parsePercent(lh, this.fontSize);
	if (isNumber(getInt(lh))) return getInt(lh);
	
	return 0;
};

/**
 * Returns the transformed lines
 *
 * @returns {Array}
**/
textElementProto.getLines = function() {
	return this.getTransformedText().split(lineBreakRegX);
};

var textElementClass = $classes.fromPrototype(textElementProto, ['elementType', 'elementName']);
textElementClass.width = 'auto';
textElementClass.height = 'auto';

$elements.addType(textElementProto.elementName, TextElement, true, true);
$classes.set(textElementProto.elementType, textElementClass);

/**
 * Creates a new ImageElement
 *
 * @class ImageElement
 * @extends Element
 * @param {Object} [elementUse]
 * @description todoc
**/
function ImageElement(elementUse) {
	Element.call(this);
	
	this.classList.add(imageElementProto.elementType, 1);
	
	forIn(imageElementClass, this.addPropSafe, this);
	
	this.img = new Image();
	
	this.edit(elementUse);
}

/** @lends ImageElement# **/
var imageElementProto = ImageElement.prototype = Object.create(Element.prototype);
imageElementProto.constructor = ImageElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'ImageElement'
 * @readonly
**/
imageElementProto.elementType = 'ImageElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Image'
 * @readonly
**/
imageElementProto.elementName = 'Image';

/**
 * The x position of the source image to draw into the destination context
 *
 * @type Number|String
 * @default 0
**/
imageElementProto.sourceX = 0;

/**
 * The y position of the source image to draw into the destination context
 *
 * @type Number|String
 * @default 0
**/
imageElementProto.sourceY = 0;

/**
 * The width of the source image to draw into the destination context
 *
 * @type Number|String
 * @default '100%'
**/
imageElementProto.sourceWidth = '100%';

/**
 * The height of the source image to draw into the destination context
 *
 * @type Number|String
 * @default '100%'
**/
imageElementProto.sourceHeight = '100%';

/**
 * The image of the element
 *
 * @type Image
 * @default null
 * @readonly
**/
imageElementProto.img = null;

Object.defineProperty(imageElementProto, 'src', {
	/**
	 * The source of the image
	 *
	 * @name ImageElement#src
	 * @type String
	 * @default ''
	 **/
	get: function() {
		return this.img.src;
	},
	set: function(newVal) {
		if (startsWith(newVal, '#')) {
			newVal = $assets.getByID(Loader.IMG, newVal.slice(1)).src;
		}
		
		return this.img.src = newVal;
	}
});

/**
 * Gets the auto width of the element
 *
 * @returns {Number}
**/
imageElementProto.measureWidth = function() {
	var sourceWidth = this.sourceWidth;
	
	if (isNumber(sourceWidth)) return sourceWidth;
	if (isPercent(sourceWidth)) return parsePercent(sourceWidth, this.img.width);
	
	return this.img.width;
};

/**
 * Gets the auto height of the element
 *
 * @returns {Number}
**/
imageElementProto.measureHeight = function() {
	var sourceHeight = this.sourceHeight;
	
	if (isNumber(sourceHeight)) return sourceHeight;
	if (isPercent(sourceHeight)) return parsePercent(sourceHeight, this.img.height);
	
	return this.img.height;
};

/**
 * Draws the element to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {ImageElement} this
**/
imageElementProto.draw = function(ctx) {
	var img = this.img, sx, sy;
	
	if (img.complete === true) {
		if (isPercent(sx = this.sourceX)) sx = parsePercent(sx, img.width);
		if (isPercent(sy = this.sourceY)) sy = parsePercent(sy, img.height);
		
		ctx.drawImage(img,
			sx, sy,
			this.measureWidth(), this.measureHeight(),
			this.screenX, this.screenY,
			this.renderWidth, this.renderHeight
		);
	}
	
	return this;
};

var imageElementClass = $classes.fromPrototype(imageElementProto, ['elementType', 'elementName']);
imageElementClass.width = 'auto';
imageElementClass.height = 'auto';

$elements.addType(imageElementProto.elementName, ImageElement, true, true);
$classes.set(imageElementProto.elementType, imageElementClass);

/**
 * Creates a new PatternElement
 *
 * @class PatternElement
 * @extends Element
 * @param {Object} [elementUse]
 * @description todoc
**/
function PatternElement(elementUse) {
	Element.call(this);
	
	this.classList.add(patternElementProto.elementType, 1);
	
	forIn(patternElementClass, this.addPropSafe, this);
	
	this.img = new Image();
	
	this.edit(elementUse);
}

/** @lends PatternElement# **/
var patternElementProto = PatternElement.prototype = Object.create(Element.prototype);
patternElementProto.constructor = PatternElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'PatternElement'
 * @readonly
**/
patternElementProto.elementType = 'PatternElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'Pattern'
 * @readonly
**/
patternElementProto.elementName = 'Pattern';

/**
 * The x position of the source image to draw into the destination context
 *
 * @type Number|String
 * @default 0
**/
patternElementProto.sourceX = 0;

/**
 * The y position of the source image to draw into the destination context
 *
 * @type Number|String
 * @default 0
**/
patternElementProto.sourceY = 0;

/**
 * The width of the source image to draw into the destination context
 *
 * @type Number|String
 * @default '100%'
**/
patternElementProto.sourceWidth = '100%';

/**
 * The height of the source image to draw into the destination context
 *
 * @type Number|String
 * @default '100%'
**/
patternElementProto.sourceHeight = '100%';

/**
 * The image of the element
 *
 * @type Image
 * @default null
 * @readonly
**/
patternElementProto.img = null;

Object.defineProperty(patternElementProto, 'src', {
	/**
	 * The source of the image
	 *
	 * @name PatternElement#src
	 * @type String
	 * @default ''
	 **/
	get: function() {
		return this.img.src;
	},
	set: function(newVal) {
		if (startsWith(newVal, '#')) {
			newVal = $assets.getByID(Loader.IMG, newVal.slice(1)).src;
		}
		
		return this.img.src = newVal;
	}
});

/**
 * A String indicating how to repeat the image (repeat|repeat-x|repeat-y|no-repeat)
 *
 * @name PatternElement#repeat
 * @type String
 * @default 'repeat'
**/
patternElementProto.repeat = 'repeat';

/**
 * Gets the auto width of the element
 *
 * @returns {Number}
**/
patternElementProto.measureWidth = function() {
	var sourceWidth = this.sourceWidth;
	
	if (isNumber(sourceWidth)) return sourceWidth;
	if (isPercent(sourceWidth)) return parsePercent(sourceWidth, this.img.width);
	
	return this.img.width;
};

/**
 * Gets the auto height of the element
 *
 * @returns {Number}
**/
patternElementProto.measureHeight = function() {
	var sourceHeight = this.sourceHeight;
	
	if (isNumber(sourceHeight)) return sourceHeight;
	if (isPercent(sourceHeight)) return parsePercent(sourceHeight, this.img.height);
	
	return this.img.height;
};

/**
 * Draws the element to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {PatternElement} this
 * @todo implement sx, sy
**/
patternElementProto.draw = function(ctx) {
	var img = this.img, sx, sy;
	
	if (img.complete === true) {
		if (isPercent(sx = this.sourceX)) sx = parsePercent(sx, img.width);
		if (isPercent(sy = this.sourceY)) sy = parsePercent(sy, img.height);
		
		ctx.rect(this.screenX, this.screenY, this.renderWidth, this.renderHeight);
		ctx.fillStyle = ctx.createPattern(img, this.repeat);
		ctx.fill();
	}
	
	return this;
};

var patternElementClass = $classes.fromPrototype(patternElementProto, ['elementType', 'elementName']);
patternElementClass.width = 'auto';
patternElementClass.height = 'auto';

$elements.addType(patternElementProto.elementName, PatternElement, true, true);
$classes.set(patternElementProto.elementType, patternElementClass);

/**
 * Creates a new SpriteSheetElement
 *
 * @class SpriteSheetElement
 * @extends Element
 * @param {Object} [elementUse]
 * @description todoc
**/
function SpriteSheetElement(elementUse) {
	Element.call(this);
	
	this.classList.add(spriteSheetElementProto.elementType, 1);
	
	forIn(spriteSheetElementClass, this.addPropSafe, this);
	
	this.img = new Image();
	this.frames = [];
	this.animations = stdClass();
	this.currentAnimationFrame = { x: 0, y: 0 };
	
	this.edit(elementUse);
}

/** @lends SpriteSheetElement# **/
var spriteSheetElementProto = SpriteSheetElement.prototype = Object.create(Element.prototype);
spriteSheetElementProto.constructor = SpriteSheetElement;

/**
 * The type of the element
 *
 * @type String
 * @default 'SpriteSheetElement'
 * @readonly
**/
spriteSheetElementProto.elementType = 'SpriteSheetElement';

/**
 * The name of the element
 *
 * @type String
 * @default 'SpriteSheet'
 * @readonly
**/
spriteSheetElementProto.elementName = 'SpriteSheet';

/**
 * The x position of the source spriteSheet to draw into the destination context
 *
 * @type Number|String
 * @default 0
**/
spriteSheetElementProto.sourceX = 0;

/**
 * The y position of the source spriteSheet to draw into the destination context
 *
 * @type Number|String
 * @default 0
**/
spriteSheetElementProto.sourceY = 0;

/**
 * The width of each frame
 *
 * @type Number|String
 * @default 0
**/
spriteSheetElementProto.frameWidth = 0;

/**
 * The height of each frame
 *
 * @type Number|String
 * @default 0
**/
spriteSheetElementProto.frameHeight = 0;

/**
 * The spriteSheet of the element
 *
 * @type Image
 * @default null
 * @readonly
**/
spriteSheetElementProto.img = null;

Object.defineProperty(spriteSheetElementProto, 'src', {
	/**
	 * The source of the spriteSheet
	 *
	 * @name SpriteSheetElement#src
	 * @type String
	 * @default ''
	 **/
	get: function() {
		return this.img.src;
	},
	set: function(newVal) {
		if (startsWith(newVal, '#')) {
			newVal = $assets.getByID(Loader.IMG, newVal.slice(1)).src;
		}
		
		return this.img.src = newVal;
	}
});

/**
 * The animations of the sprite
 *
 * @type Object
 * @default null
 * @readonly
**/
spriteSheetElementProto.animations = null;

/**
 * The frames of the sprite
 *
 * @type Array
 * @default null
 * @readonly
**/
spriteSheetElementProto.frames = null;

/**
 * The tick of the sprite
 *
 * @type int
 * @default 0
 * @readonly
**/
spriteSheetElementProto.tick = 0;

/**
 * The current animation of the sprite
 *
 * @type String
 * @default ''
**/
spriteSheetElementProto.currentAnimation = 'default';

/**
 * The current frame of the sprite
 *
 * @type int
 * @default 0
**/
spriteSheetElementProto.currentFrame = 0;

/**
 * The current animationframe of the sprite
 *
 * @type Object
 * @default null
 * @readonly
**/
spriteSheetElementProto.currentAnimationFrame = null;

/**
 * The frame rate of the sprite
 *
 * @type int
 * @default 7
**/
spriteSheetElementProto.frameRate = 7;

/**
 * The spacing of each frame
 *
 * @type int
 * @default 0
**/
spriteSheetElementProto.frameSpacing = 0;

/**
 * Indicates whether the sprite should be animated or not
 *
 * @type Boolean
 * @default false
**/
spriteSheetElementProto.paused = false;

/**
 * Adds an animation
 *
 * @param {String} key
 * @param {Object} animData
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.addAnimation = function(key, animData) {
	this.animations[key] = stdClass();
	
	assign(this.animations[key], {
		frames: [],
		next: key,
		frameRate: this.frameRate,
		direction: 'forward'
	});
	
	return this.editAnimation(key, animData);
};

/**
 * Edits an animation
 *
 * @param {String} key
 * @param {Object} animData
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.editAnimation = function(key, animData) {
	var animation;
	
	if (has(this.animations, key)) {
		animation = this.animations[key];
		
		if (isArray(animData.frames)) {
			empty(animation.frames);
			animation.frames.push.apply(animation.frames, animData.frames);
		}
		
		if (isString(animData.next)) {
			animation.next = animData.next;
		}
		
		if (isNumber(animData.frameRate)) {
			animation.frameRate = animData.frameRate;
		}
		
		if (animData.direction === 'forward' || animData.direction === 'backward') {
			animation.direction = animData.direction;
		}
	}
	
	return this;
};

/**
 * Removes an animation
 *
 * @param {String} key
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.removeAnimation = function(key) {
	delete this.animations[key];
	
	return this;
};

/**
 * Sets the frameWidth and frameHeight properties
 *
 * @param {int} rows
 * @param {int} cols
 * @param {int} [width]
 * @param {int} [height]
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.setFrameSize = function(rows, cols, width, height) {
	var w = 0, h = 0;
	
	if (isNumber(width) && isNumber(height)) {
		w = width;
		h = height;
	} else if (this.img.complete === true) {
		w = this.img.width;
		h = this.img.height;
	}
	
	this.frameWidth = w / cols - 2 * this.frameSpacing;
	this.frameHeight = h / rows - 2 * this.frameSpacing;
	
	return this.setFrames();
};

/**
 * Sets the frames
 *
 * @param {int} [count]
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.setFrames = function(count) {
	var i, sx, sy, x, y, w, h, sprite, args, onLoad;
	
	if (this.img.complete === true) {
		i = 0;
		
		if (!isNumber(count)) count = Infinity;
		
		empty(this.frames);
		
		w = this.img.width;
		h = this.img.height;
		
		if (isPercent(sx = this.sourceX)) sx = parsePercent(sx, this.frameWidth);
		if (isPercent(sy = this.sourceY)) sy = parsePercent(sy, this.frameHeight);
		
		vertical: for (y = sy; y < h; y += this.frameHeight) {
			horizontal: for (x = sx; x < w; x += this.frameWidth) {
				this.frames.push({ x: x, y: y });
				
				if (++i >= count) break vertical;
			}
		}
	} else {
		sprite = this;
		args = getArgs(arguments);
		onLoad = function() {
			sprite.setFrames.apply(sprite, args);
			sprite.img.removeEventListener('load', onLoad);
		};
		
		this.img.addEventListener('load', onLoad);
	}
	
	return this;
};

/**
 * Starts the animation
 *
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.start = function() {
	this.paused = false;
	
	return this;
};

/**
 * Stops the animation
 *
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.stop = function() {
	this.paused = true;
	
	return this;
};

/**
 * Gets the auto width of the element
 *
 * @returns {Number}
**/
spriteSheetElementProto.measureWidth = function() {
	var width = this.frameWidth;
	
	return isPercent(width) ? parsePercent(width, this.img.width) : width;
};

/**
 * Gets the auto height of the element
 *
 * @returns {Number}
**/
spriteSheetElementProto.measureHeight = function() {
	var height = this.frameHeight;
	
	return isPercent(height) ? parsePercent(height, this.img.height) : height;
};

/**
 * Draws the element to the given context
 *
 * @param {CanvasRenderingContext2D} ctx
 * @returns {SpriteSheetElement} this
**/
spriteSheetElementProto.draw = function(ctx) {
	var img = this.img
	, sw, sh, animation, frameRate, frames;
	
	if (img.complete === true) {
		if (isPercent(sw = this.frameWidth)) sw = parsePercent(sw, img.width);
		if (isPercent(sh = this.frameHeight)) sh = parsePercent(sh, img.height);
		
		if (this.paused === false && has(this.animations, this.currentAnimation)) {
			animation = this.animations[this.currentAnimation];
			frames = animation.frames;
			frameRate = isNumber(animation.frameRate) ? animation.frameRate : this.frameRate;
			
			if (++this.tick >= frameRate && frames.length > 0) {
				if (animation.direction === 'backward') {
					if (--this.currentFrame <= 0) {
						if (animation.next === this.currentAnimation && animation.loop === true) {
							this.currentFrame = frames.length - 1;
						} else {
							if (has(this.animations, animation.next)) this.currentAnimation = animation.next;
							
							this.currentFrame = 0;
						}
					}
				} else {
					if (++this.currentFrame >= frames.length) {
						if (animation.next === this.currentAnimation && animation.loop === true) {
							this.currentFrame = 0;
						} else {
							if (has(this.animations, animation.next)) this.currentAnimation = animation.next;
							
							this.currentFrame = 0;
						}
					}
				}
				
				this.currentAnimationFrame = this.frames[frames[this.currentFrame]];
				
				this.tick = 0;
			}
		}
		
		ctx.drawImage(img,
			this.currentAnimationFrame.x, this.currentAnimationFrame.y,
			sw, sh,
			this.screenX, this.screenY,
			this.renderWidth, this.renderHeight
		);
	}
	
	return this;
};

var spriteSheetElementClass = stdClass();
spriteSheetElementClass.width = 'auto';
spriteSheetElementClass.height = 'auto';
spriteSheetElementClass.frameRate = spriteSheetElementProto.frameRate;

$elements.addType(spriteSheetElementProto.elementName, SpriteSheetElement, true, true);
$classes.set(spriteSheetElementProto.elementType, spriteSheetElementClass);

/**
 * Chill namespace
 *
 * @namespace Chill
 * @description todoc
**/
var Chill = stdClass();

/**
 * Creates a new Chill application
 *
 * @class Chill.App
 * @memberof Chill
 * @param {Object|String} settings
 * @description todoc
**/
Chill.App = function(settings) {
	this.appData = settings;
};

/** @lends Chill.App# **/
var chillAppProto = Chill.App.prototype = stdClass();
chillAppProto.constructor = Chill.App;

/**
 * The data of the application
 *
 * @type Object
 * @default null
 * @readonly
**/
chillAppProto.appData = null;

/**
 * Returns the string value of the application
 * 
 * @returns {String}
**/
chillAppProto.toString = function() {
	return 'Chill App';
};

/**
 * Creates a new Chill debugger
 *
 * @class Chill.Debugger
 * @memberof Chill
 * @param {Scene} scene
 * @description todoc
**/
Chill.Debugger = function(scene) {
	this.scene = scene;
};

/** @lends Chill.Debugger# **/
var chillDebuggerProto = Chill.Debugger.prototype = stdClass();
chillDebuggerProto.constructor = Chill.Debugger;

/**
 * The scene of the debugger
 *
 * @type Scene
 * @default null
 * @readonly
**/
chillDebuggerProto.scene = null;

/**
 * Adds an element to the scene
 * 
 * @param {Layer} layer
 * @param {int} count
 * @param {String} [elementType = 'Text']
 * @param {Function} [callback]
 * @returns {Scene}
**/
chillDebuggerProto.addElements = function(layer, count, elementType, callback) {
	var callbackIsFunction = isFunction(callback);
	
	elementType = isUndefined(elementType) ? 'Text' : elementType;
	
	repeat(count, function(i) {
		var element = layer.create(elementType);
		
		layer.add(element);
		
		if (callbackIsFunction) callback(element, i);
	});
	
	return this.scene;
};

/**
 * Instantiates a chill application
 *
 * @memberof Chill
 * @param {Chill.App} app
 * @param {String|HTMLElement} wrapper
 * @param {Function} [callback]
 * @returns {Scene}
**/
Chill.out = function(app, wrapper, callback) {
	var scene;
	
	if (isString(wrapper)) wrapper = doc.getElementById(wrapper);
	
	if (!is(wrapper, HTMLElement)) die('the given wrapper not an html element');
	
	if (wrapper.children.length > 0) warning('wrapper should be empty');
	
	wrapper.classList.add('chill-wrapper');
	scene = new Scene(wrapper);
	
	if (!(app instanceof Chill.App)) {
		warning('app not instance of Chill\\App');
	}
	
	scene.include(app.appData, callback);
	
	return scene;
};

/**
 * Creates a new class
 *
 * @method
 * @name Chill.createClass
 * @param {String} className
 * @param {Object} classData
 * @returns {Object} Chill
**/
Chill.createClass = $methods.createClass;

/**
 * Creates a new mask
 *
 * @memberof Chill
 * @param {String} maskID
 * @param {Function} mask
 * @returns {Object} Chill
**/
Chill.createMask = function(maskID, mask) {
	if (isFunction(mask)) {
		$masks.set(maskID, mask);
	}
	
	return this;
};

/**
 * Creates an AbstractElement
 *
 * @method
 * @name Chill.createAbstractElement
 * @param {String} elementID
 * @param {String} elementType
 * @param {Object} [elementUse]
 * @param {Array} [args]
 * @returns {Chill} this
 * @see Scene#createAbstractElement
**/
Chill.createAbstractElement = $methods.createAbstractElement;

/**
 * Creates a new plugin
 *
 * @memberof Chill
 * @param {String} pluginID
 * @param {Function} pluginConstructor
 * @param {Object} [pluginConfig]
 * @param {Boolean} [force]
 * @returns {Object} Chill
**/
Chill.createPlugin = function(pluginID, pluginConstructor, pluginConfig, force) {
	var exists = $plugins.has(pluginID)
	, plugin, config;
	
	if (exists && force !== true) {
		warning('plugin "' + pluginID + '" already exists');
	} else {
		if (!isFunction(pluginConstructor)) {
			warning('invalid plugin constructor type');
		} else {
			plugin = stdClass();
			config = stdClass();
			
			if (isObject(pluginConfig)) assign(config, pluginConfig);
			
			plugin.constructor = pluginConstructor;
			plugin.config = config;
			
			$plugins.set(pluginID, plugin);
		}
	}
	
	return this;
};

/**
 * Creates a new Element type
 *
 * @memberof Chill
 * @param {String} type
 * @param {Object} createData
 * @param {Function} createData.constructor
 * @param {Function|Object} [createData.prototype]
 * @param {String} [createData.extends]
 * @param {Boolean} [createData.instantiatable]
 * @param {Boolean} [createData.extendable]
 * @returns {Object} Chill
**/
Chill.createElementType = function(type, createData) {
	var parent, prototype, constructor;
	
	if (isObject(createData)) {
		if (!isFunction(createData.constructor)) {
			warning('Constructor is required to create a new element type');
		} else if ($elements.hasType(type)) {
			warning('Cannot create "' + type + '", type already exists');
		} else {
			parent = $elements.getExtendable(createData.extends);
			
			if (isNull(parent)) {
				if (!isUndefined(createData.extends)) warning('Cannot extend "' + createData.extends + '", type does not exists, fall back to default: Element');
				
				parent = Element;
			}
			
			prototype = Object.create(parent.prototype);
			
			constructor = function CustomElement() {
				var args = getArgs(arguments);
				
				parent.apply(this, args);
				createData.constructor.apply(this, args);
			};
			
			if (isFunction(createData.prototype)) {
				createData.prototype.call(prototype);
			} else if (isObject(createData.prototype)) {
				assign(prototype, createData.prototype);
			}
			
			constructor.prototype = prototype;
			constructor.prototype.constructor = constructor;
			
			$elements.addType(type, constructor, createData.instantiatable, createData.extendable);
		}
	}
	
	return this;
};

window.Chill = Chill;

}(this));