/**
 * bgMax
 * =============================================================================
 * ajustes les dimensions d'une image en fonction de celles de la fenêtre
 * (conçu en particulier pour émuler des images de fond de page)
 * 
 * @author      Erwan Lefèvre <erwan.lefevre@gmail.com>
 * @copyright   Erwan Lefèvre 2009
 * @license     Creative Commons - Paternité 2.0 France - http://creativecommons.org/licenses/by/2.0/fr/
 * @version     v1.0 / 2010-07-02
 * @see			http://www.webbricks.org/bricks/bgMax/
 
 * @compatible	au 2 juillet 2010, compatibilité assurée pour :
 *				Firefox 1.5+, Internet Explorer 5.5+, Opéra, Safari, Chrome 
 */



/**
 * transfer
 * =============================================================================
 * retourne un objet contenant les propriétés et méthodes de l'objet /dest/,
 * complétées et/ou écrasées par celles de l'objet /source/
 *
 * @param       source       {object}        l'objet source
 * @param       dest         {object}        l'objet de destination
 * @return      {object}
 *
 */ 
function transfer (source, dest) {
    var prop, transfered={};
    for ( prop in dest ) { transfered[prop] = dest[prop]; }
    for ( prop in source ) { transfered[prop] = source[prop]; }
    return transfered; 
}



/**
 * byTN()
 * 
 * raccourci pour [element].getElementsByTagName()
 * retourne l'élément html de type /tagName/
 *
 * @param		tagName		{String}		Le type d'éléments recherché
 *
 * @returns		{HTMLCollection}
 * 
 * =============================================================================
 */
function byTN(tagName,container) {
	return (container||document).getElementsByTagName(tagName) ;
}





/** 
 * winDim v2.0.1, 2010-07-07
 * 
 * retourne les dimentions intérieurs de la fenêtre
 *
 * @returns		{Object}
 * 
 * =============================================================================
 */
function winDim() {
	var w,h,
		i = window,
		d = document,
		de = d.documentElement,
		db = d.body;
		
	if ( i.innerWidth ) { // autres que IE
		w = i.innerWidth;
		h = i.innerHeight;
	} else if ( de.clientWidth ) { // IE8
		w = de.clientWidth;
		h = de.clientHeight;
	}
	else { // IE6
		w = db.clientWidth;
		h = db.clientHeight;
	}

	return {'w':w, 'h':h} ;
}






/**
 * addEvent()
 * 
 * ajoute la fonction /fn/ à la pile de résolution de l'événement /evenType/ de
 * l'objet /obj/
 * 
 * merci à : http://www.scottandrew.com/weblog/articles/cbs-events
 *
 * @param		{Mixed}				obj			window, ou document, ou un élément HTML
 * @param		{String}			evType		type d'event (click, mouseover, mouseout, etc.…)
 * @param		{String}			fn			la fonction à ajouter
 * @param		{Boolean}			useCapture	"useCapture un booléen : true pour la phase de capture, ou false pour la phase de bouillonnement et la cible. On utilise quasiment toujours la valeur false." (cf : http://www.alsacreations.com/article/lire/578-La-gestion-des-evenements-en-JavaScript.html)
 * 
 * @returns		void
 * 
 * =============================================================================
 */
function addEvent (obj, evType, fn, useCapture){
	if (obj.addEventListener) { obj.addEventListener(evType, fn, useCapture); }
	else { obj.attachEvent("on"+evType, fn); }
}




/** 
 * setStyle v1.0
 * 
 * Modifie l'attribut style de l'élément /element/, selon le tableau associatif /styles/.
 *
 * @param           elem            {HTMLElement}           l'élément dont on veut modifier les styles
 * @param           styles          {Object}                définition (javascript) des styles à appliquer à l'élément
 * @returns         {void}
 * 
 * =============================================================================
 */
function setStyle(elem, styles) { // 58 octets
	for (var prop in styles) {
		elem.style[prop] = styles[prop];
	}
}




