/**
 * jQuery ajax extend with bruteCache (tm) functionality
 */
(function(){
	var _ajax = jQuery.ajax;
	var _cache = [];
	
	jQuery.extend({
		ajax: function(options){
		
			// cache is enabled by default:
			jQuery.extend(options, {
				cache: true,
				bruteCache: true
			});
			
			if (options && options.url && typeof pageTracker !== 'undefined') {
				pageTracker._trackPageview(options.url); // Google Analytics
			}
			
			if (options.bruteCache) {
				var key = '' + options.url + options.data + options.type;
				if (_cache[key]) {
					if (options.success) {
						options.success.apply(this, _cache[key]);
					}
					if (options.complete) {
						options.complete.apply(this, _cache[key]);
					}
					// do not make any calls
					return;
				} else {
					if (options.success) {
						var _success = options.success;
						options.success = function(){
							_cache[key] = arguments;
							_success.apply(this, arguments);
						}
					}
					if (options.complete) {
						var _complete = options.complete;
						options.complete = function(){
							_cache[key] = arguments;
							_complete.apply(this, arguments);
						}
					}
				}
			}
			_ajax(options);
		}
	});
})();

/**
 * Garp global namespace. For Garp functionality
 */
var Garp = typeof Garp !='object' ? {} : Garp;

/**
 * Garp BBInstance 
 */

Garp.BrowseboxInstance = {};

/**
 * @class Browsebox
 * @author Peet & Daaf - Grrr
 * 
 * Usage: var myBrowsebox = new Browsebox(config);
 * 
 * @param {Object} config
 * @param {config} url
 * @param {config} id
 * @param {config} speed
 * @param {config} autoCycle
 * @param {config} cycle
 * @param {config} lastPage
 * @param {config} page
 * @param {config} etc...
 */

