
var userAgent = navigator.userAgent.toLowerCase();

var base = {

	selectors: false,
	sizzleSrc: './sizzle/sizzle.js',
	$body: null,

	reClassNameCache: {},

	browser: {
		version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
		webkit: /webkit/.test(userAgent),
		opera: /opera/.test(userAgent),
		msie: /msie/.test(userAgent) && !/opera/.test(userAgent),
		mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent),
		msafari: /apple.*mobile.*safari/.test(userAgent)
	},

	extend: function () {
		function ext(destination, source) {
			if(!destination) return;
			for (var property in source) {
				destination[property] = source[property];
			}
		}
		if (arguments.length == 2) {
			ext(arguments[0], arguments[1]);
		} else {
			var l = arguments.length, i = l-1, src = arguments[i];
			while (i--) {
				ext(arguments[i], src);
			}
		}
	},

	getCachedRegEx: function (className) {
		return ( base.reClassNameCache[className] || 
			( base.reClassNameCache[className] = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)')));
	},

	_fire: function (element, eventName, data) {
		var evnt = document.createEvent('HTMLEvents');
		evnt.initEvent(eventName, true, true);
		evnt.data = data || {};
		element.dispatchEvent(evnt);
	},
	
	/// http://unscriptable.com/index.php/2009/03/19/hi-performance-javascript-tips-1/
	toArray: (function (slice) {
		return function toArray (object) {
			return slice.call(object, 0);
		};
	})(Array.prototype.slice),
	
	GlowOnTouch: function (el)
	{
		if( el.length ) el = el[0];
		this.radius = 100;
		this.element = el;
		this.topElement = el.findParentByClassName('current');
		var xy = el.getXY(this.topElement);
		var x = parseInt(xy.x + (el.offsetWidth/2), 10);
		var y = parseInt(xy.y + (el.offsetHeight/2), 10);
		var half = Math.round(this.radius/2);
		this.canvas = document.createElement('canvas');
		this.canvas.width = this.radius;
		this.canvas.height = this.radius;
		this.canvas.setCSS( {
				left: (x - half) + 'px',
				top: (y - half) + 'px',
				position: 'absolute',
				zIndex: '30',
				webkitTransitionProperty: 'opacity',
				webkitTransitionDuration: '250ms',
				pointerEvents: 'none'
			} );
		this.topElement.appendChild(this.canvas);
		var context = this.canvas.getContext("2d");
		var gradient = context.createRadialGradient(half,half,0,half,half,half);
		gradient.addColorStop(0, 'rgba(255,255,255,1)');
		gradient.addColorStop(0.6, 'rgba(255,255,255,0.05)');//8
		gradient.addColorStop(0.8, 'rgba(255,255,255,0)');//9
		context.fillStyle = gradient;
		context.fillRect(0,0,this.radius,this.radius);
		this.visible = true;
	}
};

base.extend(base.GlowOnTouch.prototype, {
	removeGlow: function() {
		this.canvas.remove();
		this.canvas.setCSS({opacity:0});
		this.visible = false;
		this.canvas = null;
		this.element.glow = null;
		return null;
	}
});

base.extend(Object.prototype, {
	toQueryString: function () {
		var params = [];
		for (var key in this) {
			var str = encodeURIComponent(key) + '=';
			var value = (Object.prototype.toString.call(object[key])==='[object Array]') ? this[key].join(',') : this[key];
			str += encodeURIComponent(value);
			params.push(str);
		}
		return params.join('&');
	}
});


base.extend(Array.prototype, NodeList.prototype, {   ///(TouchList?TouchList.prototype:null), {

	first: function () { return this[0]; },

	each: function (iterator, context) {
		if (context) { iterator = iterator.bind(context); }
		try {
			var c = this.length, i = 0;
			while (i < c) {
				if (typeof this[i] !== 'function') { iterator(this[i]); }
				i++;
			}
		} catch(evnt) { throw evnt; }
		return this;
	},

	eachAfter: function (iterator, interval, dir, context, callback) {
		iterator = (context) ? iterator.bind(context) : iterator;
		callback = callback || function () {};
		var tmp = dir || 1;
		var c = this.length, i = 0;
		if (tmp < 0) {
			var t = c;
			c = i;
			i = (t === 0) ? 0 : t - 1;
		}
		try {
			var eachIterator = setInterval(function () {
				if (i === c) {
					if (callback) { callback(); }
					clearInterval(eachIterator);
					return this;
				} else {
					if (typeof this[i] !== 'function') { iterator(this[i]); }
					i = i + tmp;
				}
			}.bind(this), (interval || 1000));

		} catch(evnt) { throw evnt; }
	}
});

