/*
 * Copyright (c) 2005-2006 Guillaume Outters
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* ATTENTION: dans les regex, bien penser à mettre les possibilités les plus
 * longues en premier dans les expressions avec un « | ».
 * Exemple:
 * Pour le mois, « 30 »
 * Avec ([0-2]?[0-9])|(3[01]): le « 3 » est accepté par le « [0-9] », donc ça
 * valide l'expression et le « 0 » reste inutilisé.
 * Il faut donc écrire (3[01])|([0-2]?[0-9])
 * Et pourvu que tous les navigateurs fonctionnent de la même manière. */
function FormatHeure()
{
	var fh_heures = [ [ /[  ]*((2[0-3])|([01]?[0-9]))[  ]*/, 3 ] ];
	var fh_minutes = [ [ /[  ]*[0-6]?[0-9][  ]*/, 4 ] ];
	var fh_secondes = [ [ /[  ]*[0-6]?[0-9][  ]*/, 5 ] ];
	var fh_horaire =
	[
		[ fh_heures, /[  ]*h[  ]*/ ],
		[ fh_heures, /[  ]*h[  ]*/, fh_minutes ],
		[ fh_heures, /[  ]*:[  ]*/, fh_minutes ],
		[ fh_heures, /[  ]*:[  ]*/, fh_minutes, /[  ]*:[  ]*/, fh_secondes ]
	];
	var fh_jour = [ [ /[  ]*((3[0-1])|([012]?[0-9]))[  ]*/, 2 ] ];
	var fh_mois = [ [ /[  ]*((1[0-2])|(0?[1-9]))[  ]*/, 1 ] ];
	var fh_an = [ [ /[  ]*(19|20)?[0-9][0-9][  ]*/, 0 ] ];
	var fh_date =
	[
		[ fh_jour, /\//, fh_mois, /\//, fh_an ],
		[ fh_jour, /\//, fh_mois ],
		[ fh_jour ],
		[ fh_an, /-/, fh_mois, /-/, fh_jour ]
	];
	var fh_le = [ [ /[  ]*le[  ]*/ ] ];
	var fh_a = [ [ /[  ]*(a|à|vers|,|)[  ]*/ ] ];
	this.depart = [ [ fh_horaire ], [ fh_date ], [ fh_horaire, fh_le, fh_date ], [ fh_date, fh_a, fh_horaire ] ];
}

FormatHeure.prototype.clone = function(tableau)
{
	var t = [];
	var z;
	for(z = tableau.length; --z >= 0;)
		t[z] = tableau[z];
	return t;
}

FormatHeure.prototype.lire = function(chaine)
{
	var r = [ [ this.depart ] ];
	var chemins = [ [ 0, 0 ] ]; // Chemins possibles; chaque chemin est une pile dont les éléments pairs représentent le numéro de possibilité et les impairs le numéro d'étape dans cette possibilité.
	var restes = [ chaine ]; // Restes à traiter par chemin.
	var res = [ [ -1, -1, -1, -1, -1, -1 ] ]; // Résultats obtenus par chacun des chemins. À la fin, si on a une ambiguïté (deux chemins), il faut que leurs résultats soient identiques.
	var resultats = []; // Reçoit des copies de res lorsqu'un chemin se finit.
	var encore;
	var i, j;
	var b, conteneur, p, nouveau;
	var oups = 0x20; // On limite la casse…
	
	do
	{
		encore = false;
		
		/* Pour chacun des chemins possibles */
		
		for(i = chemins.length; --i >= 0;) // ATTENTION: laisser décrémenter: il y a des insertions et suppressions en milieu de tableau qui pourraient foutre le bazar en sens croissant.
		{
			p = chemins[i];
			
			for(b = r, j = 0; j < p.length - 2; j += 2) b = b[p[j]][p[j + 1]];
			conteneur = b; // Si on peut aller plus profond, b ira, mais conteneur demeurera.
			
			/* A-t-on passé toutes les étapes de cette possibilité? */
			
			if(p[j + 1] >= b[p[j]].length)
			{
				/* On remonte au précédent. */
				
				p.pop();
				p.pop();
				
				/* On passe à l'étape suivante. */
				
				if(p.length)
					++p[p.length - 1];
				else // On est arrivé en fin de racine; le résultat est définitif, on le met de côté.
				{
					if(!restes[i].length) // Sauf s'il dépasse, évidemment.
						resultats.push(res[i]);
					chemins.splice(i, 1);
					restes.splice(i, 1);
					res.splice(i, 1);
				}
				
				encore = true;
			}
			
			/* Tombe-t-on sur un objet dans lequel il faut rentrer? */
			
			else if((b = b[p[j]][p[j + 1]]) instanceof Object && b.length) // Les regexps sont des objets, on distingue par la length nos tableaux.
			{
				for(j = b.length; --j >= 0;)
				{
					chemins.push(nouveau = this.clone(p));
					nouveau.push(j);
					nouveau.push(0);
					restes.push(restes[i]);
					res.push(this.clone(res[i]));
				}
				chemins.splice(i, 1);
				restes.splice(i, 1);
				res.splice(i, 1);
				encore = true;
			}
			
			/* Il ne devrait plus nous rester que les regex */
			
			else
			{
				trouve = b.exec(restes[i]);
				if(trouve && trouve.index == 0)
				{
					restes[i] = restes[i].substr(trouve[0].length); // On vient de passer la chaîne.
					if(conteneur[p[j]].length > p[j + 1] && typeof(conteneur[p[j]][p[j + 1] + 1]) == 'number') // Si quelque chose nous suit, on regarde s'il ne s'agit pas d'un numéro de res[i] dans lequel stocker ce qu'on vient de lire.
					{
						res[i][conteneur[p[j]][p[j + 1] + 1]] = parseInt(trouve[0].replace(/[  ]*/g, ''), 10);
						++p[j + 1];
					}
					++p[j + 1]; // On se place à l'étape d'analyse suivante.
				}
				else // Échec. On va être correct, on ne va pas tenter de passer outre le verdict de l'expression régulière. Mais ça veut dire que pour nous l'aventure s'arrête ici.
				{
					chemins.splice(i, 1);
					restes.splice(i, 1);
					res.splice(i, 1);
				}
				encore = true;
			}
		}
		
		/*
		this.trace = traces_trace;
		this.initTraces = traces_init;
		this.initTraces(40, document.body);
		this._afficheur.style.color = 'white';
		this.trace("=== Après un tour ===");
		var s;
		for(i = 0; i < chemins.length; ++i)
		{
			s = '';
			for(j = 0; j < chemins[i].length; j += 2)
				s += chemins[i][j]+'['+chemins[i][j + 1]+']';
			for(j = 0; j < res[i].length; ++j)
				s += (j ? ', ' : ' ( ')+res[i][j];
			this.trace(s+" ), reste "+restes[i]);
		}
		this.trace("=== ............. ===");
		*/
	}
	while(encore && --oups >= 0);
	
	return resultats;
}

/* Renvoie un tableau qui pour chaque champ indique si, entre les deux dates
 * 'entre' et 'et', il ne peut prendre qu'une valeur. Si c'est le cas, le champ
 * en retour aura cette valeur. Sinon il vaudra -1, indiquant que le champ
 * correspondant de 'heure' est significatif. */
FormatHeure.prototype.explicite = function(heure, entre, et)
{
	var r = [ -1, -1, -1, -1, -1, -1 ];
	var i, j, d, f, ld, lf; // ld et lf: limite au début ou à la fin.
	
	/* Quel est le premier champ connu? */
	
	for(i = 0; i < 6 && (entre[i] < 0 || et[i] < 0); ++i)
		if(heure[i] >= 0) // Si l'heure est plus précise que les deux qui sont censées l'encadrer, elle prime sur toute la ligne.
			return r;
	
	for(; i < 6; ++i)
	{
		if(heure[i] >= 0 && (heure[i] < entre[i] || heure[i] > et[i]))
			return r; // Au premier champ qui doit rester explicite, on quitte: les suivants en font aussi partie.
		if(entre[i] < et[i] - 2) // Trop éloignés, on a de quoi caser plusieurs possibilités dans l'intervalle.
			return r;
		
		/* On cherche maintenant, sur ce champ, à lever l'ambiguïté: pour cela,
		 * on va chercher les champs plus profondément, jusqu'à ce que l'un
		 * permette de réduire à une la possibilité; bien sûr, il faut aussi que
		 * 'heure', si le champ correspondant est renseigné, possède la même
		 * valeur.
		 * Exemple: entre décembre 2003 et février 2005, si on a une date au
		 * mois de juin, c'est nécessairement en 2004. Cas qui peut foutre le
		 * bazar: un 11 octobre entre le 16 octobre 1978 et le 10 octobre 1979,
		 * il n'y en a aucun (ce n'est qu'en arrivant au jour que l'on note
		 * l'impossibilité de concilier les années). Par contre, en ajoutant un
		 * an de différence, il y en a un seul. */
		
		d = entre[i]; // C'est d et f que l'on va essayer de faire coïncider.
		f = et[i];
		lf = ld = true; // Limite, ça veut dire que jusque-là les champs de 'heure' sont égaux à notre début ou fin, et que la moindre divergence nous fera changer d ou f.
		for(j = i; j < 6 && d < f; ++j)
		{
			if(heure[j] >= 0)
			{
				if(ld)
				{
					if(heure[j] < entre[j]) { ++d; ld = false; } // On diverge; seule solution: passer à la valeur suivante du champ étudié.
					if(heure[j] > entre[j]) ld = false; // On n'est plus limite: on est au-dessus de entre.
				}
				if(lf)
				{
					if(heure[j] > et[j]) { --f; lf = false; }
					if(heure[j] < et[j]) lf = false;
				}
			}
		}
		if(d == f) // Ouf, on a réussi à se mettre d'accord!
		{
			r[i] = d;
			if(j > i) // Les champs suivants sont significatifs: ils nous ont permis de résoudre celui-ci. On ne va donc pas plus loin.
			{
				--j;
				while(--j > i) // Les champs entre celui que l'on vient de déterminer et celui qui a permis de déterminer doivent être identiques (sinon on n'aurait jamais pu atteindre le champ déterminant). /* ATTENTION: ça ne sera plus vrai lorsqu'on sera capable de passer d'un mois à l'autre. Déjà que je ne suis pas sûr que ça le soit maintenant… */
					r[j] = entre[j];
				return r;
			}
		}
		else
			return r;
	}
	
	return r;
	
	/* Le cas qui tue: un 19 h 30 entre un 31 août 18 h et un 1er septembre 8 h,
	 * il n'y en n'a qu'un. Comment fait-on pour le savoir? Pour ce type de
	 * problème, il faudrait savoir exprimer un champ en fonction de l'autre
	 * (dans notre cas: le 1er septembre comme un 32 août). */
	/* ATTENTION: il faudra modifier d et f qui deviendront des tableaux, dans
	 * lesquels on pourra avoir à faire remonter des retenues. */
	/* On ne gère pas les cas tordus du genre 29 février qui ne peut tomber que
	 * les années bisextiles, etc. */
}

/* Renvoie comme pour explicite() un tableau; les champs en retour sont à -1 si
 * 'depuis' et 'd' y ont la même valeur, sinon à la valeur de 'd'. Si auMoinsUn
 * est false, on se permet de renvoyer un tableau de -1 si 'd' et 'depuis' sont
 * identiques; sinon on essaie de trouver au moins un élément rappelant 'd'. */
FormatHeure.prototype.distinct = function(d, depuis, auMoinsUn)
{
	var r = new Array();
	var i, n;
	
	for(i = -1, n = auMoinsUn ? 3 : 6; ++i < n;) // On s'arrête à l'heure, car de toute façon il nous faut un élément distinctif, et si on met les minutes il nous faut les minutes et avec les minutes les heures.
	{
		if(d[i] != depuis[i])
			break;
		r[i] = -1;
	}
	
	for(--i; ++i < 6;)
		r[i] = d[i];
	
	/* Bon, maintenant, il se peut que la date soit imprécise (ex.: juste un
	 * jour/mois/an, sans heure); on peut alors tomber sur un tableau de -1, pas
	 * joli à afficher. On cherche donc le premier champ significatif. */
	
	if(auMoinsUn)
		for(i = 6; --i >= 0;)
			if(d[i] >= 0)
			{
				r[i] = d[i];
				break;
			}
	
	return r;
}

/* Complète une date avec les champs d'une autre, pour les premiers de ses
 * champs qui seraient indéterminés. */
FormatHeure.prototype.completer = function(heure, avecHeure)
{
	var z;
	for(z = 0; z < 6; ++z)
		if(heure[z] < 0)
			heure[z] = avecHeure[z];
		else
			break;
	return heure;
}

/* Renvoie une chaîne de caractères représentant la date 'heure', qui soit
 * relisible par lire, en essayant de grapiller des caractères superflus si, sur
 * la plage horaire 'entre' - 'et', il n'y a pas d'ambiguïté, ou si 'heure' est
 * incomplète.
 * Paramètres:
 *   heure: tableau [ A, M, J, h, m, s ] de la date à sortir.
 *   entre: tableau, début de la plage dans laquelle la date est implicite. Le
 *          tableau, à l'heure actuelle, doit être entièrement renseigné (pas de
 *          -1).
 *   et: tableau, pendant de 'entre'.
 * Retour:
 *   chaîne représentant la date.
 */
FormatHeure.prototype.ecrire = function(heure, entre, et)
{
	var d = null, h = null;
	var champs = [ 0, 0, 0, 0, 0, 0 ]; // 0: non renseigné; 1: explicite; 2: ambigu.
	var i;
	
	/* Qu'est-ce qui est renseigné? */
	
	for(i = 3; --i >= 0 && heure[i] >= 0;) // On n'accepte pas d'an sans mois, pas de mois sans jour.
		champs[i] = 1;
	for(i = 2; ++i < 6 && heure[i] >= 0;) // Pas de secondes sans minutes, pas de minutes sans heures.
		champs[i] = 1;
	
	/* Calcul de ce qu'on dit superflu; mieux vaut pas ne pas faire de zèle, et
	 * être trop explicite que perdre de l'info. */
	
	for(i = 0; i < 3; ++i)
		if(champs[i])
			if(heure[i] == entre[i] && heure[i] == et[i])
	
	/* La date */
	
	if(heure[2] >= 0)
	{
		//if(heure[1] >= 0 && heure[0] >= 0) // Avec une année, on met des 0 partout.
	}
	
	/* L'heure */
	
	if(heure[3])
	{
		/* Si on a des secondes, on prend le format hh:mm:ss; sinon h:mm */
		
		if(heure[5] <= 0) // On se permet de zaper les secondes quand elles sont nulles.
		{
			
		}
	}
	
	/* Résultat des courses */
	
	if(d) if(h) return d+' '+h; else return d; else return h;
}

FormatHeure.prototype.sprintf = function(chiffres, valeur)
{
	var s;
	var r = '';
	if(valeur < 0) { valeur = -valeur; s = true; } else s = false;
	while(valeur != 0)
	{
		r = ''+(valeur % 10)+r;
		valeur = Math.floor(valeur / 10);
		--chiffres;
	}
	while(--chiffres >= 0)
		r = ''+'0'+r;
	return s ? '-'+r : r;
}

/* Chaîne intelligible, d'où sont zappés les champs à -1. */
/* COPIE: date_affichage_tableau dans date.inc */
FormatHeure.prototype.aff = function(tableau)
{
	var masque = 0;
	var jour = '';
	var heure = '';
	var i, h, m;
	var nombre;
	
	/* Jour */
	
	for(i = 3; --i >= 0;)
		if(((nombre = tableau[i]) >= 0) || (i == 1 && tableau[0] >= 0 && tableau[2] >= 0))
		{
			if(nombre < 0) nombre = 1;
			jour += (masque ? '/' : '')+this.sprintf(i == 0 ? 4 : 2, nombre);
			masque = 2;
		}
	
	/* Heure */
	
	if((tableau[3] >= 0) || (tableau[4] >= 0) || (tableau[5] >= 0))
	{
		if((h = tableau[3]) < 0) h = 0;
		if((m = tableau[4]) < 0) m = 0; /* À FAIRE: éventuellement un mode « 8 h » sans minutes. */
		heure = this.sprintf(2, h)+':'+this.sprintf(2, m);
		if((i = tableau[5]) >= 0)
			heure += ':'+this.sprintf(2, i);
		masque |= 1;
	}
	
	return jour+(masque == 3 ? ' ' : '')+heure;
}

/* Crée un tableau à partir d'une date en secondes */
FormatHeure.prototype.depuisEpoch = function(epoch)
{
	epoch = new Date(epoch * 1000);
	return new Array(epoch.getFullYear(), epoch.getMonth() + 1, epoch.getDate(), epoch.getHours(), epoch.getMinutes(), epoch.getSeconds());
}

FormatHeure.prototype.versEpoch = function(tableau)
{
	var i;
	for(i = 6; --i >= 0;)
		if(tableau[i] < 0)
		{
			tableau = this.clone(tableau);
			for(++i; --i >= 0;)
				if(tableau[i] < 0)
					tableau[i] = i < 3 ? 1 : 0;
			break;
		}
	return new Date(tableau[0], tableau[1] - 1, tableau[2], tableau[3], tableau[4], tableau[5]).getTime() / 1000;
}

FormatHeure.prototype.tester = function()
{
	this.initTraces = traces_init;
	this.trace = traces_trace;
	this.initTraces(40, document.body);
	
	var tests = [ '17 15 h', '17', '15 : 14', '15:', '15h', '17/12 à 13h' ];
	var r, i, j, s;
	for(t = 0; t < tests.length; ++t)
	{
		s = tests[t]+':';
		r = this.lire(tests[t]);
		for(i = 0; i < r.length; ++i)
		{
			for(j = 0; j < r[i].length; ++j)
				s += (j ? ', ' : ' [ ')+r[i][j];
			s+= ' ]';
		}
		this.trace(s);
	}
}

FormatHeure.prototype.testerExplicite = function()
{
	this.initTraces = traces_init;
	this.trace = traces_trace;
	this.initTraces(40, document.body);
	
	var i, r, j, s;
	var tests = // heure, entre, et, solution… et explication.
	[
		[ -1, -1, -1, 15, 12, 0 ], [ 2005, 9, 17, 8, 0, 0 ], [ 2005, 9, 17, 18, 0, 0 ], [ 2005, 9, 17, -1, -1, -1 ], // Cas simple: on veut un simple complément.
		[ -1, -1, -1, 21, 12, 0 ], [ 2005, 9, 17, 8, 0, 0 ], [ 2005, 9, 18, 18, 0, 0 ], [ 2005, 9, 17, -1, -1, -1 ], // À cheval sur deux jours, mais solvable.
		[ -1, -1, -1, 15, 12, 0 ], [ 2005, 9, 17, 8, 0, 0 ], [ 2005, 9, 18, 18, 0, 0 ], [ 2005, 9, -1, -1, -1, -1 ], // À cheval sur deux jours, deux possibilités donc insolvable.
		[ -1, -1, -1, 15, 12, 0 ], [ 2005, 9, 17, 8, 0, 0 ], [ 2005, 9, 19, 7, 0, 0 ], [ 2005, 9, -1, -1, -1, -1 ], // À cheval sur trois jours, deux possibilités.
		[ -1, -1, -1, 7, 12, 0 ], [ 2005, 9, 17, 8, 0, 0 ], [ 2005, 9, 19, 7, 0, 0 ], [ 2005, 9, 18, -1, -1, -1 ], // À cheval sur trois jours, mais une seule possibilité.
		[ -1, 10, 11, -1, -1, -1 ], [ 1978, 10, 16, 0, 0, 0 ], [ 1979, 10, 10, 0, 0, 0 ], [ -1, -1, -1, -1, -1, -1 ], // À cheval sur trois ans, seul le jour permet de distinguer, et il n'y a pas de solution.
		[ -1, 4, 11, -1, -1, -1 ], [ 1978, 4, 16, 0, 0, 0 ], [ 1980, 4, 10, 0, 0, 0 ], [ 1979, 4, -1, -1, -1, -1 ], // À cheval sur trois ans, seul le jour permet de distinguer, mais une seule possibilité.
		[ -1, -1, 11, -1, -1, -1 ], [ 1978, 10, 16, 0, 0, 0 ], [ 1979, 10, 10, 0, 0, 0 ], [ -1, -1, -1, -1, -1, -1 ], // À cheval sur trois ans, 'heure' ne permet pas de trancher.
		[ -1, 10, -1, -1, -1, -1 ], [ 1978, 10, 16, 0, 0, 0 ], [ 1979, 10, 10, 0, 0, 0 ], [ -1, -1, -1, -1, -1, -1 ], // Le premier champ qui aurait permis de trancher n'est pas là, donc pas de solution.
		[ -1, 4, -1, -1, -1, -1 ], [ 1978, 4, 16, 0, 0, 0 ], [ 1980, 4, 10, 0, 0, 0 ], [ -1, -1, -1, -1, -1, -1 ] // Le premier champ qui aurait permis de trancher n'est pas là, donc trop de solution, donc pas de solution.
	];
	
	for(i = 0; i < tests.length; i += 4)
	{
		r = this.explicite(tests[i], tests[i + 1], tests[i + 2]);
		s = (i / 4);
		for(j = 0; j < r.length; ++j)
			s += (j ? ', ' : ': [ ')+r[j];
		s += ']';
		r = tests[i + 3];
		for(j = 0; j < r.length; ++j)
			s += (j ? ', ' : ' (attendu: [ ')+r[j];
		this.trace(s+' ])');
	}
}