Garp.Browsebox = function(cfg){

	var bb = {

		init: function(cfg){
			this.setCfg(cfg);
						
			var initialHtml = document.getElementById(cfg.id+'_content').innerHTML, //this.container.html(), 
				loaderBg = $('<div class="'+this.loaderBgClass+'"></div>'),
				loader = $('<div class="loader" style="z-index:11;"></div>'),
				container = this.container;

			container.html('');
			container.css('position', 'relative');

			for (var i = 0; i < 2; i++) {
				this.layer[i] = $('<div id="BB'+i+'" style="position:absolute;z-index:10;top:0;left:0;opacity:' + ( 1-i ) + ';" />').appendTo(container);
			}
			this.layer[0].html(initialHtml);
			var h = this.layer[0].height();
			container.css('height',h);

			loaderBg.css({
				'width': container.width(),
				'height': h//container.height()
			}).fadeTo(0,0,function(){
				loaderBg.hide();
				loaderBg.appendTo(container);
			});

			loader.css({
				'left': container.width() / 2,
				'top': h/2 //container.height() / 2
			}).fadeTo(0,0,function(){
				loader.hide();
				loader.appendTo(container);	
			});

			this.loader=loader;
			this.loaderBg=loaderBg;
			this.addHandlers();
			this.hideNonApplicableButtons();
			
			this.loadPage(this.onchange);
			this.onchange();
		},

		setCfg: function(cfg){			
		
			this.page = cfg.page || 1;
			this.speed = cfg.speed || 200;
			this.autoCycle = cfg.autoCycle || false;
			this.cycle = cfg.cycle || false;
			this.hideNavButtons = cfg.hideNavButtons || false;
			this.url = cfg.url;
			this.elID = cfg.id;
			this.pageSize = cfg.pageSize;
			this.queryParams = typeof cfg.queryParams!='undefined' ? '/query_params:'+cfg.queryParams : '';
			this.pages=cfg.pages; // page Cache
			this.chunk = cfg.chunk;
			this.chunkSize = cfg.chunkSize;
			this.lastPage = cfg.lastPage;
			this.layer = [];
			this.firstPage = 1;
			this.loaderBgClass= cfg.loaderBgClass;
			this.container = $('div.browsebox#' + cfg.id + ' div.browsebox_content');
			this.interval = null;
			this.isLoading = false;
			this.onchange = cfg.onchange;
		},

		addHandlers: function(){
			$('div.browsebox#' + this.elID + ' div.navbar ul li.prev').click(function(){
				bb.prev();
			});
			$('div.browsebox#' + this.elID + ' div.navbar ul li.next').click(function(){
				bb.next();
			});
		},

		hideNonApplicableButtons: function(){
			var prevButton = $('div.browsebox#' + this.elID + ' div.navbar ul li.prev'), 
				nextButton = $('div.browsebox#' + this.elID + ' div.navbar ul li.next');

			if (!this.autoCycle || !this.cycle) {

				if (this.page == this.firstPage) {
					prevButton.addClass('disabled');
				} else {
					prevButton.removeClass('disabled');
				}
				if (this.page == this.lastPage) {
					nextButton.addClass('disabled');
				} else {
					nextButton.removeClass('disabled');
				}
				
				if(this.page > this.firstPage){
					nextButton.removeClass('noprevious');
				} else {
					nextButton.addClass('noprevious');
				}
				if(this.page < this.lastPage){
					prevButton.removeClass('nonext');
				} else{
					prevButton.addClass('nonext');	
				}
				
			} else if(this.hideNavButtons){
				prevButton.addClass('disabled');
				nextButton.addClass('disabled');
			}
			
			if ($('div.browsebox#' + this.elID + ' div.navbar ul .disabled').length === 2) {
				$('div.browsebox#' + this.elID + ' div.navbar').css({visibility:'hidden'});
			}
			
		},

		prev: function(){
			if (this.isLoading) {
				return;
			}
			
			var button = $('div.browsebox#' + this.elID + ' div.navbar ul li.prev'), 
				s = this.speed;
			this.page--;
			if(this.cycle && this.page < this.firstPage){
				this.page = this.lastPage;
			} else if(this.page < this.firstPage){
				this.page = this.firstPage;
			}
			button.fadeTo(s, 0);
			this.loadPage(function(){
				button.fadeTo(s, 1);
			});
			if (this.autoCycle) {
				clearInterval(this.interval);
			}
		},

		next: function(){
			if (this.isLoading) {
				return;
			}

			var button = $('div.browsebox#' + this.elID + ' div.navbar ul li.next'), 
				s = this.speed;
			this.page++;
			if(this.cycle && this.page > this.lastPage){
				this.page = this.firstPage;
			} else if(this.page > this.lastPage){
				this.page = this.lastPage;
			}
			button.fadeTo(s, 0);
			this.loadPage(function(){
				button.fadeTo(s, 1);
			});
			if (this.autoCycle) {
				clearInterval(this.interval);
			}
		},

		setPage : function(layer,page,cback){
			var cb = function(){ // defer the callback function by 100msecs
				setTimeout(cback,100);
			};

			if (!cback) {
				throw "No callback for browsebox.setPage() specified.";
			}

			if (this.pages[page]) {
				$(layer).html(this.pages[page].html);

				var imgs = $('div.browsebox#' + this.elID + ' div.browsebox_content img');

				if (imgs && imgs.length && imgs.length > 0) {
					var counter = imgs.length;
					var img = [];
					$(imgs).each(function(i){

						var status = false, src = $(this).attr('src');

						img[i] = new Image();

						$(img[i]).error(function(){
							counter--;
							if (!status && counter == 0) {
								status = true;
								cb();
							}

						}).load(function(){
							counter--;
							if (!status && counter == 0) {
								status = true;
								cb();
							}
						});

						img[i].src = src;

					});

				} else {
					cb();
				}
			}
		},

		loadPage: function(pageLoadedCb){			
			if (this.isLoading) {
				return;
			}
			this.isLoading = true;
			this.toggle = !this.toggle;
			var scope = this,
			s = this.speed, 
			url = this.url + this.elID + '/' + this.page, 
			a = this.layer[this.toggle *1], 
			b = this.layer[!this.toggle *1],
			c = this.container, 
			elID = this.elID, 
			loader = this.loader, 
			loaderBg = this.loaderBg,
			autoCycle = this.autoCycle, 
			onchange = this.onchange,
			fadeBack = function(){
				b.fadeTo(s, 1,function(){
					scope.isLoading = false;
				});
				var h = b.height();
				if (!h) {
					h = a.height;
				}
				if (onchange) {
					onchange();
				}
				loader.animate({
					'opacity': 0,
					'top': (h / 2)
				}, s, null, function(){
					loader.hide();
					loaderBg.hide();
				});
				loaderBg.animate({'opacity':0},s);
				if(typeof stopLoading === 'function'){
					stopLoading();
				}
				c.animate({
					'height': h
				}, s);
			}; 
			this.hideNonApplicableButtons();		

			if (!this.autoCycle) {
				loader.css({
					'left': this.container.width() / 2,
					'top': this.container.height() / 2
				});
				loaderBg.css({
					'width': this.container.width(),
					'height': this.container.height()
				});
				loader.show();
				loaderBg.show();
				loader.fadeTo(s, 1);
				loaderBg.fadeTo(s,0.8);
				if(typeof animateLoading === 'function'){
					animateLoading();
				}
			}
			if (typeof this.pages[this.page] == 'undefined' || this.filter) { // not in cache, so make subsequent call
				this.chunk = Math.ceil(this.page / this.chunkSize);

				var scope = this;
				
				if(this.filter) {
					var qp = '/query_params:' + Garp.URLEncode(this.filter);
				} else if(this.queryParams){
					var qp = this.queryParams;
				} else {
					var qp = '';
				}
					
				$.getJSON(this.url + this.elID + '/' + this.chunk + qp, function(json){
					for (var p in json) {
						scope.pages[p] = json[p];
					}
					scope.setPage(b, scope.page,function(){
						fadeBack();
						a.fadeTo(s, 0, function(){
							a.html('');
							if (pageLoadedCb) {
								pageLoadedCb();
							}
						});						
					});
				});
			} else {
				this.setPage(b, this.page,function(){
					fadeBack();
					a.fadeTo(s, 0, function(){
						a.html('');
						if (pageLoadedCb) {
							pageLoadedCb();
						}
					});
				});

			}
		},
		
		query:function(filter){			
			this.page = 1;
			this.filter = JSON.encode(filter);
			this.isLoading = false;
		
			this.loadPage();
		},

		rndPage: function(){
			var r = Math.ceil(Math.random() * this.lastPage);
			return r;
		},

		autoLoad: function(){
			if (this.lastPage > 1) {
				do {
					var rnd = this.rndPage();
				} while (this.page == rnd);
				this.page = rnd;
				this.loadPage();
			}
		}
	};
	
	(function(){
		bb.init(cfg);
		if (bb.autoCycle && bb.autoCycle > 0) {
			bb.interval = setInterval(function(){
				bb.autoLoad();
			}, bb.autoCycle);
		}
	})();
	
	return bb;
};

