/** section: libraries
 * class DropDown
 *
 *  v0.0.1-dev
 *  for dropdown menus, based on dropline code
 *  (c) 2010 Maciej Taranienko <matt@prayam.com>
 *  All rights reserved
 *
 *  TODO:
 *  - opened items handling
 *
**/
DropDown = Class.create({

	// globals
	menu: undefined,

	switchers: [],
	targets: [],

	active: null,

	timerObj: undefined,
	timerRunning: false,

	timerPeriod: 1.5,
	switchDuration: 0.3,

	isSwitching: false,

	// init
	initialize: function(menu) {

		if($(menu)) {

			// set
			this.menu = $(menu);

			// switchers
			this.switchers = this.menu.down(0).select('li.droppable');
			 //alert(this.switchers.inspect() + '\nlength: ' + this.switchers.length);

			// targets
			this.switchers.each(function(switcher) { this.targets.push(switcher.select('ul')[0]) }.bind(this));
			 //alert(this.targets.inspect() + '\nlength: ' + this.targets.length);

			// register behaviours
			if(this.switchers.length == this.targets.length) { this.register(); }
			else { throw('DropDown.js: incorrect switchers/targets amount for #' + menu); }
		}
	},

	register: function() {

		this.applyEvents();
	},

	applyEvents: function() {

		// switchers hovers
		applySwitchers = function(switcher, index) {

			switcher.observe('mouseover', this.showTarget.bindAsEventListener(this, index));
			switcher.observe('mouseout', this.timerStart.bindAsEventListener(this));
		}
		this.switchers.each(applySwitchers.bind(this));

		// target link hovers
		applyTargetLinks = function(link) {

			link.observe('mouseover', this.timerStop.bindAsEventListener(this));
			link.observe('mouseout', this.timerStart.bindAsEventListener(this));
		}
		this.targets.each( function(target) { target.select('li a').each( applyTargetLinks.bind(this) ) }.bind(this) );	// ** over-binded structure:S
	},

	// helper method
	/*
	clearHovers: function() {

		this.switchers.invoke('removeClassName', 'selected');
	},
	*/

	// events
	showTarget: function(e, index) {

		// kill timer
		this.timerStop();

		// ** add more rules here
		if(index != this.active && !this.isSwitching) {

			// set that we're switching now
			this.isSwitching = true;

			// do highlights
			/*
			this.clearHovers();
			Event.element(e).addClassName('selected');
			*/

			// fast switch if anything is active, or effect-switch if not
			if(this.active != null) {

				this.targets[this.active].hide();
				this.targets[index].show();

				this.active = index;
				this.isSwitching = false;
			}
			else {

				switchingDone = function() {

					this.active = index;
					this.isSwitching = false;
				}
				new Effect.Appear(this.targets[index], {
				
					duration: this.switchDuration,
					afterFinish: switchingDone.bind(this)
				});
			}
		}
	},

	hideTarget: function() {

		if(this.active != null && !this.isSwitching) {

			this.isSwitching = true;

			hidingDone = function() {

				this.active = null;
				//this.clearHovers();

				this.isSwitching = false;
			}

			new Effect.Fade(this.targets[this.active], {

				duration: this.switchDuration,
				afterFinish: hidingDone.bind(this)
			});
		}
	},

        /**
	 * timer methods
	**/
	timerStart: function() {

		if(!this.timerRunning) {

			this.timerRunning = true;
			this.timerObj = new PeriodicalExecuter(this.hideTarget.bind(this), this.timerPeriod);
		}
	},
	timerStop: function() {

		if(this.timerRunning) {

			this.timerRunning = false;
			this.timerObj.stop();
		}
	}
});

/**
 * class Slideable
 *
 *  v0.1.6-beta
 *
 *  for defining all the slideable objects
**/
Slideable = Class.create({

	// config
	duration: undefined,

	// elements
	handle: undefined,
	target: undefined,

	// controllers
	isAnimating: false,
	isOpened: undefined,	// why not false? check :>

	// callbacks
	beforeOpen: undefined,
	afterClose: undefined,
	afterOpen: undefined,

	/* base methods */
	initialize: function(params) {

		// required params
		if(!params.handle) throw('Slideable: No handler attribute defined');
		if(!params.target) throw('Slideable: No target attribute defined');

		// assing elements
		this.handle = (Object.isElement(params.handle)) ? params.handle : $(params.handle);
		this.target = (Object.isElement(params.target)) ? params.target : $(params.target);
		if(!this.handle) throw('Slideable: No handle element found at #' + params.handle);
		if(!this.target) throw('Slideable: No target element found at #' + params.target);

		// assign callbacks
		if(Object.isFunction(params.beforeOpen)) this.beforeOpen = params.beforeOpen;
		if(Object.isFunction(params.afterOpen)) this.afterOpen = params.afterOpen;
		if(Object.isFunction(params.afterClose)) this.afterClose = params.afterClose;

		// duration
		this.duration = (params.duration) ? params.duration : 1.0;

		// register
		this.register();
	},

	register: function() {

		this.getState();
		this.handle.observe('click', this.toggle.bindAsEventListener(this));
	},

	getState: function() {

		$(this.target).visible() ? this.isOpened = true : this.isOpened = false;
	},

	/* open/close */
	toggle: function(e) {

		if(!this.isAnimating) {

			this.isAnimating = true;
			this.isOpened ? this.close() : this.open();
		}
		e.stop();
	},

	open: function() {

		if(this.beforeOpen) this.beforeOpen();

		Effect.SlideDown(this.target, {

			duration: this.duration,
			afterFinish: function() {

				// update state
				this.isAnimating = false;
				this.isOpened = true;

				// bind callbacks
				if(this.afterOpen) this.afterOpen.call();
			}.bind(this)
		});
	},

	close: function() {

		Effect.SlideUp(this.target, {

			duration: this.duration,
			afterFinish: function() {

				// update state
				this.isAnimating = false;
				this.isOpened = false;

				/// bind callbacks
				if(this.afterClose) this.afterClose.call();
			}.bind(this)
		});
	}

});