/**
 * setOpacity
 * 
 * règle l'opacité d'un élément
 *
 * @param       elem            {element}       l'élément à traiter
 * @param       value           {float}         valeur souhaitée (0=transparent, 1=opaque)
 * @return      string
 *
 * =============================================================================
 */
function setOpacity(elem, value) {
	value = (value == 1)?0.99999:value;

	elem.style.opacity = value;
	elem.style.filter = 'alpha(opacity=' + value*100 + ')';
	elem.style.MozOpacity = value;
	elem.style.KhtmlOpacity = value;
}




/**
 * fade()
 * 
 * permet d'effectuer une animation d'opacité sur un élément HTML
 *
 * @requires	setOpacity
 *
 * @param		{HTMLelement}	elem			l'élément HTML à animer
 * @param		{float}			to				l'opacité finale (0=transparent, 1=opaque)
 * @param		{float}			from			l'opacité initiale
 * @param		{integer}		duration		la durée de l'animation, en millisecondes
 * @param		{object}		options			tableau associatif contenant les options supplémentaires :
 * 														-   duration : integer - durée de l'animation, en millisecondes
 * 														-	frameRate : integer - nombre d'images par secondes
 * 														-	onFinish : function - fonction à appeler à la fin de l'animation
 * 
 * @returns		{void}
 * =============================================================================
 */
function fade (elem, to, from, options) {
	
	// initialisation des paramètre principaux
	this.elem = elem || document.body;
	this.to = to!==undefined ? to : 1;
	var st = this.elem.style;
	this.from = (from===undefined ? ( !st.opacity&&st.opacity!==0 ? (this.to>0?0:1) : parseFloat(st.opacity) ) : from);
	
	// initialisation des options
	options = options || {};
	this.duration = options.duration || 500;
	this.frameRate = options.frameRate || 30;
	this.onFinish = options.onFinish;
	
	// calculs pour découpage de l'animation en plusieurs étapes
	this.totalFrames = Math.ceil(this.duration/1000*this.frameRate);
	this.perFrame = (this.to-this.from)/this.totalFrames;
	this.frameNb = 0;
	
	// utile pour les setTimeout
	var self = this;



    /**
     * next
     * -------------------------------------------------------------------------
     * lance l'étape suivante de l'animation
     * 
	 * @returns		{void}
     * 
     */
    this.next = function () {
		this.prog = setTimeout (
			function(){self.frame();},
			1000/this.frameRate
		);
	};
		
	
	/**
	 * frame()
	 * -------------------------------------------------------------------------
	 * exécute une étape de l'animation
	 */
	this.frame = function () {
		// règle l'opacité de l'élément
		setOpacity(this.elem, this.from + this.perFrame*this.frameNb);
		
		// si anim terminée
        if ( this.frameNb===this.totalFrames ) {
				setOpacity(this.elem, this.to);
                if (typeof this.onFinish=='function') { setTimeout(this.onFinish,1); } // fonction callback
                }
				
        // sinon lancer le frame suivant
        else {
                this.frameNb++;
                this.next();
        }
	};
	
	
	// lancer la première étape de l'anim
	this.next();
}





/**
 * redimArea v1.2 / 2010-06-26
 * 
 * retourne les mesures /{w,h}/ de /src_w/ et /scr_h/, après redimentionnement homotétique
 * d'après les critères /mesures{max_w, min_w, max_h, max_h}/
 *
 * @param		{Integer}		src_w		largeur de la surface à redimentionner
 * @param		{Integer}		src_h		hauteur de la surface à redimentionner
 * @param		{Object}		mesures		mesures maximales et/ou minimales pour
 *											la largeur et/ou la hauteur
 *
 * @returns		{Object}
 *											
 * =============================================================================
 */