Garp.flashMessage = function(msg) {
	$('#flashMessage').remove();
	// if it's not a string and not an array, it's gotta be an object
	if (typeof msg !== 'string' && !('push' in msg)) {
		var _msg = '';
		for (var i in msg) {
			_msg += msg[i]+' ';
		}
	} else {
		var _msg = msg;
	}
	var theClass = '';
	if ('flashMessageHtml' in Garp) {
		_msg = Garp.flashMessageHtml.open+_msg+Garp.flashMessageHtml.close;
		theClass = Garp.flashMessageHtml.cls;
	}
	$('body').prepend('<div class="'+theClass+'" id="flashMessage">'+_msg+'</div>');
	Garp.animateFlashMessage();
};

Garp.animateFlashMessage = function() {
	var fm = $('#flashMessage');
	if (fm.length) {
		fm.animate({
			'top': 0
		}, 150, 'swing', function(){
			fm.animate({
				'top': -30
			}, 150, 'swing', function(){
				fm.animate({
					'top': 0
				}, 150, 'swing', function(){
					fm.animate({
						'top': -10
					}, 150, 'swing', function(){
						var fn = function(){
							fm.animate({
								'top': 0
							}, 200, 'swing', function(){
								fm.animate({
									'top': -200
								}, 220, 'swing', function(){
									fm.remove();
								});
							});
						}
						fm.click(fn);
						setTimeout(fn, 5000);
					});
				});
			});
		});
	}
};
Garp.animateFlashMessage();