/**
 * class Slideable.Menu
 *
 * v0.2.0-dev
 *
 * KNOWN ISSUES:
 *  It's possible to mess things up on rapid clicking. Most likely cause of no local switching/notswitching controller.
 *  It looks like a small design flaw in Slideable/Slideable.Menu interaction. Don't give a shit anyway.. :)
**/
Slideable.Menu = Class.create({

	// elms
	elmWrapper: undefined,

	// helpers
	handles: [],
	targets: [],
	slideables: [],

	// controllers
	slideOpen: undefined,

	// init
	initialize: function(wrapper) {

		if($(wrapper)) {

			this.elmWrapper = $(wrapper);

			// store handlers/targets elements
			this.handles = this.elmWrapper.select('a.dropdown_handle');
			this.targets = this.elmWrapper.select('div.dropdown_target');

			// iterate and create in a hacky way;
			// WARNING, improperly built HTML will crash eveyrhting up
			this.handles.each(function(handle, index) {

				// assign target
				var target = this.targets[index];	// ** very experimental and possibly failable on bad HTML

				// idendify elements
				[handle, target].invoke('identify');

				// and create a new object for them
				this.slideables[index] = new Slideable({
					handle: handle.id,
					target: target.id,
					duration: 0.3,

					// callbacks
					beforeOpen: function() {

						// close previous slide
						if(this.slideOpen !== undefined) this.slideables[this.slideOpen].close();

					}.bind(this),

					afterOpen: function() {

						// assign new open slide
						this.slideOpen = index;

					}.bind(this),

					// ** consider if it's really neccessary here
					afterClose: function() {

						// remove opened if somehow we close the only opened slide
						if(this.slideOpen == index) this.slideOpen = undefined;
					}.bind(this)
				});

			}.bind(this));

		}
		else throw('Slideable.Menu: Wrapper id not found at #' + wrapper);
	}
});

/**
 * class inputHovers
 *
 * v0.2-bugged
 *
 * TODO:
 * - improve it with plugin input fields handling
 *   (like datepicekr fields, swfupload fields and tinymce/fck fields)
 *
**/
InputHovers = Class.create({

	// elements
	elmTds: [],
	elmInputs: [],

	// init
	initialize: function() {

		// get valid inputs (recurrently)
		this.elmTds = $$('td.input');
		this.elmTds.each(function(td) {

			// fix: actually catch both textareas and inputs ;)
			td.adjacent('input, textarea').each(function(input) {
				this.elmInputs.push(input);
			}.bind(this));

		}.bind(this));

		// apply blurs
		this.elmInputs.invoke('observe', 'focus', function(e) {

			e.stop();
			elmParent = Event.element(e).up(0);
			if(elmParent.nodeName == "TD" && !elmParent.hasClassName('inputFocused')) elmParent.addClassName('inputFocused');

		}.bindAsEventListener(this));

		this.elmInputs.invoke('observe', 'blur', function(e) {

			e.stop();
			elmParent = Event.element(e).up(0);
			if(elmParent.nodeName == "TD" && elmParent.hasClassName('inputFocused')) {

				elmParent.removeClassName('inputFocused');
				elmParent.removeClassName('inputHovered');
			}


		}.bindAsEventListener(this));

		// apply hovers
		this.elmInputs.invoke('observe', 'mouseover', function(e) {

			e.stop();
			elmParent = Event.element(e).up(0);
			if(elmParent.nodeName == "TD" && !elmParent.hasClassName('inputFocused')) elmParent.addClassName('inputHovered');

		}.bindAsEventListener(this));

		this.elmInputs.invoke('observe', 'mouseout', function(e) {

			e.stop();
			elmParent = Event.element(e).up(0);
			if(elmParent.nodeName == "TD" && !elmParent.hasClassName('inputFocused')) elmParent.removeClassName('inputHovered');


		}.bindAsEventListener(this));
	}


});

