/**
 * Garp Frontend library
 * @author Peter Schilleman | Grrr
 * @author Harmen Janssen | Grrr
 * @package Garp
 */
Garp = typeof Garp == 'undefined' ? { } : Garp;
BASE = typeof BASE == 'undefined' ? '/' : BASE;

// http://jdbartlett.github.com/innershiv | WTFPL License
Garp.innerShiv = (function() {
	var d, r;
	
	return function(h, u) {
		if (!d) {
			d = document.createElement('div');
			r = document.createDocumentFragment();
			/*@cc_on d.style.display = 'none';@*/
		}
		
		var e = d.cloneNode(true);
		/*@cc_on document.body.appendChild(e);@*/
		e.innerHTML = h.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
		/*@cc_on document.body.removeChild(e);@*/
		
		if (u === false) return e.childNodes;
		
		var f = r.cloneNode(true), i = e.childNodes.length;
		while (i--) f.appendChild(e.firstChild);
		
		return f;
	}
}());

/**
 * Utility function. Binds receiverObj's properties to senderObj's properties 
 * @param {Object} receiverObj
 * @param {Object} senderObj
 * @return {Object} receiverObj
 */
Garp.apply = function(receiverObj, senderObj){
	for (var i in senderObj) {
		receiverObj[i] = senderObj[i];
	}
	return receiverObj;
};

/**
 * Utility function. Binds receiverObj's properties to senderObj's properties if not already present
 * @param {Object} receiverObj
 * @param {Object} senderObj
 * @return {Object} receiverObj
 */
Garp.applyIf = function(receiverObj, senderObj){
	for (var i in senderObj) {
		if (!receiverObj.hasOwnProperty(i)) {
			receiverObj[i] = senderObj[i];
		}
	}
	return receiverObj;
}

/**
 * Utility string function: use a simple tpl string to format multiple arguments
 * 
 * example:
 * var html = Garp.format('<a href="${1}">${2}</a>"', 'http://www.grrr.nl/', 'Grrr Homepage');
 * 
 * @param {String} tpl  template
 * @param {String} ...n input string(s)
 * @return {String}
 */
Garp.format = function(tpl){
	for (var res = tpl, i = 1, l = arguments.length; i < l; i++) {
		res = res.replace(new RegExp("\\${" + i +"\\}","g"), arguments[i]);
	}
	return res;
};

/**
 * Utility function each
 * Calls fn for each ownProperty of obj. fn(property, iterator, obj)
 * 
 * @param {Object} obj
 * @param {Function} fn 
 */
Garp.each = function(obj, fn){
	for(var i in obj){
		if (obj.hasOwnProperty(i)) {
			fn(obj[i], i, obj);
		}
	}
}

/**
 * Creates a Delegate function
 * @param {Function} function
 * @param {Object} scope
 */
Garp.createDelegate = function(fn, scope){
	return function(){
		fn.apply(scope, arguments);
	};
}

/**
 * 
 */
Garp.parseQueryString = function(str, decode) {
    var str = str || window.location.search;
	if (decode) {
		str = unescape(str);
	}
    var objURL = {};
    str.replace(
        new RegExp( "([^?=&]+)(=([^&]*))?", "g" ),
        function( $0, $1, $2, $3 ){
                objURL[ $1 ] = $3;
        }
    );
    return objURL;
};

/**
 * @class Observable
 */
Garp.Observable = function(){
	/**
	 * Creates a global event handler
	 *
	 * @example Garp.on('onbeforeload', function(){}, scope);
	 *
	 * @param {String} event
	 * @param {Function} handler
	 * @param {Object} scope
	 */
	this.on = function(event, handler, scope){
		scope = typeof scope == 'undefined' ? this : scope;
		
		this.on.events = typeof this.on.events == 'undefined' ? [] : this.on.events;
		this.on.events[event] = typeof this.on.events[event] == 'undefined' ? [] : this.on.events[event];
		
		this.on.events[event].push({
			'eventName': event,
			handler: handler,
			scope: scope
		});
	};
	
	this.addListeners = function(eventsConfig){
		Garp.each(eventsConfig, function(event){
			this.on(event.event, event.fn, event.scope);
		});
	}
	
	/**
	 * Removes an event handler
	 * @param {String} event
	 * @param {Function} handler
	 * @param {Object} scope
	 */
	this.un = function(event, handler, scope){
		scope = typeof scope == 'undefined' ? this : scope;
		this.on.events[event].pop({
			'eventName': event,
			handler: handler,
			scope: scope
		});
	};
	
	/**
	 * Fires a global event
	 * @param {String} eventName
	 */
	this.fireEvent = function(eventName, options){
		if(typeof this.on.events == 'undefined'){
			return;
		}
		if(!options){
			options = {};
		}
		Garp.each(this.on.events[eventName], function(obj){
			typeof obj.handler == 'function' ? obj.handler.call(obj.scope, options) : true;
		});
	};
	
};

