/**
 * Définition d'une date dans le calendrier grégorien.
 *
 * @author	Xavier Crégut
 * @version	1.5
 */
public class Date {

	/*@ // le jour, le mois et l'année sont valides
		public invariant
		Date.estValide(this.getJour(), this.getMois(), this.getAnnee());
		
		// le quantième est valide
		public invariant
		Date.estQuantiemeValide(this.getQuantieme(), this.getAnnee());

		private invariant	// cumulJoursMois est cohérent avec nbJoursDansMois
			(\forall int mois; 0 <= mois && mois < Date.cumulJoursMois.length;
				Date.cumulJoursMois[mois] ==
						(\sum int m; 0 <= m && m < mois; Date.nbJoursMois[m]));
	@*/

	/** Tableau du nombre de jours par mois (sans prendre en compte les
		* années bissextiles). nbJoursMois[i] est le nombre de jours du
		* mois de numéro i+1.
		*/
	private static int nbJoursMois[] =
			{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

	/** Cumul des jours des mois précédents. cumulJoursMois[i] est le
		* nombre cumulé des jours des mois dont les numéros sont dans
		* l'intervalle 1..i-1.
		*/
	private static int cumulJoursMois[] =
			{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

	/** le numéro du jour dans le mois */
	private int jour;

	/** le numéro du mois dans l'année */
	private int mois;

	/** l'année */
	private int annee;

	/** Construire une date à partir du numéro du jour, du numéro du mois et de
		* l'année (qui doivent former une date valide).
		* @param j le numéro du jour dans le mois m
		* @param m le numéro du mois
		* @param a l'année
		*/
	//@ requires Date.estValide(j, m, a);
	//@ ensures getJour() == j;
	//@ ensures getMois() == m;
	//@ ensures getAnnee() == a;
	public Date(int j, int m, int a) {
		this._set(j, m, a);
			// On ne peut pas appeler directement set(j, m, a) car comme elle est
			// publique, elle provoque la vérification des invariants.
	}

	/** Construire une date à partir du quantième du jour dans l'année et de
	 * l'année.
	 * @param q le quantième du jour dans l'année
	 * @param a l'année
	 */
	//@ requires Date.estAnneeValide(a);
	//@ requires Date.estQuantiemeValide(q, a);
	//@ ensures getQuantieme() == q;
	//@ ensures getAnnee() == a;
	public Date(int q, int a) {
		this._set(q, a);
	}

	/** Changer la date à partir du numéro du jour, du numéro du mois et de
	 * l'année.
	 * @param j le numéro du jour dans le mois m
	 * @param m le numéro du mois
	 * @param a l'année
	 */
	// Cette méthode est privée et déclarée comme <em>helper</em> de manière à
	// ce que les invariants de la classe ne soient pas vérifiés quand elle
	// appelée depuis une autre méthode de la classe.
	//@ requires Date.estValide(j, m, a);
	//@ ensures this.jour == j;
	//@ ensures this.mois == m;
	//@ ensures this.annee == a;
	//@ helper
	private void _set(int j, int m, int a) {
		this.jour = j;
		this.mois = m;
		this.annee = a;
	}

	/** Changer la date à partir du numéro du jour, du numéro du mois et de
	 * l'année.
	 * @param j le numéro du jour dans le mois m
	 * @param m le numéro du mois
	 * @param a l'année
	 */
	//@ requires Date.estValide(j, m, a);
	//@ ensures getJour() == j;
	//@ ensures getMois() == m;
	//@ ensures getAnnee() == a;
	public void set(int j, int m, int a) {
		this._set(j, m, a);
	}

	/** Changer la date à partir du quantième du jour dans l'année et de
	 * l'année.
	 * @param q le quantième du jour dans l'année
	 * @param a l'année
	 */
	//@ requires Date.estAnneeValide(a);
	//@ requires Date.estQuantiemeValide(q, a);
	//@ ensures getQuantieme() == q;
	//@ ensures getAnnee() == a;
	//@ helper
	private void _set(int q, int a) {
		// Initialiser l'année
		this.annee = a;

		// Initialiser le mois
		this.mois = 1;
		while (q > nbJoursDansMois(this.mois, a)) { //  le mois est terminé
			q = q - nbJoursDansMois(this.mois, a);
			this.mois++;
		}

		// Initialiser les jours
		this.jour = q;
	}

	/** Changer la date à partir du quantième du jour dans l'année et de
	 * l'année.
	 * @param q le quantième du jour dans l'année
	 * @param a l'année
	 */
	//@ requires Date.estAnneeValide(a);
	//@ requires Date.estQuantiemeValide(q, a);
	//@ ensures getQuantieme() == q;
	//@ ensures getAnnee() == a;
	public void set(int q, int a) {
		this._set(q, a);
	}

	/** Obtenir le numéro du jour dans le mois.
	 * @return le numéro du jour dans le mois
	 */
	//@ pure
	//@ helper
	public int getJour() {
		return this.jour;
	}

	/** Obtenir le numéro du mois dans l'année.
	 * @return le numéro du mois dans l'année
	 */
	//@ pure
	//@ helper
	public int getMois() {
		return this.mois;
	}

	/** Obtenir l'année.
	 * @return l'année
	 */
	//@ pure
	//@ helper
	public int getAnnee() {
		return this.annee;
	}

	/** Obtenir le quantième du jour dans l'année.
	 * @return le quantième du jour dans l'année
	 */
	//@ pure
	//@ helper
	public int getQuantieme() {
		int resultat = this.jour	// nb de jours dans le mois en cours
				+ cumulJoursMois[this.mois-1]; // nb de jours des mois précédents
		// Prendre en compte le cas de l'année bissextile
		if (this.mois > 2 && estBissextile(this.annee)) {
			resultat++;
		}

		return resultat;
	}

	/** Obtenir le nombres de jours depuis l'an 0.
	 * @return nombres de jours depuis l'an 0.
	 */
	//@ pure
	public int getNbJoursDepuisAn0() {
		int a = this.annee - 1;	// nb d'années pleines
		return this.getQuantieme()	// nb de jours dans l'année en cours
				+ 365 * a				// années considérées non bissextiles
				+ (a / 4 - a / 100 + a / 400);	// nb d'années bissextiles
	}

	/** Afficher la date sous la forme jour/mois/année.  */
	public void afficher() {
		System.out.println(this);
	}

	// Méthode définie dans objet.  Je n'utilise pas un commentaire javadoc car
	// le commentaire défini dans la classe parente est le commentaire à
	// utiliser.
	public String toString() {
		return "" + int2String(this.jour)
						+ '/' + int2String(this.mois)
						+ '/' + this.annee;
				// le "" intial est là pour forcer la conversion en String,
				// les caractères sont en effet compatibles avec les entiers !
	}

	/** Obtenir la représentation d'un entier sous la forme d'une chaîne de
	 * caractères avec au moins 2 caractères.
	 * @param entier l'entier à convertir
	 * @return  la chaîne de caractères correspondant à entier
	 */
	private static String int2String(int entier) {
		String prefixe = (entier >= 0 && entier < 10) ? "0" : "";
		return  prefixe + entier;
	}

	/** L'année a est-elle bissextile ?
	 * @param a l'année
	 * @return vrai si l'année est bissextile
	 */
	//@ pure
	public static boolean estBissextile(int a) {
		return (a % 4 == 0)			// divisible par 4
				&& ((a % 100 != 0)		// et non divible par 100
					|| (a % 400 == 0));	// sauf si divisible par 400
	}

	/** Nombre de jours dans le mois m de l'année a.
	 * @param m le numéro du mois
	 * @param a l'année
	 * @return le nombre de jours dans le mois m de l'année a
	 */
	//@ pure
	public static int nbJoursDansMois(int m, int a) {
		int resultat = nbJoursMois[m-1];	// nb de jours dans le mois
		if (m == 2 && estBissextile(a)) {	// cas du mois de février
			resultat++;
		}
		return resultat;
	}

	/** Est-ce que le numéro de jour j est valide pour le mois m de l'année a ?
	 * @param j le numéro du jour dans le mois m
	 * @param m le numéro du mois
	 * @param a l'année
	 */
	//@ requires Date.estAnneeValide(a);
	//@ requires Date.estMoisValide(m);
	//@ pure
	public static boolean estJourValide(int j, int m, int a) {
		return 1 <= j && j <= nbJoursDansMois(m, a);
	}

	/** Est-ce que le numéro de mois m est valide ?
	 * @param m le numéro du mois
	 */
	//@ ensures \result <==> (1 <= m && m <= 12);
	//@ pure
	public static boolean estMoisValide(int m) {
		return 1 <= m && m <= 12;
	}

	/** Est-ce que le quantième est valide ?
		* @param q le numéro du jour dans l'année
		* @param a l'année
		*/
	//@ pure
	//@ helper
	public static boolean estQuantiemeValide(int q, int a) {
		boolean resultat = estAnneeValide(a);
		int nbJours = 365;	// nb de jours dans l'année a;
		if (estBissextile(a)) {
			nbJours++;
		}
		resultat = resultat && 1 <= q && q <= nbJours;
		return resultat;
	}

	/** Est-ce que l'année est valide ?
	 * @param a l'année
	 */
	//@ ensures \result <==> a > 0;
	//@ pure
	public static boolean estAnneeValide(int a) {
		return a > 0;
	}

	/** Est-ce que la date définie par le jour j, le mois m et l'année a est
	 * valide ?
	 * @param j le numéro du jour dans le mois m
	 * @param m le numéro du mois
	 * @param a l'année
	 */
	//@ pure
	//@ helper
	public static boolean estValide(int j, int m, int a) {
		return estAnneeValide(a)
				&&  estMoisValide(m)
				&&  estJourValide(j, m, a);
	}

//	Relation d'ordre sur les dates
//	------------------------------

	/** Déterminer si une date est antérieure strictement à une autre date.
	 * @param autre l'autre date (non nulle)
	 * @return cette date est-elle strictement antérieure à l'autre date ?
	 */
	/*@ requires autre != null;	// l'autre date est définie
	    ensures \result == this.getAnnee() < autre.getAnnee()
	   	 || (this.getAnnee() == autre.getAnnee()
	   	 	&& getMois() < autre.getMois())
	   	 || (this.getAnnee() == autre.getAnnee()
	   		   && this.getMois() == autre.getMois()
	   		   && this.getJour() < autre.getJour());
	    pure
	@*/
	public boolean lt(Date autre) {
		if (this.getAnnee() != autre.getAnnee()) {
			return this.getAnnee() < autre.getAnnee();
		} else if (this.getMois() != autre.getMois()) {
			return  this.getMois() < autre.getMois();
		} else {
			return  this.getJour() < autre.getJour();
		}
	}

	/** Déterminer si une date est postérieure ou égale à une autre date.
	 * @param autre l'autre date (non nulle)
	 * @return cette date est-elle postérieure ou égale à l'autre date
	 */
	//@ ensures \result == ! lt(autre);
	//@ pure
	public boolean ge(Date autre) {
		return ! this.lt(autre);
	}

	/** Déterminer si une date est strictement postérieure à une autre date.
	 * @param autre l'autre date (non nulle)
	 * @return cette date est-elle strictement postérieure à l'autre date
	 */
	//@ ensures \result == autre.lt(this);
	//@ pure
	public boolean gt(Date autre) {
		return autre.lt(this);
				// Utilisation obligatoire de this (on ne peut pas s'en passer).
	}

	/** Déterminer si une date est antérieure ou égale à une autre date.
	 * @param autre l'autre date (non nulle)
	 * @return cette date est-elle antérieure ou égale à l'autre date
	 */
	//@ ensures \result == !autre.lt(this);
	//@ pure
	public boolean le(Date autre) {
		return  ! autre.lt(this);
	}

//	Égalité de deux dates
//	---------------------

	/** Déterminer si deux dates sont égales.
	 * @param autre l'autre date
	 * @return cette date est égale à l'autre date
	 */
	//@ ensures \result == (le(autre) && ge(autre));
	//@ pure
	public boolean equals(Date autre) {
		return autre != null
				&& this.getAnnee() == autre.getAnnee()
				&& this.getMois() == autre.getMois()
				&& this.getJour() == autre.getJour();
	}

	/** Déterminer si cette date est égale à un autre objet.
	 * Ceci est une redéfinition de la méthode définie dans Object
	 * @param objet l'autre objet
	 * @return cette date est égale à l'autre objet
	 */
	//@ pure
	public boolean equals(/*@ nullable @*/ Object objet) {
		return objet != null
				&& (objet instanceof Date)
				// XXX on pourrait faire aussi objet.getClass() == this.getClass()
				&& equals((Date) objet);
	}

}
// vi: sw=4 ts=4