/**
 * class ConfirmLink
 * v0.3.3-beta
 *
 * requires:
 * - Dialog class 2.1
 *
 * TODO:
 * - consider giving all ajax callback functions some parameter
 * - make non :json responses return the request object as well
 * - extend to work not only with <a href=""> thingys
 * - consider moving to some DialogExtensions among with MessageDialog class?
 
**/
ConfirmLink = Class.create({

	// values
	href: null,
	question: null,
	loadingText: null,

	// ajax stuff here
	ajax: undefined,

	// elements
	elmLink: undefined,

	// events
	evLinkClick: undefined,

	// controllers
	allowRedirection: false,

	// init
	initialize: function(linkId, params) {

		// grab valid link element
		if(Object.isElement(linkId)) this.elmLink = linkId;
		else if($(linkId)) this.elmLink = $(linkId);

		// proceed only for valid link reference
		if(this.elmLink) {

			// parse params
			this.elmLink = $(linkId);
			this.question = (params.question) ? params.question : 'Czy jesteś pewien?';	// UTF-8
			this.loadingText = (params.loadingText) ? params.loadingText : 'Wczytywanie...';

			// depply analyse ajax and override default callbacks
			this.ajax = (params.ajax) ? params.ajax : undefined;
			if(this.ajax.onSuccess) this.onAjaxSuccess = this.ajax.onSuccess;
			if(this.ajax.onFail) this.onAjaxFail = this.ajax.onFail;
			this.ajax.params = (this.ajax.params) ? this.ajax.params : null;

			// non-ajax calls
			if(!this.ajax) this.href = (params.href) ? params.href : this.elmLink.href;

			// store event object
			this.evLinkClick = this.onClickLink.bindAsEventListener(this);

			// apply hovers
			this.elmLink.observe('click', this.evLinkClick);
		}
		// silent fail
	},

	// helper
	preloadAndGo: function() {

	},

	// events
	onClickLink: function(e) {

		e.stop();

		// popup dialog
		Dialogs.confirm(this.question, function() {

			// close confirm and open preloading dialog
			Dialogs.close();

			modal = new Dialog({

				content: function() {

					elmP = Builder.node('p', { style: 'text-align: center;' }, [
						Builder.node('br'),
						this.loadingText
					]);

					// hacky image preloader here :>
					elmImg = Builder.node('img', { src: GlobalSettings.ConfirmLink.preloadingImage, alt: this.loadingText } );
					elmImg.observe('load', function(e) {

						// set that we're allowed to redirect now
						this.allowRedirection = true

					}.bindAsEventListener(this));

					elmP.insert({ top: elmImg });
					return elmP;

				}.bind(this),
				close: {
					link:false,
					overlay:false,
					esc:false
				},
				afterOpen: function() {

					new PeriodicalExecuter(function(pe) {

						// check if we're allowed for redirection and redirect if yes!
						if(this.allowRedirection) {

							pe.stop();

							if(this.ajax) {

								new Ajax.Request(this.ajax.url, {

									parameters: this.ajax.params,
									onComplete: function(request) {

										// accept :json attribute
										// ** enhace it in the future to seek for stuff like :json.url
										if(this.ajax.expect == ':json') request.responseText.isJSON() ? this.onAjaxSuccess(request.responseText.evalJSON()) : this.onAjaxFail.call();
										// old, unchanged .call() logic not to violate reverse compatibility
										else (request.responseText == this.ajax.expect) ? this.onAjaxSuccess.call() : this.onAjaxFail.call();
									}.bind(this)
								});
							}
							
							// basic href calls
							else window.location.href = this.href;
						}
					}.bind(this), 1);
				}.bind(this)
			});
			modal.open();

		}.bind(this));
	},

	// default ajax behaviour callbacks	(to be overriden by class parameters function sent in object)
	onAjaxSuccess: function() {

		reloadBrowserWindow();
	},
	onAjaxFail: function() {

		// ** consider putting all these in Dialog closing callback

		Dialogs.close();

		new Effect.Highlight($('page_main'), {

			startcolor: GlobalSettings.blinkColorError,
			endcolor: GlobalSettings.blinkColorEndDefault
		});
	},

	// public methods
	destroy: function() {
		this.elmLink.stopObserving('click', this.evLinkClick);
	}
});


// Global functions
function reloadBrowserWindow() {

	Try.these(
		window.location.reload(),       // 'true' tells the browser not to use their cache (?)
		history.go(0),
		window.location.href=window.location.href
	);
}
function unixTime() {

	return parseInt(new Date().getTime().toString().substring(0, 10))
}
function fixUrlDecode(str) {

	[['+', ' '], ['&nbsp;', ' '], ['&amp;', '&'], ['&#039;', "'"]].each(function(pattern) { str = str.gsub(pattern[0], pattern[1]); });
	return str;
}
function randString(length) {

	// define our charracter set for randomizing
	var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';

	// prepare the output var
	var randomstring = '';

	// run a loop and build it
	for (var i=0; i<length; i++) {
		var rnum = Math.floor(Math.random() * chars.length);
		randomstring += chars.substring(rnum,rnum+1);
	}
	return randomstring;
}

// Common runtime (mostly layout elements)
Event.observe(window, "load", function() {

	// Top menu
	new DropDown('header_menu');
});