/**
 * @class Transition
 * @param {Object} Browsebox reference
 * @param {String} Transition name (internal function reference)
 */
Garp.Transition = function(bb, transitionName){
	
	this.crossFade = function(){
		bb.on('beforeload', function(){
			
			this.copy = this.elm.clone().addClass('crossfade');
			this.shim = this.elm.clone().addClass('shim');
			
			this.copy.insertAfter(this.elm);
			this.shim.insertAfter(this.elm);
			
			var pos = this.elm.position();
			this.copy.css({
				top: pos.top,
				left: pos.left,
				width: this.elm.width(),
				height: this.elm.height(),
				position: 'absolute'
			});
			this.elm.hide();
		}, this);
		bb.on('afterload', function(){
			this.shim.remove();
			this.elm.fadeIn(this.speed);
			var scope = this;
			this.copy.fadeOut(this.speed, function(){
				$(this).remove();
			});
		}, this);
	};
	
	this.slideUp = function(){
		bb.on('beforeload', function(){
			this.elm.slideUp(this.speed);
		}, this);
		bb.on('afterload', function(){
			this.elm.slideDown(this.speed);
		}, this);
	};
	
	this.slideLeft = function(){
		bb.on('beforeload', function(options){
			this.elm.wrap('<div class="x-wrap" />');
			this.elm.parent('div.x-wrap').css({
				position: 'relative',
				overflow: 'hidden'
			});
			this.elm.css('position','relative').animate({
				left: options.direction * 900
			}, this.speed);
		}, this);
		bb.on('afterload', function(options){
			var scope = this;
			this.elm.stop().css('left', (1-options.direction) * 900).animate({
				left: 0
			}, this.speed, null, function(){
				scope.elm.unwrap();
			});
		}, this);
	};
	
	this.init = function(){
		$('#' + bb.id).children().wrap('<div />');
		this.elm = $('#' + bb.id, ' div');
		this.speed = 'slow';
		this[transitionName].call(this);
	};
	this.init();
};
	

/**
 * @class Browsebox.
 * The browsebox is a simple interface to paging content.
 * 
 * @inherits Observable
 * @param {Object} config
 */