base.extend(Function.prototype, {
	bind: function () {
		var method = this, args = Array.prototype.slice.call(arguments), object = args.shift();
		return function () {
			return method.apply(object, args.concat(Array.prototype.slice.call(arguments)));
		};
	},

	bindAsEventListener: function () {
		var method = this, args = Array.prototype.slice.call(arguments), object = args.shift();
		return function (event) {
			return method.apply(object, [event || window.event].concat(Array.prototype.slice.call(arguments)));
		};
	}
});

var io = function (url, options) {
	this.options = {
		method: 'post',
		asynchronous: true,
		contentType: 'application/x-www-form-urlencoded',
		encoding: 'UTF-8',
		params: {},
		format: 'text',
		sanitizeJSON: false
	};
	base.extend(this.options, options || {});
	this.options.method = this.options.method.toLowerCase();
	this.xhr = new XMLHttpRequest();
	var params = this.options.params.toQueryString();
	if (this.options.method.toLowerCase() == 'get') {
		url += (url.indexOf('?') >= 0) ? '&' : '?' + params;
	}
	try {
		this.xhr.open(this.options.method, url, this.options.asynchronous);
		this.xhr.onreadystatechange = this._onStateChange.bind(this);
		var headers = {
			'X-Requested-With': 'XMLHttpRequest',
			'Accept': '*/*'
		};
		if (this.options.method == 'post') {
			headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : '');
		}
		for (var name in headers) {
			this.xhr.setRequestHeader(name, headers[name]);
		}
		this._body = this.options.method.toLowerCase() == 'post' ? (this.options.postBody || params) : null;
		this.xhr.send(this._body);

	} catch(evnt) { console.error('request error', evnt); }
};

base.extend(io, {
	response: function (response, format, sanitize) {
		switch (format.toLowerCase()) {
		case 'xml':
			var xml = (response.responseXML) ? response.responseXML : new String('');
			return xml;
			//break;
		case 'json':
			var json = response.responseText;
			if (sanitize) { json = json.sub(/^\/\*-secure-([\s\S]*)\*\/\s*$/, '#{1}'); }
			try {
				var str = response.responseText.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
				if ((/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str)) { return eval('(' + json + ')'); }
			} catch(evnt) { console.error('json eval error', evnt); }
			break;
		case 'text':
			return response.responseText;
			//break;
		default:
			return response;
			//break;
		}
	},

	Events: ['Uninitialized', 'Connected', 'Requested', 'Processing', 'Complete', 'Failure', 'Success']
});

base.extend(io.prototype, {
	_onStateChange: function () {
		var readyState = this.xhr.readyState;
		if (readyState === 4) {
			this._complete = true;
			var res = new io.response(this.xhr, this.options.format, this.options.sanitizeJSON);
			if (this.options.onFailure || this.options.onSuccess) {
				var successCode = (!this.xhr.status) ? 5 : (this.xhr.status >= 200 && this.xhr.status < 300) ? 6 : 5;
				(this.options['on' + io.Events[successCode]] ||
				function () {})(res);
			}(this.options['on' + io.Events[readyState]] ||
			function () {})(res);
		} else if (readyState > 1 && !((readyState == 4) && this._complete)) {
			(this.options['on' + io.Events[readyState]] ||
			function () {})();
		}
	}
});

var Template = function (template) {
	this.template = template;
	return this.template;
};

