

/**
 * 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() {
	var WIN = this,
			EASY = WIN.ev;
	// Si les namespaces/classes nécessaires ne sont pas chargées : exception
	if (!EASY) {throw new Error("Le namespace 'ev' doit exister");}
	if (!EASY.tools) {throw new Error("Le namespace 'ev.tools' doit exister");}
	if (!EASY.log) {throw new Error("Le namespace 'ev.log' doit exister");}
	if (!EASY.dom) {throw new Error("Le namespace 'ev.dom' doit exister");}
	// On s'assure que le namespace ev.tpl existe
	if (!EASY.tpl) {EASY.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 {Object} 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) {
			//      EASY.log.debug('tpl.templateManager.__cnr()> '+s+'- null');
			return n;
		}
		var nn, i;
		switch (n.constructor) {
			case Array: // le contenu est un tableau de noeuds
				//      EASY.log.debug('tpl.templateManager.__cnr()> '+s+'+ array');
				nn = [];
				i = n.length;
				while (i) {
					--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);
				//      EASY.log.debug('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)
				//      EASY.log.debug('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;
		//    EASY.log.debug('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]);
			//      EASY.log.debug('tpl.templateManager.__fmt()> - '+dyn[i]+' -> '+v);
			if (!v) {
				if (v === undefined || v === null) {
					// on ne dit rien si la valeur est 0 ou ''
					//          EASY.log.warn('tpl.templateManager.__fmt()> no value found for given dynamic sequence ['+dyn+']');
					v = '';
				}
				break;
			}
		}
		//    EASY.log.debug('tpl.templateManager.__fmt()> '+t+' -> '+t.replace(REGEXP_DYN_TEXT, v));
		return fmt(t.replace(REGEXP_DYN_TEXT, v), o);
	}

	/**
	 * Ajoute l'élément DOM enfant 'e' à la fin de l'élément
	 * DOM parent 'p'.
	 * @param {Element} p élément DOM parent.
	 * @param {Element} e élément DOM enfant.
	 * @return {Element} 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(EASY.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);}
		//    EASY.log.debug('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 = EASY.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
						//EASY.log.debug('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 traite d'abord les images au cas où et pour éviter de faire le test sur chaque itération
				if (a.src && tg.toLowerCase() === 'img') {
					a.src = EASY.b64.ie7FixSrc(fmt(a.src, o));
				}
				// 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) {
							EASY.log.error('tpl.templateManager.__$Node()> un noeud sans nom de tag doit avoir un contenu de type texte (chaine de caractères)');
						}else{// FIXME gérer mémoire aussi pour les noeud textes
							$t(e, c[i].content, o);
						}
					}
					else if (mem && o[i].__node) {
						//EASY.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 {
						//EASY.log.debug('tpl.templateManager.__$Node()> NOT using memory DOM node...');
						// create & append new DOM node
						n = $a(e, $Node(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, $Node(c.tag, c.attr, c.content, o));
				if (mem) {
					o.__node = n;
				}
				break;
		}
		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);}
		//EASY.log.debug('tpl.templateManager.__print()> [ctn='+ctn+', tpl='+tpl+', src='+src+', redraw='+redraw+']');
		//var t0=new Date().getTime(), t1, t2;
		EASY.dom.clear(ctn);
		//function _print(){
		//  t1=new Date().getTime();
		$Node(ctn, 0, tpl, src, redraw);
		//  t2=new Date().getTime();
		//  EASY.log.info('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){
	//    EASY.log.debug('tpl.templateManager.__update()> [ctn='+ctn+', tpl='+tpl+', src='+src+']');
	//    uNode(ctn, 0, tpl, src);
	//  }

	/**
	 * Le gestionnaire de templates dynamiques.
	 */
	EASY.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) {
			//EASY.log.debug('tpl.templateManager#createIteration()> [tpl='+tpl+', it=['+it+'], v='+v+']');
			if (!tpl || (tpl.constructor !== Object && tpl.constructor !== Array)) {throw new Error('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('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('tpl.templateManager#createIteration()> La variable donnée doit être une chaine de caractères non vide.');}
			//EASY.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) {
			//EASY.log.debug('tpl.templateManager#initialize()> [ctn='+ctn+', tpl='+tpl+', defSrc='+defSrc+', redraw='+redraw+']');
			var c = EASY.dom.element(ctn);
			if (!c) {return EASY.log.error(NO_CONTAINER + ' [' + ctn + ']');}
			if (!tpl) {return EASY.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 {boolean} '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) {
			//EASY.log.debug('tpl.templateManager#memoryPrint()> [ctn='+ctn+', src='+src+']');
			var c = EASY.dom.element(ctn), ok = !0, i;
			if (!c) {return EASY.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) {
				//EASY.log.debug('tpl.templateManager#memoryPrint()> Using memory printing... ['+src.__node.tagName+']');
				EASY.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) {
				i = src.length;
				while (i) {
					--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) {
					//EASY.log.debug('tpl.templateManager#memoryPrint()> Using memory printing... ['+src.length+' objects]');
					EASY.dom.clear(c);
					for (i = 0; i < src.length; ++i) {
						// append memory DOM node in container
						$a(c, src[i].__node);
					}
				}
				//else{ EASY.log.warn('tpl.templateManager#memoryPrint()> Pas encore mémorisé ['+src.length+' objects]'); }
				return ok;
			}
			return !1;
		}
	};
}());