/**
 * 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);
						}
					}
				});
				// if the form is valid, encrypt the password fields
				var valid = true;
				for (var i in Garp.Validator.errorMessages) {
					valid = false;
					break;
				}
				if (valid) {
					Garp.Authenticator.encryptPasswords(formId);
					return true;
				} else {
					for (var j in Garp.Validator.errorMessages) {
						Garp.flashMessage(Garp.Validator.errorMessages[j]);
						break;
					}
					e.preventDefault();
					return false;
				}
			});
		},
		// add custom rules, with custom functions if required
		pushRule: function(rule, fn) {
			rules.push(rule);
			if (fn) {
				validationFunctions.push(fn);
			}
		},
		// add errors
		triggerError: function(id, msg) {
			var label = $('label[for='+id+']');
			Garp.Validator.errorMessages[id] = msg.replace('%s', label.text());
		}
	};
})();

Garp.SearchBox = function(selector, callbacks) {

	var elm = selector + ' input.text', 
		btn = selector + ' input.button', 
		res = selector + ' div#searchresult', 
		frm = selector + ' form',
		SPEED = 300,
		MINCHARS = 3,
		MINCHARMSG = '<p>'+__('Je zoekopdracht is te kort...')+'</p>';
	
	function toggleResultView(state){
		if (state) {
			$(res).slideDown(SPEED);
		} else {
			$(res).slideUp(SPEED);
		}
	}
	
	function submit(e) {
		if ($(elm).val() && $(elm).val().length >= MINCHARS) {
			$(res).html('');
			$(btn).addClass('loading');
			toggleResultView(true);
			$(btn).ajaxError(function(){
				$(btn).removeClass('loading');
				toggleResultView(false);
			});
			$.getJSON(BASE + 'garp/search/index/q:' + $(elm).val() +'/.json',null,function(response) {
					$(btn).removeClass('loading');
					$(res).slideUp({
						duration: 1,
						complete: function(){
							$(res).html(response.html);
							if (typeof callbacks !== 'undefined' && 'afterComplete' in callbacks) {
								callbacks.afterComplete($(res));
							}
							toggleResultView(true);
							$(document.body).one('click',function(){
								toggleResultView(false);
								return true;
							});
						}
					});
				}
			);
		} else {
			if ($(elm).val() && $(elm).val().length <= MINCHARS) {
				setTimeout(function(){
					$(document.body).one('click', function(){
						toggleResultView(false);
						return true;
					});
				},300);

				$(res).html(MINCHARMSG);
				toggleResultView(true);
			} else {
				toggleResultView(false);
				$(elm).focus();
				$(elm).val('');
			}
		}
		
		if (e && typeof e.preventDefault === 'function') {
			e.preventDefault();
		}
		return false;
	}
	
	$(frm).submit(submit);
	$(btn).click(submit);
	
	if (!$(res).length) {
		$(selector).append('<div style="display:none;" id="searchresult"></div>');
	}

};

/**
 * Authenticator object, hashes a password before submitting a form.
 * @version 2.0
 */
Garp.Authenticator = (function () {
	var encryption = 'md5';
	return {
		encryptPasswords: function(formId) {
			$('#'+formId+' input[type="password"]').each(function() {
				var self = $(this);
				if (self.val()) {
					self.parent().append(
						'<input type="hidden" name="'+
						self.attr('name')+
						'" value="'+
						Garp.Authenticator.encryptPassword(self.val(), encryption)+
						'">'
					);
					self.attr({
						name: ''
					});
					self.val('');
				}
			});
		},
		encryptPassword: function(plainPassword, encryptionMethod) {
			switch (encryptionMethod) {
				case 'sha1':
					return hex_sha1(plainPassword);
				case 'md5':
					return hex_md5(plainPassword);
				case 'none':
					return plainPassword;
				default:
					throw "No encryption method specified";
			}
		}
	};
})();