function redimArea (src_w, src_h, options) {
	
	// initialisations
	
		var max_w, min_w, max_h, min_h, // contraintes données en options
			round,						// option indiquant d'arrondir les dimensions obtenues
			wh, hw,						// rapports de proportion de la surface
			height, width;				// dimensions finales de la surface
		
		// mesures souhaitées
		options = options || {};
		max_w = options.max_w;
		min_w = options.min_w;
		max_h = options.max_h;
		min_h = options.max_h;
		
		// autres options
		round = options.round===undefined ? 1 : options.round; // pour rétrocompatibilité : undefined=>true
	
		// calcul du rapport largeur/hauteur de la source
		wh = src_w / src_h ;
		hw = src_h / src_w ;
		
		// par défaut, garder les mesures initiales
		height = src_h ;
		width = src_w ;
		
	// redimentionnements
		
		// agrandissement largeur
		if ( width < min_w ) {
			width = min_w;
			height = width * hw ;
		}
		
		// agrandissement hauteur
		if ( height < min_h ) {
			height = min_h;
			width = height * wh ;
		}
	
		// réduction largeur
		if ( max_w && (width > max_w) ) {
			width = max_w;
			height = width * hw ;
		}
	
		// réduction hauteur
		if ( max_h && (height > max_h) ) {
			height = max_h;
			width = height * wh ;
		}
		
	// valeurs négatives interdites
		width = width<0 ? 0 : width;
		height = height<0 ? 0 : height;
	
	return {
		w : round ? Math.round(width) : width,
		h : round ? Math.round(height) : height
	};
}





/**
 * bgMax    v1.0 / 2010-07-02
 * 
 * ajustes les dimensions d'une image en fonction de celles de la fenêtre
 *
 * @requires		addEvent, byTN, fade, redimArea, setOpacity, setStyle, transfer, winDim
 * 
 * =============================================================================
 */