Garp.Browsebox = function(config){

	// Browsebox extends Garp.Observable
	Garp.apply(this, new Garp.Observable());

	// Defaults:
	this.cache = true;
	this.PRELOAD_DELAY = 850;
	this.BROWSEBOX_URL = 'g/browsebox/';
		
	// Apply config
	Garp.apply(this, config);
	
	// private
	this.timer = null;
	this.cacheArr = [];
	
	/**
	 * Puts the processed data in the BB, sets up the links and fires afterload event
	 * @param {String} data
	 */
	this.afterLoad = function(data, options){
		$('#' + this.id).html(Garp.innerShiv(data));
		this.hijackLinks();
		this.fireEvent('afterload', options);
		this.preloadNext();
	};
		
	/**
	 * Searches for images, and waits for them to load first
	 * @param {String} data
	 * @param {Bool} preloadOnly
	 * @param {Number} direction
	 */
	this.processData = function(data, preloadOnly, dir){
		var imgs = $('img', data);
		var count = imgs.length;
		var scope = this;
		
		function checkStack(){
			count--;
			if (count <= 0 && !preloadOnly) {
				scope.afterLoad(data,{
					direction: dir
				});
			}
		}

		// See if there are any images. If so, wait for the load/error event on them.
		if (count > 0) {
			var I = [];
			$(imgs).each(function(i, img){
				I[i] = new Image();
				if (!preloadOnly) {
					$(I[i]).bind('load', checkStack).bind('error', checkStack);
				}
				I[i].src = $(img).attr('src');
			});
		} else{
			if (!preloadOnly) {
				this.afterLoad(data,{
					direction: dir
				});
			}
		}
		
	};
	
	/**
	 * Loads a page. Fires beforeload and afterload events
	 * @param {String} chunk
	 * @param {String} [optionally] filters 
	 * @param {Number} direction
	 */
	this.loadPage = function(chunk, filters, dir){
		var url = BASE + this.BROWSEBOX_URL;
		url += this.id + '/' + chunk + '/' + 
			(filters ? filters : 
				(this.filters  ? this.filters : '')
			);
		
		this.fireEvent('beforeload', {
			direction: dir
		});
		
		if (this.cacheArr[url]) {
			this.processData(this.cacheArr[url], false, dir);
		} else {
			var scope = this;
			$.ajax({
				url: url,
				cache: this.cache,
				success: function(data){
					scope.cacheArr[url] = data;
					scope.processData(data, false, dir);
				}
			});
		}
	};
	
	this.preloadNext = function(){
		var url = $('.bb-next a', '#' + this.id).attr('href');
		
		if (url) {
			var queryComponents = Garp.parseQueryString(url, true);
			var chunk = queryComponents['bb[' + this.id + ']'];
			var url = BASE + this.BROWSEBOX_URL;
			url += this.id + '/' + chunk + '/' +
			(this.filters ? this.filters : '');
			
			
			if (this.cacheArr[url]) {
				this.processData(this.cacheArr[url], true);
			} else {
				var scope = this;
				this.timer = setTimeout(function(){
					$.ajax({
						url: url,
						cache: scope.cache,
						success: function(data){
							scope.cacheArr[url] = data;
							scope.processData(data, true);
						}
					});
				}, this.PRELOAD_DELAY);
			}
		}
	};
	
	/**
	 * Sets up previous & next buttons
	 */
	this.hijackLinks = function(){
		var scope = this;
		$('.bb-next a, .bb-prev a', '#' + this.id).unbind().bind('click', function(e){
			e.preventDefault();
			if(scope.timer){
				clearTimeout(scope.timer);
				scope.timer = false;
			}
			if (scope.rememberState) {
				scope.setHash($(this).attr('href'));
			}
			var queryComponents = Garp.parseQueryString($(this).attr('href'), true);
			var chunk = queryComponents['bb[' + scope.id + ']'];
			var dir = $(this).parent('.bb-next').length ? 1 : -1;
			scope.loadPage(chunk, null, dir);
			return false;
		});
	};
	
	/**
	 * Sets up a new location.hash
	 * @param {String} hash
	 */
	this.setHash = function(hash){
		hash = hash.substr(hash.indexOf('?') + 1, hash.length);
		window.location.hash = hash;
	};
	
	/**
	 * Tries to find a previous location.hash state. Loads the according page
	 * @TODO: expand this to find if we do not already have this state (location.hash v.s. loaction.search)
	 */
	this.getDejaVu = function(){
		var hashComponents = Garp.parseQueryString(window.location.hash.replace('#','','g'), true);
		if(hashComponents['bb[' + this.id +']']){
			var chunk = hashComponents['bb[' + this.id + ']'];
			this.loadPage(chunk, null, null);		
		}
	};
	
	/**
	 * Init
	 */
	this.init = function(){
		this.transition = new Garp.Transition(this, this.transition);
		this.hijackLinks();
		this.getDejaVu();
		
		if (!this.hideSpinner) {
			this.on('beforeload', function(){
				$('#' + this.id + ' .spinner').css({
					display: 'block'
				});
			}, this);
			
			this.on('afterload', function(){
				$('#' + this.id + ' .spinner').css({
					display: 'none'
				});
			}, this);
		}
		
		if(this.autoRun){
			var scope = this;
			var speed = typeof this.speed != 'undefined' ? this.speed : 800;
			this.interval = setInterval(function(){
				$('#' + this.id + '.bb-next a').click();
			}, speed);
		}
	};

	this.init();
};

/**
 * Inline label module. For labels that look as if they are the value of an input field
 */
Garp.inlineLabels = {
	/**
	 * Find correct labels on the page and display 'em 'inline'
	 * @param Mixed $elements Optional elements, if none, "label.inline" will be used.
	 */
	init: function(elements) {
		var self = this;
		elements = elements || 'label.inline';
		$(elements).each(function() {
			var thisLabel = $(this);
			var input = $('#'+thisLabel.attr('for'));
			input.focus(function() {
				self.focus.call(input, thisLabel);
			}).blur(function() {
				self.blur.call(input, thisLabel);
			});
			
			// 'cause browsers remember certain form values, there needs to be one manual check.
			setTimeout(function() {
				if ($(input).val()) {
					self.focus.call(input, thisLabel);
				}
			}, 1000);
		});
	},
	/**
	 * Focus event handler on inputs
	 */
	focus: function(theLabel) {
		theLabel.addClass('hidden');
	},
	/**
	 * Blur event handler on inputs
	 */
	blur: function(theLabel) {
		if (!$(this).val()) {
			theLabel.removeClass('hidden');
		}
	}
};
Garp.inlineLabels.init();