base.extend(Template.prototype, {
	parse: function (object) {
		this.data = object;
		this.output = this.template;
		this.output = this.output.replace(/#\{(\w+)\}/g, this._replaceCallback.bind(this));
		return this.output;
	},
	_replaceCallback: function (match1, match2) {
		return this.data[match2] || '';
	}
});

var Element = function (type, atts, content) {
	this.el = document.createElement(type);
	for (var attr in atts) { this.el.setAttribute(attr, atts[attr]); }
	this.el.innerHTML = content || '';
	return this.el;
};

base.extend(String.prototype, {
	isBlank: function () {
		return /^\s*$/.test(this);
	},
	toHTML: function () {
		var div = document.createElement('div');
		div.innerHTML = this;
		return div.childNodes;
	},
	trim: function (match) {
		var re = new RegExp(match) || new RegExp(/\s+?/);
		return this.replace(re, '');
	}
});

base.extend(Event.prototype, {
	stop: function () {
		this.preventDefault();
		this.stopPropagation();
	}
});

base.extend(HTMLElement.prototype, {
	data: {},
	glow: null,
	setCSS: function (css) {
		for (var prop in css) { this.setStyle(prop, css[prop]); }
		return this;
	},
	getStyle: function (prop) {
		return document.defaultView.getComputedStyle(this,"").getPropertyValue(prop);
	},
	setStyle: function (prop, val) {
		this.style[prop] = val;
		return this;
	},
	hasAttribute: function (attributeName) {
		return this.getAttribute(attributeName) !== undefined;
	},
	hasClass: function (className) {
		var re = base.getCachedRegEx(className);
		return re.test(this.className);
	},
	addClass: function (className) {
		if (!this.hasClass(className)) {
			this.className += ' ' + className.trim();
		}
		return this;
	},
	removeClass: function (className) {
		if (this.hasClass(className)) {
			var re = base.getCachedRegEx(className);
			this.className = this.className.replace(re, ' ');
			if (this.hasClass(className)) {
				this.removeClass(className);
			} else {
				this.className = this.className.trim();
			}
		}
		return this;
	},
	toggleClass: function (className) {
		if (this.hasClass(className)) {
			this.removeClass(className);
		} else {
			this.addClass(className);
		}
	},
	getXY: function () {  /// top?
		var valueX = 0, valueY = 0;
		var element = this;
		do {
			valueY += element.offsetTop || 0;
			valueX += element.offsetLeft || 0;
			element = element.offsetParent;
		} while (element);
		var offset = [valueX, valueY];
		offset.x = valueX;
		offset.y = valueY;
		return offset;
	},
	remove: function (el) {
		el = el || this;
                (el.parentNode).removeChild(el);
	},
	clean: function() {
		var isEmpty = /\S/;
		this.each(function(el) {
			for (var cur=el.firstChild,next ; cur ; cur=next ) {
				next = cur.curSibling;
				if (cur.nodeType == 3 && !isEmpty.test(cur.nodeValue))
					{ el.removeChild(cur); }
			}
		});
		return this;
	},
	fire: function (eventName, data) {
		base._fire(this, eventName, data);
	},
	findParentByTagName: function(localName) {
		localName = localName.toLowerCase();
		node = this;
		while (node && (node.nodeType != 1 || node.localName.toLowerCase() != localName))
		{
			node = node.parentNode;
		}
		return node;
	},
	findParentByClassName: function(className) {
		var node = this;
		while (node && typeof(node.hasClass) == 'function' && !node.hasClass(className))
		{
			node = node.parentNode;
		}
		return (node?node:this);
	},	
	showGlow: function(el) {
		if(this.glow === null)
		{
			this.glow = new base.GlowOnTouch( this );
		}
		setTimeout(function(self) {
				if (self.glow)
				{
					self.glow = self.glow.removeGlow();
					glowing = false;
				}
			}, 450, this);
	}
});

base.extend(document, {
	_loaded: false,
	fire: function (eventName, data) { base._fire(this, eventName, data); }
});


(function () {

	var timer;

	function fireContentLoaded() {
		if (!document._loaded && timer) { timer=window.clearInterval(timer); }

		document.fire('dom:loaded');
		document._loaded = true;
	}

	if (typeof console !== 'object') {
		console = {
			log: function () {},
			alert: function () {},
			warn: function () {},
			info: function () {},
			time: function () {},
			timeEnd: function () {},
			error: function () {}
		};
	}

	if (typeof document.querySelectorAll === 'function') {
		window.$ = function (selector, context) {
			context = ( !! context) ? context : document;
			base.selectors = true;
			return context.querySelectorAll(selector);
		};
		document.addEventListener('DOMContentLoaded', fireContentLoaded, false);
	} else {
		console.warn('Selectors API not available. Falling back on Sizzle query selector.');

		var sizzle = new Element('script', {
			type: 'text/javascript',
			src: base.sizzleSrc
		});
		
		sizzle.onload = function () {
			window.$ = Sizzle;
			if (base.browser.webkit && parseInt(base.browser.version,10) < 525) {
				console.info('DOMContentLoaded not available. Falling back on document.readyState.');
				timer = window.setInterval(function () {
					if (/loaded|complete/.test(document.readyState)) {
						fireContentLoaded();
					}
				}, 0);
			} else {
				document.addEventListener('DOMContentLoaded', fireContentLoaded, false);
			}
		};

		sizzle.onerror = function () {
			console.error('Horrible failure getting Sizzle. That sucks.');
		};

		document.getElementsByTagName('head')[0].appendChild(sizzle);
	}


	//----------------------------------------------------------


	var	touchId = 1,
		touchA = null,
		mouseDown = false;

	var	types = {
		"mousedown": "touchstart",
		"mousemove": "touchmove",
		"mouseup": "touchend"
	};

	function mapMouseEvents(evnt)  ///mapMouseEvents - no gestures
	{
		if (evnt.type == "mousemove" && !mouseDown)
		{
			return;
		}

		var touch = {
				identifier: (evnt.type == "mousedown") ? ++touchId : touchId,
				target: evnt.target,
				clientX: evnt.clientX,
				clientY: evnt.clientY,
				pageX: evnt.clientX,
				pageY: evnt.clientY,
				screenX: evnt.screenX,
				screenY: evnt.screenY
			};

		if (evnt.type == "mousedown")
		{
			touchA = touch;
			mouseDown = true;
		}

		var touchEvent = document.createEvent("MouseEvents");
		touchEvent.initMouseEvent(
			types[evnt.type], 
			true, 
			true, 
			evnt.view, 
			evnt.detail, 
			evnt.screenX, 
			evnt.screenY, 
			evnt.clientX, 
			evnt.clientY, 
			evnt.ctrlKey, 
			evnt.altKey, 
			evnt.shiftKey, 
			evnt.metaKey,
			0,
			null
		);
		touchEvent.touches = [touch];
		touchEvent.targetTouches = [touchA];
		touchEvent.changedTouches = [touch];
		touchEvent.scale = 1;
		touchEvent.rotation = 0;

		if (evnt.type == "mouseup")
		{
			mouseDown = false;
		}

		evnt.target.dispatchEvent(touchEvent);
	}

	document.addEventListener("mousedown", mapMouseEvents, false);
	document.addEventListener("mousemove", mapMouseEvents, false);
	document.addEventListener("mouseup", mapMouseEvents, false);


	//----------------------------------------------------------


	var nodes = [],
		startX = 0,
		startY = 0,
		startTime = null,
		deltaX = 0,
		deltaY = 0,
		deltaT = 0,
		tapped = null;

	function touchstart(evnt)
	{
		//evnt.stop();
		startX = evnt.changedTouches[0].clientX;
		startY = evnt.changedTouches[0].clientY;
		startTime = (new Date()).getTime();
		deltaX = 0;
		deltaY = 0;
		deltaT = 0;
		tapped = evnt.target;
		console.log(" tapped: "+tapped+" / "+typeof(tapped)+"  ["+tapped.id+"]");
	}

	function touchmove(evnt)
	{
		//evnt.stop();
	}

	function touchend(evnt)
	{
		//evnt.stop();
		if (tapped !== null)
		{
			if ((deltaT=(new Date()).getTime()-startTime) < 250)
			{
				deltaX = evnt.changedTouches[0].clientX - startX;
				deltaY = evnt.changedTouches[0].clientY - startY;
				var absX = Math.abs(deltaX);
				var absY = Math.abs(deltaY);

				// Check for swipe, ...if so, left or right.
				if (absX > absY && absX > 25 && tapped.hasClass('swipable'))
				{
					tapped.fire('swipe', {direction: (deltaX < 0) ? 'left' : 'right', deltaX: deltaX, deltaY: deltaY });
				}
				// ...else check for tap (as in click)
				else if (absX < 25 && absY < 25)
				{
					tapped.fire('tap');
				}
			}
		}
	}

	// ...allow through for user to use as needed.
	function gesturestart(evnt)
	{
		console.log(" sys gesturestart");
	}

	function gesturechange(evnt)
	{
		console.log(" sys gesturechange");
	}

	function gestureend(evnt)
	{
		console.log(" sys gestureend");
	}

	document.addEventListener("touchstart", touchstart, false);
	document.addEventListener("touchmove", touchmove, false);
	document.addEventListener("touchend", touchend, false);
	document.addEventListener("gesturestart", gesturestart, false);
	document.addEventListener("gesturechange", gesturechange, false);
	document.addEventListener("gestureend", gestureend, false);
})();