var bgMax = {
	
	/*
	 * @var defaults
	 *
	 * options par défaut
	 *
	 * -------------------------------------------------------------------------
	 */
	defaults : {
		mode : 'max',			// string - max (occuper toute la fenêtre, quite à déborder) | full (toute l'image est visible, et aussi grande que possible)
		enlarge : 1,			// boolean - autorise ou non à agrandir l'image au dessus de ses dim réelles
		reduce: 1,				// boolean - autorise ou non à réduire l'image au dessus de ses dim réelles
		//ffHack : 0,				// String - décalage vers le haut à appliquer au body, pour Firefox<3.
		zIndex : -1,			// integer - profondeur du bloc-image
		position : 'absolute',	// string - positionnement de l'image (absolute|fixed)
		align : 'center',		// string - alignement horizontal de l'image
		vertAlign : 'top',		// string - alignement vertical de l'image
		fadeAfter : 400,		// integer - indique de faire apparaître l'image en fondu, si elle n'est pas chargée à l'issue de ce délai, exprimé en millisecondes
												// 0 : pour faire un fondu dans tous les cas
												// false : pour interdire le fondu
		fadeOptions : {			// Object - options du fondu. Voir les options de fade.js : http://www.webbricks.org/bricks/fade/
			duration : 1000,
			frameRate : 25
		}
	},											
	
	/**
	 * redim
	 *
	 * redimentionne l'image de façon proportionnelle
	 *
	 * @access		protected
	 * @returns		void
	 * 
	 * -------------------------------------------------------------------------
	 */
	redim : function () {
		// préparatifs
		var self = bgMax,
			img,			// l'élément image manipulé
			options,	// options
			win = winDim(),						// mesure de la fenêtre
			imgTop, imgBottom, imgLeft, imgRight,		// positionnement de l'image
			imgDim,								// mesures de l'image
			max_W, max_H, min_W, min_H;			// contraintes de mesures pour l'image
		
		img  = self.img;
		imgTop = imgBottom = imgLeft = imgRight = '';
		options = self.opt;
		imgDim = img.fileDim;
		
		if (imgDim) {
			
			// dimentionnement de l'image
			
				// établir les mesures max/min, horiz/vert
				
					// mode "full" (toute l'image est visible, et aussi grande que possible)
					if (options.mode == 'full') {
						max_W = min_W = win.w;
						max_H = min_H = win.h;
						if (!options.enlarge) {
							if (max_W > imgDim.w) { max_W = min_W = imgDim.w; }
							if (max_H > imgDim.h) { max_H = min_H = imgDim.h; }
						}
						if (!options.reduce) {
							if (min_W < imgDim.w) { min_W = imgDim.w; max_W = Math.max(min_W, max_W); }
							if (min_H < imgDim.h) { min_H = imgDim.h; max_H = Math.max(min_H, max_H); }
						}
					}
					
					// mode "max" (occuper toute la fenêtre, quite à déborder)
					else {
						min_W = max_W = win.w;
						min_H = max_H = 0;
						if (!options.reduce) {
							max_W = 0;
						}
						// on ne traite pas options.enlarge qui est sous-entendu à 1
					}
	
				// optimiser la surface
				imgDim = redimArea(imgDim.w, imgDim.h, {
					min_w : min_W,
					win_h : min_H,
					max_w : max_W,
					max_h : max_H,
					round : 1
				});
				
				// appliquer les dimensions
				img.width = imgDim.w;
				img.height = imgDim.h;
				
			// positionnement de l'image
				
				// calcul de la position horizontale
				switch (options.align) {
					case 'left' :
						imgLeft = '0px';
						break;
					case 'right' :
						imgRight = '0px';
						break;
					default :
						imgLeft = -Math.round((imgDim.w - win.w)/2) + 'px';
				}
				
				
				// calcul de la position vericale
				if (options.vertAlign=='bottom') {
					imgBottom = '0px';
				}
				else {
					imgTop = img.hack||'0px';
				}
				
				// positionnement effectif
				setStyle(img,{
					left : imgLeft,
					right : imgRight,
					top : imgTop,
					bottom : imgBottom
				});
		}
	},
												
	
	/**
	 * show
	 *
	 * affiche l'image
	 *
	 * @access		protected
	 * @returns		void
	 * 
	 * -------------------------------------------------------------------------
	 */
	show : function() {
		
		var self = bgMax,
			img;
			
		img = self.img;
		
		// relever les mesures normales de l'image
		img.fileDim = {
			w : img.clientWidth,
			h : img.clientHeight
		};
		
		// redimentionner l'image
		self.redim();
		
		// faire apparaître l'image
		if (self.fadeIt) { fade(self.img,1,0,self.opt.fadeOptions); }
		else { setOpacity(img,1); } // parfois inutile, mais est-ce la peine de mettre une condition ?
	},
	
	/**
	 * init
	 *
	 * initialise le script
	 * 
	 * @access		public
	 *
	 * @param		src			{String}		url de l'image
	 * @param		options		{Object}		tableau associatif des options (voir this.defaults)
	 *
	 * @returns		void
	 * 
	 * -------------------------------------------------------------------------
	 */
	init : function (src, options) {
		
		var self = bgMax,
			body = byTN('body')[0],
			fadeAfter,
			img,
			ff;  // version de Firefox
	
		options = self.opt = transfer(options, this.defaults);
		fadeAfter = options.fadeAfter;
		img = self.img = document.createElement('img');
		
		img.id = "bgMax";
		
		setStyle(img,{
			zIndex : options.zIndex,
			position : options.position
		});
		
		// hack pour Firefox<3 (pour une fois, c'est Firefox !)
		ff = navigator.userAgent.match(/Firefox.(\d+(\.\d+))/);
		if (ff && parseFloat(ff[1]) < 3) {
			setStyle(body,{
				zIndex : 0,
				position : 'relative',
				top : 0,
				left : 0
			});
			img.hack = '-'+options.ffHack;
		}
		
		//body.appendChild(img);
		body.insertBefore(img, body.childNodes[0]);
		img.onload = self.show;
		
		if (fadeAfter!==false) {
			setOpacity(img,0);
			if (fadeAfter) {
				setTimeout(function(){
					self.fadeIt = 1;
				},fadeAfter);
			}
			else {
				self.fadeIt = 1;
			}
			
		}
		
		addEvent(window,'resize',self.redim);
		
		img.src = src;
	}
};