/**
 * Garp.CountDownArea
 * twitter-style countdown for textareas
 * @param {string} formSelector
 * @param {bool} allowBlank allow no comment (do not disable form submit)
 */
Garp.CountDownArea = function(formSelector, allowBlank) {
		var maxCharacters = 140,
			textarea = $(formSelector + ' textarea'),
			submit = $(formSelector + ' input[type="submit"]');
		allowBlank = allowBlank || false;
		
		function updateCounter() {
			$(formSelector + ' .counter').html(maxCharacters - textarea.val().length + '');
		}

		function checkLength() {
			var len = textarea.val().length;
			if (len > maxCharacters) {
				submit.attr({
					'disabled': 'disabled'
				});
				if (len > 0) {
					$(formSelector + ' .counter').addClass('surplus');
				}
			} else {
				submit.removeAttr('disabled');
				$(formSelector + ' .counter').removeClass('surplus');
			}
			if(!allowBlank && len == 0){
				submit.attr({
					'disabled': 'disabled'
				});
			}
			updateCounter();
		}
		
		textarea.keyup(checkLength)
				.keypress(checkLength)
				.blur(checkLength)
				.click(checkLength);
		if (!$(formSelector+' .counter').length) {
			textarea.before('<p class="counter" />');
		}
		
		checkLength();
};

/**
 * Generic hijackin' of requests
 * @author Harmen Janssen
 */
Garp.Hijax = {
	/**
	 * Callback method
	 * @var Array
	 */
	callbacks: [],
	/**
	 * Initialize behavior
	 * @return Object this
	 */
	init: function() {
		var _self = this;
		$(document).ready(function() {
			$('a.hijax').click(function(e) {
				var theElement = this;
				var theUrl = $(theElement).attr('href');
				theUrl += theUrl[theUrl.length-1] != '/' ? '/' : '';
				theUrl += 'ajax:1';
				$.getJSON(theUrl, {}, function(data) {
					if (_self.callbackExists(theElement)) {
						_self.executeCallback(theElement, data);
					} else {
						if (!data.message) {
							throw 'Key "message" required in returned data.';
						}
						Garp.flashMessage(data.message);
					}
				});
				e.preventDefault();
			});
		});
		return this;
	},
	/**
	 * Check if custom callback function exists
	 * @param HTMLElement $element The element
	 * @return Boolean
	 */
	callbackExists: function(element) {
		for (var i = 0; i < this.callbacks.length; i++) {
			if (this.callbacks[i].element == element) {
				return true;
			}
		}
		return false;
	},
	/**
	 * Execute custom callback function
	 * @param HTMLElement $element The element
	 * @param Object $data The data returned from the AJAX call
	 * @return Void
	 */
	executeCallback: function(element, data) {
		for (var i = 0; i < this.callbacks.length; i++) {
			if (this.callbacks[i].element == element) {
				this.callbacks[i].fn(data);
			}
		}
	},
	/**
	 * If something other than the display of a flash message needs to happen
	 * when a link is clicked, use this method to register a custom callback function.
	 * @param HTMLElement $element The element
	 * @param Function $fn The callback function
	 * @return Object this
	 */
	registerCallback: function(element, fn) {
		this.callbacks.push({
			element: element,
			fn: fn
		});
		return this;
	}
}.init();

Garp.makeBlockLinks = function(e) {
	e.live('click', function() {
		window.location = $(this).find('a:first').attr('href');
	}).live('mouseover', function() {
		window.status = $(this).find('a:first').attr('href');
		$(this).addClass('hover');
	}).live('mouseout', function() {
		window.status = '';
		$(this).removeClass('hover');
	});
};

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

/**
 * i18n module
 */
Garp.i18n = {};

function __(identifier) {
	if (LANGUAGE in Garp.i18n && identifier in Garp.i18n[LANGUAGE]) {
		return Garp.i18n[LANGUAGE][identifier];
	}
	return identifier;
}