/**
 * Validator object
 * @version 1.1
 */
Garp.Validator = (function() {
	/**
	 * Private methods
	 */
	// validation functions. The key is the className that triggers the function
	var rules = {
		required: function(elm) {
			if (!elm.val()) {
				Garp.Validator.triggerError(elm.attr('id'), __('%s is een verplicht veld.'));
			}
		},
		noBMP: function(elm) {
			if (elm.val()) {
				var e = elm.val();
				e = e.substring(e.length-4, e.length);
				e = e.toUpperCase();
				if (e === '.BMP') {
					Garp.Validator.triggerError(elm.attr('id'), __('Geen geldig bestandsformaat.'));
				}
			}
		},
		email: function(elm) {
			var email = /([\w]+)(\.[\w]+)*@([\w\-]+\.){1,5}([A-Za-z]){2,4}$/;
			if (!email.test(elm.val())) {
				Garp.Validator.triggerError(elm.attr('id'), __('%s is geen geldig e-mailadres.'));
			}
		},
		password: function(elm) {
		},
		repeatPassword: function(elm) {
			if (elm.attr('rel') && $('#'+elm.attr('rel'))) {
				var theOtherPwdField = $('#'+elm.attr('rel'));
				if (theOtherPwdField.length) {
					if (theOtherPwdField.val() !== elm.val()) {
						Garp.Validator.triggerError(elm.attr('id'), __('De wachtwoorden komen niet overeen.'));
					}
				}
			}
		},
		requiredIf: function(elm) {
			if (elm.attr('rel') && $('#'+elm.attr('rel'))) {
				var otherField = $('#'+elm.attr('rel'));
				var otherFieldFilled = false;
				if (otherField.attr('type') == 'checkbox') {
					otherFieldFilled = otherField.is(':checked');
				} else {
					otherFieldFilled = otherField.val();
				}
				if (otherFieldFilled && !elm.val()) {
					var verb = otherField.attr('type') === 'checkbox' ? 'aangevinkt' : 'ingevuld';
					var str = __('Als ### is '+verb+', is %s verplicht.');
					str = str.replace('###', $('label[for="'+otherField.attr('id')+'"]').text());
					Garp.Validator.triggerError(elm.attr('id'), str);
				}
			}
		}
	};
	
	/**
	 * Public methods
	 */
	return {
		// Validate the form according to the rules above
		validateForm: function(formId) {
			// loop thru all the different input types
			var fields = $('#'+formId+' input, #'+formId+' select, #'+formId+' textarea');
			$('#'+formId).submit(function(e) {
				// reset errorMessages to an empty array
				Garp.Validator.errorMessages = {};
				fields.each(function() {
					var self = $(this);
					for (var i in rules) {
						if (self.hasClass(i)) {
							rules[i](self);
						}
					}
				});
				var valid = true;
				for (var i in Garp.Validator.errorMessages) {
					valid = false;
					break;
				}
				if (valid) {
					return true;
				} else {
					var errorTxt = '';
					for (var j in Garp.Validator.errorMessages) {
						errorTxt += Garp.Validator.errorMessages[j]+'<br>';
					}
					$('#'+formId+' p.error').html(errorTxt);
					e.preventDefault();
					return false;
				}
			});
		},
		// add custom rules, with custom functions if required
		pushRule: function(rule, fn) {
			rules[rule] = fn ; //.push(rule);
		},
		// add errors
		triggerError: function(id, msg) {
			var label = $('label[for='+id+']');
			Garp.Validator.errorMessages[id] = msg.replace('%s', label.text());
		}
	};
})();

/**
 * Retrieves an object housing in an array by looking for one of its keys and possibly it's value
 * @param {Array} array of objects
 * @param {String} key
 * @param {String} val [optional]
 * 
 * @return {Array | Object} 
 */
Garp.getBy = function(arr, key, val){
	var out = [];
	for(var k in arr){
		var item = arr[k];
		if(item[key]){
			if (!val || (val && item[key] == val)) {
				out.push(item);
			}
		}
	}
	return out.length == 1 ? out[0] : out;
}

/**
 * Stub i18n
 * @param {Object} s
 */
