/*

	Script:			Modalog 1.0  -  http://modalog.com/
	Dependencies:	Prototype.js (1.5+) & Animator.js
	Created By:		Ryan Wilke  -  ryan@modalog.com
	
	Table of Contents:
		- initialize()
		- setStyles()
		- open()
		- loadContent()
		- update()
		- size()
		- resize()
		- close()

*/


var Modalog = Class.create();

Modalog.prototype = {
	
	// CONSTANTS -------------------------------------------------------
	IS_IE: (navigator.appVersion.match('MSIE')) ? (true) : (false),
	IS_IE6: (navigator.appVersion.match('MSIE 6.0')) ? (true) : (false),
	
	// DEFAULT OPTIONS -------------------------------------------------
	options: {
		id: "uniqueModalId",					// Give your modal a name so you can access it with CSS.
		width: 600,							    	// end width (in pixels)
		height: 400,						    	// end height (in pixels)
		opacity: 0.55,						   	// final opacity of the overay
		transition: "stretch",				// (drop, drawer, grow, stretch, genie)
		overlayEffect: "easeOut",			// (smooth, snappy, easeIn, easeOut)
		openingEffect: "smooth",			// (smooth, snappy, easeIn, easeOut, elastic, veryElastic, bouncy, veryBouncy)
		resizingEffect: "smooth",			// (smooth, snappy, easeIn, easeOut, elastic, veryElastic, bouncy, veryBouncy)
		closingEffect: "snappy",			// (smooth, snappy, easeIn, easeOut, elastic, veryElastic, bouncy, veryBouncy)
		startingScale: 1.3,					  // 1 (exact width & height), 2 (half size), 3 (1/3 size), ect...
		draggable: false,						  // (true, false)
		overlayCloseOnClick: false,		// (true, false)
		resetAfterOpen: true,			   	// (true, false)
		ajax: true,								    // (true, false)
		contentStyle: {},						  // use in action
		modalStyle: {},						    // use in action
		overlayStyle: {},					   	// use in action
		beforeStart: function(){},		// use in action
		afterFinish: function(){},		// use in action
		beforeClose: function(){},		// use in action
		afterClose: function(){},		 	// use in action
		animatorOptions: {}				  	// use in action
	},
	
	// EFFECT OPTIONS (used in the default options) --------------------
	effect: {
		smooth: { transition: Animator.tx.easeOut, duration: 250, interval: 15 },
		snappy: { transition: Animator.tx.easeInOut , duration: 1, interval: 15 },
		easeIn: { transition: Animator.tx.easeIn, duration: 1250, interval: 15 },
		fastEaseIn: { transition: Animator.tx.easeIn, duration: 350, interval: 15 },
		easeOut: { transition: Animator.tx.easeOut, duration: 1250, interval: 15 },
		fastEaseOut: { transition: Animator.tx.strongEaseOut, duration: 700, interval: 15 },
		elastic: { transition: Animator.makeElastic(2), duration: 1000, interval: 15 },
		veryElastic: { transition: Animator.makeElastic(3), duration: 1450, interval: 15 },
		bouncy: { transition: Animator.makeBounce(3), duration: 2250, interval: 15 },
		veryBouncy: { transition: Animator.makeBounce(5), duration: 3000, interval: 15 }
	},

	// FUNCTION THAT RUNS ON PAGE LOAD ---------------------------------
	initialize: function(htmlPartial, options) {
		if (options) { this.setOptions(options); } // Reset Options
		
		// Open the container if this.hide() was called
		if ($('modalogContainer')) {
			this.container.show();
			return;
		}
		
		// Init the html used for the modalog
		var modalogHTML = [];
		modalogHTML.push('<div id="modalogContainer" style="display: none">');
		modalogHTML.push('	<div id="modalogOverlay"></div>');
		modalogHTML.push('	<div id="modalogBox" class="closed loading">');
		modalogHTML.push('  	<div id="' + this.options.id + '">');
		modalogHTML.push('	  	<div id="modalogChrome">');
		modalogHTML.push('	  		<a id="modalogCloseButton" href="#">Close</a>');
		modalogHTML.push('	  	</div>');
		modalogHTML.push('	  	<div id="modalogContent"></div>');
		modalogHTML.push('	  </div>');
		modalogHTML.push('	</div>');
		modalogHTML.push('</div>');
		
		// Insert the html at the end of the <body>
		new Insertion.Bottom(document.body, modalogHTML.join(''));
		
		// Store the DOM objects in variables for use later
		this.container = $('modalogContainer');
		this.overlay = $('modalogOverlay');
		this.modal = $('modalogBox');
		this.content = $('modalogContent');
		
		// Initialize some variables that we can use later
		this.defaultOptions = this.options;
		this.windowHeight = this.Window.getHeight();
		this.windowWidth = this.Window.getWidth();
		this.scrollTop = this.Window.getYScroll();
		this.scrollLeft = this.Window.getXScroll();
		this.documentHeight = this.Document.getHeight();
		this.documentWidth = this.Document.getWidth();
		
		// Make the overlay start out completely opaque
		this.overlay.setStyle({ opacity: 0 });
		
		// Make the modal draggable
		this.draggable();
		
		// Init the close button
		$('modalogCloseButton').onclick = function(){
			this.destroy();
			return false;
		}.bind(this);
		
		if (this.options.overlayCloseOnClick) {
			this.overlay.onclick = function(){ this.destroy(); }.bind(this);
			this.overlay.title = "Click to temporarily hide this window.";
		}
		
		// Go get the content
		this.update(htmlPartial);
	},
	
	// FUNCTION TO EXTEND THE DEFAULT OPTIONS --------------------------
	setOptions: function(options) {
		Object.extend(this.options, options);
	},

	// FUNCTION TO APPLY STYLES THAT YOU'VE SET ------------------------
	setStyles: function() {
		this.content.setStyle(this.options.modalStyle);
		this.modal.setStyle(this.options.modalStyle);
		this.overlay.setStyle(this.options.overlayStyle);
	},
	
	buildPosition: function(){
		// Calculate the starting position and size
		if (this.options.transition == "drawer")
		{
			var startingMarginTop = (this.Window.getYScroll() + 75) + "px";
			var startingMarginLeft = ((this.options.width / 2) * -1) + "px";
			var startingWidth = this.options.width + "px";
			var startingHeight = "150px";
		}
		else if (this.options.transition == "grow" || this.options.transition == "stretch")
		{
			var startingMarginTop = ((this.Window.getYScroll() + (this.Window.getHeight() / 2)) - ((this.options.height / this.options.startingScale) / 2)) + "px";
			var startingMarginLeft = (((this.options.width / this.options.startingScale) / 2) * -1) + "px";
			var startingWidth = (this.options.width / this.options.startingScale) + "px";
			var startingHeight = (this.options.height / this.options.startingScale) + "px";
		}
		else if (this.options.transition == "drop")
		{
			var startingMarginTop = (this.Window.getYScroll() - this.options.height) + "px";
			var startingMarginLeft = ((this.options.width / 2) * -1) + "px";
			var startingWidth = this.options.width + "px";
			var startingHeight = this.options.height + "px";
		}
		
		// Set the position of the modal
		this.modal.setStyle({
			position: 'absolute',
			top: 0,
			left: '50%',
			zIndex: 760,
			marginTop: startingMarginTop,
			marginLeft: startingMarginLeft,
			height: startingHeight,
			width: startingWidth
		});
		
		// Set the height of the container
		this.container.setStyle({ height: this.documentHeight + 'px' });
		
		// Set the height of the overlay if its IE6
		if (this.IS_IE6){
			this.overlay.setStyle({ height: this.documentHeight + 'px' });	
		}
	},
	
	// ANIMATOR EFFECTS ------------------------------------------
	buildTransitions: function(){
		// Build out the transitions that can be set in this.options.transition
		this.transition = {
			drop:
				"width: " + this.options.width + "px; " +
				"height: " + this.options.height + "px; " +
				"margin-left: " + ((this.options.width / 2) * -1) + "px; " +
				"margin-top: " + (this.scrollTop) + "px; ",
			drawer:
				"width: " + this.options.width + "px; " +
				"height: " + this.options.height + "px; " +
				"margin-left: " + ((this.options.width / 2) * -1) + "px",
			grow:
				"height: " + this.options.height + "px; " + 
				"margin-top: " + ((this.scrollTop + (this.windowHeight / 2)) - (this.options.height / 2)) + "px; " +
				"width: " + this.options.width + "px; " +
				"margin-left: " + (this.options.width / 2 * -1) + "px",
			stretch: {
				width:
					"width: " + this.options.width + "px; " +
					"margin-left: " + (this.options.width / 2 * -1) + "px",
				height: 
					"height: " + this.options.height + "px; " + 
					"margin-top: " + ((this.scrollTop + (this.windowHeight / 2)) - (this.options.height / 2)) + "px; "
			}
		};
		
		// Hook up the overlay to animator
		this.overlayAnimation = new Animator(this.effect[this.options.overlayEffect]).addSubject(
			new NumericalStyleSubject(this.overlay, "opacity", this.overlay.getStyle('opacity'), this.options.opacity)
		);
				
		// Finally! Let's hook it up with animator
		if (this.options.transition == "stretch")
		{
			// Build on to the animationOptions
			var widthOptions = {};
			if (!this.modal.hasClassName('open')){
				Object.extend(widthOptions, this.effect[this.options.openingEffect]);
			} else {
				Object.extend(widthOptions, this.effect[this.options.resizingEffect]);
			}
			
			var heightOptions = {};
			Object.extend(heightOptions, widthOptions);
			Object.extend(heightOptions, { onComplete: function(){ this.load(); }.bind(this) });
			
			// The width animation
			var resizeWidth = new Animator(widthOptions).addSubject(
				new CSSStyleSubject(this.modal, this.transition[this.options.transition].width)
			);
			
			// The height animation
			var resizeHeight = new Animator(heightOptions).addSubject(
				new CSSStyleSubject(this.modal, this.transition[this.options.transition].height)
			);

			// Chain them together
			this.modalAnimation = new AnimatorChain([resizeWidth, resizeHeight]);
		}
		else
		{
			// Build onto the animation options
			if (!this.modal.hasClassName('open')){
				Object.extend(this.options.animatorOptions, this.effect[this.options.openingEffect]);
			} else {
				Object.extend(this.options.animatorOptions, this.effect[this.options.resizingEffect]);
			}
			Object.extend(this.options.animatorOptions, { onComplete: function(){ this.load(); }.bind(this) });
			
			// Create the animation
			this.modalAnimation = new Animator(this.options.animatorOptions).addSubject(
				new CSSStyleSubject(this.modal, this.transition[this.options.transition])
			);
		}
	},
	
	animate: function(){
		if (!this.modal.hasClassName('open')){
			// Initialize the starting position of the modal
			this.buildPosition();
			
			// Show the container
			this.container.show();
		}
		
		// Initialize the animation we'll be using on the modal
		this.buildTransitions();
		
		// Play the animations
		this.overlayAnimation.play();
		this.modalAnimation.play();
	},
	
	// GETS THE CONTENT USED IN THE MODAL ----------------------------------------
	update: function(htmlPartial, options) {
		if (options) { this.setOptions(options); } // Reset Options
		
		// Run any additional passed in functions before we get things kicked off
		this.options.beforeStart();
		
		// Clear out the modal
		this.clear();
		
		// Do the ajax call
		if (this.options.ajax) {
			new Ajax.Request(htmlPartial, {
				method: "get",
				onSuccess: function(request) {
					this.modalContent = request.responseText;
					this.animate();
				}.bind(this)
			});
		}
	},
	
	// LOADS THE CONTENT AFTER THE ANIMATION IS COMPLETE --------------------------
	load: function(){
		// Remove the loading icon
		this.modal.removeClassName('loading').addClassName('done').addClassName('open');	
		
		// Add in the content
		this.content.update(this.modalContent);
		
		// Update the modal id (in case it changed)
		this.modal.down().id = this.options.id;
		
		// Run the function that you've set
		this.options.afterFinish(this);
		
		// Reset the functions
		if (this.options.resetAfterOpen) {
			this.setOptions({ afterFinish: function(){}, beforeStart: function(){} });
		}
	},
	
	// FUNCTION THAT ANIMATES & SIZES THE MODAL ---------------------------------
	resize: function(options) {
		if (options) { this.setOptions(options); } // Reset Options
		
		// Replay the animation
		this.animate();
	},
	
	// USE THE SAME EFFECTS TO CLOSE THE MODAL -----------------------------------
	close: function(options){
		if (options) { this.setOptions(options); } // Reset Options
		
		// Play the animations
		this.overlayAnimation.reverse();
		this.modalAnimation.reverse();
	},
	
	// LITERALLY GET RID OF THE MODAL FROM THE DOM -------------------------------
	destroy: function(options){
		if (options) { this.setOptions(options); } // Reset Options
		
		// Remove the container html from the DOM
		this.container.remove();
	},
	
	// FUNCTION THAT CLEARS OUT THE MODAL ------------------------------
	clear: function() {
		// this.content.update('');
		this.modal.removeClassName('four_oh_four').removeClassName('done').addClassName('loading');
	},
	
	// USES THE DRAGGABLE CLASS FOR DRAGGING THE MODAL ---------------------------
	draggable: function(){
		if (this.options.draggable) {
			if (typeof Draggable != 'undefined') {
				new Draggable(this.modal, { handle: 'modalogChrome' });
			} else {
				throw("You need to include the draggable.js library which is included with modalog or implement one of your choice.");
			}
		}
	},
	
	// HELPER CLASS TO GET WINDOW DIMENSIONS ---------------------------
	Window: {
		getHeight: function() {
			return (window.innerHeight) ? (window.innerHeight) : (document.documentElement.clientHeight);
		},
		
		getWidth: function() {
			return (window.innerWidth) ? (window.innerWidth) : (document.documentElement.clientWidth);
		},
		
		getYScroll: function() {
			return (self.pageYOffset) ? (self.pageYOffset) : (document.documentElement.scrollTop);
		},
		
		getXScroll: function() {
			return (self.pageXOffset) ? (self.pageXOffset) : (document.documentElement.scrollLeft);
		}
	},
	
	// HELPER CLASS TO GET DOCUMENT DIMENSIONS -------------------------
	Document: {	
		getHeight: function() {
			var scrollHeight = (window.innerHeight && window.scrollMaxY) ?
				(window.innerHeight + window.scrollMaxY) :
				(document.body.offsetHeight);
				
			var windowHeight = (window.innerHeight) ? (window.innerHeight) : (document.documentElement.clientHeight);
			
			return (scrollHeight < windowHeight) ? (windowHeight) : (scrollHeight);
		},
		
		getWidth: function() {
			var scrollWidth = (window.innerHeight && window.scrollMaxY) ?
				(document.body.scrollWidth) :
				(document.body.offsetWidth);
				
			var windowWidth = (window.innerWidth) ? (window.innerWidth) : (document.documentElement.clientWidth);
				
			return (scrollWidth < windowWidth) ? (windowWidth) : (scrollWidth);
		}
	}
};