/**
 * Fonction anonyme de déclaration des classes d'écriture DOM.<br>
 * Certaines fonctions ne sont visible que par les classes
 * concernées (déclarées ici).<br>
 * Ce mécanisme de fonction anonyme permet de reproduire un
 * système d'encapsulation digne d'un langage de programmation
 * évolué (comme le Java).<br>
 * <br>
 * Classes et éléments visibles seront stockés dans le
 * namespace 'ev.dom'.
 */
(function (){
	// Si les namespaces/classes nécessaires ne sont pas chargées : exception
	if(!window.ev){throw new Error("Le namespace 'ev' doit exister");}
	if(!ev.tools){throw new Error("Le namespace 'ev.tools' doit exister");}
	if(!ev.log){throw new Error("Le namespace 'ev.log' doit exister");}
	if(!ev.dom){throw new Error("Le namespace 'ev.dom' doit exister");}
	// On s'assure que le namespace ev.tpl existe
	if(!ev.tpl){ev.tpl={};}

	// Constantes textes
	var NEED_TAG_OR_CONTENT='Il faut soit un nom de tag, soit un contenu texte pour construire le noeud.',
	NO_CONTAINER='Conteneur manquant ou nom de conteneur inexistant.',
	NO_TEMPLATE='Modèle manquant.',
	NO_SOURCE='Objet source manquant.',
	// Expression de reconnaissance des zones dynamiques de texte
	REGEXP_DYN_TEXT=/\[¤([a-zA-Z0-9,]+)¤\]/,
	// Expression d'interprétation des dollars (itérations de template) 
	REGEXP_DOLLAR=/\$/g,
	// Expression de reconnaissance de code HTML dans les textes
	REGEXP_HTML=/&/,
	// Expression de séparation des zones multiples (texte dynamique) 
	REGEXP_SPLIT=/,/;
	
	/**
	 * cnr : Clone And Replace.<br>
	 * Clone le noeud donné de manière récursive.<br>
	 * Convertit également toutes les occurences de la
	 * variable donnée par la valeur de remplacement
	 * correspondante.
	 * @param {Object} n objet (noeud) à cloner
	 * @param {Object} er expression régulière (variable à remplacer)
	 * @param {Object} r chaine de remplacement
	 * @return le nouvel objet créé
	 */
	// * @param {Object} s [optionel] espace pour debug (FIXME à supprimer)
	//function cnr(n, er, r, s){
	function cnr(n, er, r){
		if(!n){
//			ev.log.debug('ev.tpl.templateManager.__cnr()> '+s+'- null');
			return n;
		}
		var nn, i;
		switch(n.constructor){
		case Array: // le contenu est un tableau de noeuds
//			ev.log.debug('ev.tpl.templateManager.__cnr()> '+s+'+ array');
			for(nn=[], i=n.length; i--;){
				//nn[i]=cnr(n[i], er, r, s?s+' ':' ');
				nn[i]=cnr(n[i], er, r);
			}
			return nn;
		case String: // l'objet est un texte
			nn=n.replace(new RegExp(er.replace(REGEXP_DOLLAR,'\\\$'), 'g'), r);
//			ev.log.debug('ev.tpl.templateManager.__cnr()> '+s+'- string : \''+n+'\' ->  \''+nn+'\'');
			return nn;
		case Function: // l'objet est une fonction
			return n;
		default: // sinon, on suppose que c'est un simple objet (un noeud unique)
//			ev.log.debug('ev.tpl.templateManager.__cnr()> '+s+'+ object');
			nn={};
			for(i in n){
				if(n.hasOwnProperty(i)){
					//nn[i]=cnr(n[i], er, r, s?s+' ':' ');
					nn[i]=cnr(n[i], er, r);
				}
			}
			return nn;
		}
	}

	/**
	 * Récupère une propriété dans l'objet donné.
	 * @param {Object} o objet source dans lequel récupérer la propriété
	 * @param {Object} p propriété à récupérer dans l'objet
	 */
	function objectProperty(o, p){
		if(o[p]===undefined){ // Là on veut être sûr que c'est undefined
			var getter=o['get'+p.capitalize()];
			if(typeof(getter)==='function'){
				return getter.call(o);
			}
		}
		// sinon on retourne la valeur de la propriété (y compris null, 0, '')
		return o[p];
	}

	/**
	 * Formate le texte donné s'il y a des zones dynamiques
	 * à interpréter. S'il n'y en a pas, la fonction retourne.
	 * la chaine de caractères donnée.
	 * @param {String} t chaine de texte pouvant contenir des zones à interpréter (ex: [¤ville¤])
	 * @param {Object} o objet source (pour les parties textes dynamiques)
	 * @throws If there is no source given
	 */
	function fmt(t, o){
		if(!o){throw new Error(NO_SOURCE);}
		if(!REGEXP_DYN_TEXT.test(t)){return t;}
		var dyn=RegExp.$1.split(REGEXP_SPLIT), l=dyn.length, i, v=o;
//		ev.log.debug('ev.tpl.templateManager.__fmt()> something to parse... : \''+dyn+'\'');
		for(i=0; i<l; ++i){
			if(v.constructor===String&&dyn[i]!=='length'){break;}
			v=objectProperty(v, dyn[i]);
//			ev.log.debug('ev.tpl.templateManager.__fmt()> - '+dyn[i]+' -> '+v);
			if(!v){
				if(v===undefined||v===null){
					// on ne dit rien si la valeur est 0 ou ''
//					ev.log.warn('ev.tpl.templateManager.__fmt()> no value found for given dynamic sequence ['+dyn+']');
					v='';
				}
				break;
			}
		}
//		ev.log.debug('ev.tpl.templateManager.__fmt()> '+t+' -> '+t.replace(REGEXP_DYN_TEXT, v));
		return arguments.callee(t.replace(REGEXP_DYN_TEXT, v), o);
	}

	/**
	 * Ajoute l'élément DOM enfant 'e' à la fin de l'élément
	 * DOM parent 'p'.
	 * @param {Object} p élément DOM parent
	 * @param {Object} e élément DOM enfant
	 * @return l'élément DOM enfant ajouté
	 */
	function $a(p, e){
		p.appendChild(e);
		return e;
	}

	/**
	 * Adds the given text to the given parent node.<br>
	 * <br>
	 * It replaces the dynamic parts with the source
	 * object properties, check if the text contains
	 * any HTML code, and creates and inserts the
	 * appropriate node into the given parent node.
	 * @param {Object} p parent node
	 * @param {Object} t text to format and add
	 * @param {Object} o source object for formating
	 */
	function $t(p, t, o){
		t=fmt(t, o);
		if(REGEXP_HTML.test(t)){
			// noeud text avec code HTML
			p.innerHTML=t;
		}
		else{
			// noeud text sans code HTML
			p.appendChild(ev.dom.createText(t));
		}
		
	}

	/**
	 * Crée un noeud avec le nom, les attributs et le contenu donné.
	 * Les parties dynamiques du contenu sont interprétées à partir
	 * de l'objet source donné.
	 * @param {String} tg nom du tag (ou élément parent si premier appel)
	 * @param {Object} a attributs supplémentaires du noeud à créer si besoin (ou null sinon)
	 * @param {Object} c contenu (texte ou tableau d'objets représentant le contenu)
	 * @param {Object} o objet source (pour les parties textes dynamiques)
	 * @param {Boolean} r précise s'il l'on souhaite redessiner le
	 *     noeud DOM (dans le cas où il a déjà été dessiné auparavant)
	 * @throws If there is no tag and no content given
	 * @throws If there is no source given
	 */
	function $Node(tg, a, c, o, r){
		if(!o){throw new Error(NO_SOURCE);}
		if(!c&&!tg){throw new Error(NEED_TAG_OR_CONTENT);}
//		ev.log.debug('ev.tpl.templateManager.__$Node()> creating node... [tg='+tg+', a='+a+', c='+c+', o='+o+']');
		var e, l, i, n, mem;
		if(tg.constructor===String){
			// Nouveau noeud de type donné dans 'tg'
			e=ev.dom.create(tg);
			// Traitement de tous les attributs du noeud
			if(a){
				if(a.empty){
					// si c'est une chaine de caractères, on formate le champ dans une variable temporaire (sinon i sera 'true')
					i=typeof(a.empty)!=='string'||fmt(a.empty, o);
					// on revérifie s'il est bien défini (la chaine vide '' correspond à "non défini")
					if(i){ 
						e.empty=i;
						// Si la propriété 'empty' est définie et non nulle
						// on ne fixe que le className, au cas ou on aurait prévu une classe spécifique
						e.className=fmt(a.className, o);
						//on ne rempli pas le noeud (plus bas), on sort
//ev.log.debug('ev.tpl.templateManager.__$Node()> tag \''+e.tagName+'\' set to EMPTY : '+e.empty);
						return e;
					}
					// si i est faux, il faut créer les attributs et
					// le contenu du noeud courant (plus bas)
				}
				// On ne copie les propriétés que si la propriété 'empty' n'est pas là
				for(i in a){
					if(a.hasOwnProperty(i)){
						if(typeof(a[i])==='string'){
							e[i]=fmt(a[i], o);
						}
						else if(a[i]!==undefined){
							e[i]=a[i];
						}
					}
				}
			}
		}
		else{
			// On suppose ici que c'est l'élément parent (existant déjà), au premier appel
			e=tg;
		}
		// On peut écrire un bloc sans contenu. Dans ce cas on sort
		if(!c){return e;}
		// On ajoute le contenu après avoir vérifié de quel type il est
		switch(c.constructor){
		case Array: // le contenu est un tableau de noeuds
			// ici, on sait que le contenu est un tableau
			// 4 conditions pour utiliser la mémoire des noeuds créés :
			// - si on est sur l'élément conteneur (e[lement]===tg[ name])
			// - si l'objet source est lui aussi un tableau (o[bjet source].constructor===Array)
			// - si les 2 tableaux font la même taille (o[bjet source].length===c[ontenu du template].length)
			// - si on ne souhaite pas redessiner le DOM à chaque fois (r[edraw]===false)
			mem=!r&&e===tg&&o.constructor===Array&&o.length===c.length;
			for(i=0, l=c.length; i<l; ++i){
				if(!c[i].tag){
					if(!c[i].content||c[i].content.constructor!==String){
						ev.log.error('ev.tpl.templateManager.__$Node()> un noeud sans nom de tag doit avoir un contenu de type texte (chaine de caractères)');
						continue;
					}
					// FIXME gérer mémoire aussi pour les noeud textes
					$t(e, c[i].content, o);
				}
				else if(mem&&o[i].__node){
//ev.log.debug('tpl.templateManager.__$Node()> Using memory DOM node... ['+o[i].__node.tagName+']');
					// append memory DOM node in parent element
					$a(e, o[i].__node);
				}
				else{
//ev.log.debug('tpl.templateManager.__$Node()> NOT using memory DOM node...');
					// create & append new DOM node
					n=$a(e, arguments.callee(c[i].tag, c[i].attr, c[i].content, o));
					if(mem){
						// memorizing DOM node
						o[i].__node=n;
					}
				}
			}
			break;
		case String: // le contenu est un texte
			// FIXME gérer mémoire aussi pour les noeud textes
			$t(e, c, o);
			break;
		default: // sinon, on suppose que c'est un simple objet (un noeud unique)
			// ici, on sait que le contenu est un simple objet
			// 4 conditions pour utiliser la mémoire du noeud créé :
			// - si on est sur l'élément conteneur (e[lement]===tg[ name])
			// - si on ne souhaite pas redessiner le DOM à chaque fois (r[edraw]===false)
			mem=!r&&e===tg;
			n=$a(e, arguments.callee(c.tag, c.attr, c.content, o));
			if(mem){
				o.__node=n;
			}
		}
		return e;
	}

	/**
	 * Imprime dans le conteneur donné (ctn), les éléments
	 * du template donné (tpl), en prennant si besoin des
	 * éléments dynamiques dans l'objet source donné (src).
	 * @param {Object} ctn conteneur à remplir
	 * @param {Object} tpl contenu de template à utiliser comme modèle
	 * @param {Object} src objet source contenant les éléments dynamiques
	 * @param {Boolean} redraw précise s'il l'on souhaite redessiner le
	 *     contenu DOM (dans le cas où il a déjà été dessiné auparavant)
	 * @throws If there is no tag given
	 * @throws If there is no template content given
	 * @throws If there is no source given
	 */
	function print(ctn, tpl, src, redraw){
		if(!src){throw new Error(NO_SOURCE);}
//ev.log.debug('ev.tpl.templateManager.__print()> [ctn='+ctn+', tpl='+tpl+', src='+src+', redraw='+redraw+']');
//var t0=new Date().getTime(), t1, t2;
		ev.dom.clear(ctn);
//function _print(){
//	t1=new Date().getTime();
		$Node(ctn, 0, tpl, src, redraw);
//	t2=new Date().getTime();
//	ev.log.info('ev.tpl.templateManager.__print()> ctn '+ctn.id+' printed. times['+(t1-t0)+' ; '+(t2-t1)+' = '+(t2-t0)+']');
//}
//if(redraw){
//	_print();
//}
//else{
//	setTimeout(_print, 600);
//}
	}

//	/**
//	 * Met à jour dans le conteneur donné (ctn), les éléments
//	 * du template donné (tpl), en prennant si besoin des
//	 * éléments dynamiques dans l'objet source donné (src).
//	 * @param {Object} ctn conteneur à mettre à jour
//	 * @param {Object} tpl contenu de template à utiliser comme modèle
//	 * @param {Object} src objet source contenant les éléments dynamiques
//	 * @throws If there is no tag given
//	 * @throws If there is no template content given
//	 * @throws If there is no source given
//	 */
//	function update(ctn, tpl, src){
//		ev.log.debug('ev.tpl.templateManager.__update()> [ctn='+ctn+', tpl='+tpl+', src='+src+']');
//		uNode(ctn, 0, tpl, src);
//	}

	/**
	 * Le gestionnaire de templates dynamiques.
	 */
	ev.tpl.templateManager={
		/**
		 * 
		 * @param {Object} tpl : template à dupliquer
		 * @param {Object} it : tableau des valeurs sur lesquelles itérer ou nombre d'itérations
		 * @param {Object} v : identifiant à remplacer par les valeurs d'itération dans le contenu du template
		 * @throws If template is neither an array nor an object
		 * @throws If iterator is neither a number nor an array (or is a negative number, a null number or an empty array)
		 * @throws If variable is not a string (or is an empty string)
		 */
		createIteration: function (tpl, it, v){
//ev.log.debug('tpl.templateManager#createIteration()> [tpl='+tpl+', it=['+it+'], v='+v+']');
			if(!tpl||tpl.constructor!==Object&&tpl.constructor!==Array){throw new Error('ev.tpl.templateManager#createIteration()> Le template doit être non nul est de type Array ou Object.');}
			if(!it||(it.constructor!==Array||!it.length)&&(typeof(it)!=='number'||it<0)){throw new Error('ev.tpl.templateManager#createIteration()> L\'iterator doit être un nombre positif non nul ou un tableau non vide.');}
			if(typeof(it)!=='number'&&(!v||(v.constructor!==String||!v.length))){throw new Error('ev.tpl.templateManager#createIteration()> La variable donnée doit être une chaine de caractères non vide.');}
//ev.log.info('tpl.templateManager#createIteration()> ok: tpl='+tpl+', it=['+it+'], v='+v);
			// FIXME check tableau iterator associatif
			//var nTpl=[], t, l=it.length, i;
			var nTpl=[], tl, ti, l=it.length? it.length: it, i, get;
			if(it.length){
				//c'est un tableau
				get=function (x){
					return it[x];
				};
			}
			else{
				//c'est un nombre
				get=function (x){
					return x;
				};
			}
			if(tpl.constructor===Array){
				for(i=0; i<l; ++i){
					for(ti=0, tl=tpl.length; ti<tl; ++ti){
						//nTpl.push(cnr(tpl[ti], v, get(i), '### '));
						nTpl.push(cnr(tpl[ti], v, get(i)));
					}
				}
			}
			else{
				for(i=0; i<l; ++i){
					//nTpl.push(cnr(tpl, v, get(i), '### '));
					nTpl.push(cnr(tpl, v, get(i)));
				}
			}
			return nTpl;
		},

		/**
		 * Initialise le conteneur donné (ctn), avec 1 méthode
		 * de préparation du contenu DOM et 1 méthode de mise à
		 * jour après une première impression.
		 * @param {Object} ctn conteneur à remplir
		 * @param {Object} tpl template à utiliser comme modèle
		 * @param {Object} defSrc objet source contenant les éléments dynamiques
		 * @param {Boolean} redraw précise s'il l'on souhaite redessiner
		 *     le contenu DOM à chaque fois (dans le cas où il a déjà été
		 *     dessiné auparavant)
		 * @throws If there is no container given
		 * @throws If there is no template given
		 */
		initialize: function (ctn, tpl, defSrc, redraw){
//ev.log.debug('tpl.templateManager#initialize()> [ctn='+ctn+', tpl='+tpl+', defSrc='+defSrc+', redraw='+redraw+']');
			var c=ev.dom.element(ctn);
			if(!c){return ev.log.error(NO_CONTAINER+' ['+ctn+']');}
			if(!tpl){return ev.log.error(NO_TEMPLATE);}
			if(redraw===undefined){
				// par défaut on redessine à chaque appel
				redraw=true;
			}
			c.evTplPrint=function (src){
				print(c, tpl, src||defSrc, redraw);
			};
//			c.evTplUpdate=function (updSrc){
//				update(c, tpl, updSrc);
//			};
			return c;
		},

		/**
		 * Rempli le conteneur donné (ctn), avec les noeuds
		 * de l'objet source (ou du tableau d'objets source)
		 * s'il ont tous déjà été créés.
		 * @param {Object} ctn conteneur à remplir
		 * @param {Object} src objet source contenant les éléments dynamiques
		 * @return 'true' si l'écriture du(des) noeud(s) a pu se faire ; sinon 'false'
		 * @throws If there is no container given
		 * @throws If there is no source given
		 */
		memoryPrint: function (ctn, src){
//ev.log.debug('tpl.templateManager#memoryPrint()> [ctn='+ctn+', src='+src+']');
			var c=ev.dom.element(ctn), ok=!0, i;
			if(!c){return ev.log.error(NO_CONTAINER+' ['+ctn+']');}
			if(!src){throw new Error(NO_SOURCE);}

			// s'il y a une sauvegarde pour l'objet on l'utilise
			if(src.__node){
//ev.log.debug('tpl.templateManager#memoryPrint()> Using memory printing... ['+src.__node.tagName+']');
				ev.dom.clear(c);
				// append memory DOM node in container
				$a(c, src.__node);
				return !0;
			}

			// sinon, dans le cas d'un tableau d'objets on regarde s'il y a une sauvegarde pour chacun de ses éléments
			if(src.constructor===Array){
				for(i=src.length; i--;){
					if(!src[i].__node){
						// le noeud n'a jamais été écrit, on ne peut pas utiliser la mémoire
						ok=!1;
						break;
					}
				}
				if(ok){
//ev.log.debug('tpl.templateManager#memoryPrint()> Using memory printing... ['+src.length+' objects]');
					ev.dom.clear(c);
					for(i=0; i<src.length; ++i){
						// append memory DOM node in container
						$a(c, src[i].__node);
					}
				}
//else{ ev.log.warn('tpl.templateManager#memoryPrint()> Pas encore mémorisé ['+src.length+' objects]'); }
				return ok;
			}
			return !1;
		}
	};
}());