function __(s) { return s; }

/**
 * Garp.flashMessage
 */
Garp.flashMessage = (function(){
	
	/**
	 * Override Garp.flashMessage.delay to have a different delay
	 */
	this.delay = 6000; // 6 seconds
	this.elm = null;
	 
	/**
	 * Override Garp.flashMessage.animate to have a different animation:
	 * Eg:
	 *	Garp.flashMessage.animate = function(){
	 *		this.elm.fadeOut();
	 *	}
	 */
	this.animate = function(){
		this.elm.hide();
	};
	
	// private:
	var flash = this;
	$(function(){
		flash.elm = $('#flashMessage');
		flash.elm.bind('click', function(){
			flash.animate();
		});
		setTimeout(function(){
			flash.animate();
		},flash.delay);
	});
	
	return this;
})();

/**
 * Google Maps
 */
Garp.buildGoogleMap = function(elm, config){
	var map = new google.maps.Map(elm, {
		mapTypeId: google.maps.MapTypeId[config.maptype.toUpperCase()],
		navigationControl: true,
		navigationControlOptions: {
			style: google.maps.NavigationControlStyle.SMALL
		},
		mapTypeControlOptions: {
			mapTypeIds: ['']
		},
		scaleControl: true,
		center: new google.maps.LatLng(parseFloat(config.center.lat), parseFloat(config.center.lng)),
		zoom: parseInt(config.zoom)
	});
	
	if(config.markers){
		for(var i in config.markers){
			var marker = config.markers[i];
			
			var gMarker = new google.maps.Marker({
				map: map,
				title: marker.title,
				position: new google.maps.LatLng(parseFloat(marker.lat), parseFloat(marker.lng))
			});
			
		}		
	}
};

$(function(){
	$('.g-googlemap').each(function(){
		
		var mapProperties = Garp.parseQueryString($(this).attr('src'));
		var center = mapProperties.center.split(',');
		Garp.apply(mapProperties,{
			width: $(this).attr('width'),
			height: $(this).attr('height'),
			center: {
				lat: center[0],
				lng: center[1]
			},
			markers: mapProperties.markers ? mapProperties.markers.split('|') : false
		});
		for (var i in mapProperties.markers){
			var m = mapProperties.markers[i].split(',');
			mapProperties.markers[i] = {
				lat: m[0],
				lng: m[1],
				title: m[2] ? m[2] : ''
			}
		}
		
		$(this).wrap('<div class="g-googlemap-wrap"></div>');
		var wrap = $(this).parent('.g-googlemap-wrap').width(mapProperties.width).height(mapProperties.height);
		Garp.buildGoogleMap(wrap[0], mapProperties);	
	});
});

/**
 * YouTube ready: remove iframe with alternative YouTube rendering. Chrome renders both otherwise.
 */
function onYouTubePlayerReady(){
	$(function(){
		$('iframe.youtube-player').remove();
	});
}

/**
 * Snippet edit links
 */
$(function(){
	setTimeout(function(){
		var elms = [];
		(function walk(elm){
			if (elm.childNodes && elm.childNodes.length > 0) {
				for (var e in elm.childNodes) {
					walk(elm.childNodes[e]);
				}
			} else {
				elms.push(elm);
			}
		})($('body')[0]);
		for (var e in elms) {
			var elm = elms[e];
			var token = '//garp-snippet//';
			if (elm.nodeType == 8 && elm.nodeValue.indexOf(token) > -1) {
				var snippet = elm.nextSibling;
				var url = BASE + 'g/content/admin/?model=Snippet&id=' + elm.nodeValue.replace(token, '');
				//$(snippet).wrap('<div style="position:relative;">')
				var link = $('<a href="' + url + '" title="edit"><img src="' + BASE + 'media/images/garp/icons/pencil.png" /></a>').insertBefore(snippet).css({
					position: 'absolute',
					zIndex: '999999',
					padding: '4px',
					width: '20px',
					height: '20px',
					margin: '-14px 0 0 -14px'
					//border: '0'
				});
				(function(link){
					function mouseOut(){
						$(link).css({
							opacity: .5,
							border: '2px #fff outset',
							background: '#ddd'
						});
					}
					$(link).bind('mouseenter', function(){
						$(link).css({
							opacity: 1,
							border: '2px #fff outset',
							background: '#ddd'
						});
					}).bind('mouseleave', mouseOut);
					mouseOut();
				})(link);
			}
		}
	},1000);
});
