import java.awt.Graphics;

/**
 *  La classe Polygone decrit la notion geometrique usuelle de polygone, c'est a
 *  dire, celle d'une figure fermée plane delimitée par un nombre arbitraire de
 *  segments, chacun de ces segments formant un cote du polygone. En pratique,
 *  chaque instance de la classe Polygone est definie par un tableau d'instances
 *  de la classe Point. Chaque element de ce tableau definie un sommet du
 *  polygone et deux elements successifs (selon l'ordre que definie le tableau)
 *  forment les extremites d'un segment de droite formant un cote du polygone.
 *  Le premier et le dernier point sont joints par un segment de droite qui
 *  ferme le polygone. Le nombre de sommets d'un polygone est fixe une fois pour
 *  toute au moment de la creation de l'objet et ne peut etre modifie. Ce nombre
 *  doit etre strictement positif. Seules les modifications des coordonnees des
 *  sommets sont permises, mais ne sont possibles que par
 *  l'intermediaire des methodes de la classe Polygone.
 *
 * @author     Marc Champesme
 * @version    2 janvier 2005
 * @since      21/02/2002
 */

public class Polygone implements Cloneable {
	/*@
	  @ public invariant    Polygone.getNbPetitsPolygones() >= 0;
	  @ public invariant    Polygone.getNbGrandsPolygones() >= 0;
	  @
	  @ public invariant    getNbSommets() > 0;
	  @ public invariant    (\forall int i; i >= 0 && i < getNbSommets(); getSommet(i) != null);
	  @ public invariant    getNom() != null;
	  @*/
	private static int compteurDInstances;
	private static int nbPetitsPolygones;
	private static int nbGrandsPolygones;
	private Point[] sommets;
	private int nbSommets;
	private String monNom;


	/**
	 *  Renvoie le nombre de petits polygones crees depuis le demarrage de
	 *  l'application. Un petit polygone est un polygone possedant moins de 5
	 *  sommets.
	 *
	 * @return    nombre de petits polygones crees
	 */
	//@ pure
	public static int getNbPetitsPolygones() {
		return Polygone.nbPetitsPolygones;
	}


	/**
	 *  Renvoie le nombre de grands polygones crees depuis le demarrage de
	 *  l'application. Un grand polygone est un polygone possedant 5 sommets ou
	 *  plus
	 *
	 * @return    nombre de grands polygones crees
	 */
	//@ pure
	public static int getNbGrandsPolygones() {
		return Polygone.nbGrandsPolygones;
	}


	/**
	 *  Initialise un polygone a nbSommets sommets. Tous les sommets sont
	 *  initialises avec des coordonees (0, 0). Les coordonnees de chaque sommet
	 *  peuvent ensuite etre modifiées individuellement a l'aide de la methode
	 *  setSommet(int, Point).
	 *
	 * @param  nbSommets  Nombre total de sommets du Polygone
	 */
	/*@
	  @ requires nbSommets > 0;
	  @ ensures getNbSommets() == nbSommets;
	  @ ensures (\forall int i; i >= 0 && i < getNbSommets();
	  @				getSommet(i).getX() == 0
	  @				&& getSommet(i).getY() == 0);
	  @ ensures (nbSommets < 5) ==>
	  @		(getNbPetitsPolygones() == \old(getNbPetitsPolygones()) + 1);
	  @ ensures (nbSommets >= 5) ==>
	  @		(getNbGrandsPolygones() == \old(getNbGrandsPolygones()) + 1);
	  @*/
	public Polygone(int nbSommets) {
		this.sommets = new Point[nbSommets];
		// Il faut maintenant creer les objets Point, car la creation
		// du tableau se contente d'initialiser chaque element a null
		for (int i = 0; i < nbSommets; i++) {
			this.sommets[i] = new Point();
		}
		this.nbSommets = nbSommets;
		compteurDInstances++;
		monNom = "polygone#" + compteurDInstances;
		if (nbSommets < 5) {
			nbPetitsPolygones++;
		} else {
			nbGrandsPolygones++;
		}
	}


	/**
	 *  Initialise un polygone a nbSommets sommets. Les sommets sont initialises a
	 *  partir des points du tableau sommets.
	 *
	 * @param  sommets    les points utilises pour initialises les sommets du
	 *      polygone
	 * @param  nbSommets  Nombre total de sommets du Polygone
	 */
	/*@
	  @ requires nbSommets > 0;
	  @ requires sommets != null;
	  @ requires sommets.length >= nbSommets;
	  @ requires (\forall int i; i >= 0 && i < nbSommets;
	  @					sommets[i] != null);
	  @ ensures getNbSommets() == nbSommets;
	  @ ensures (\forall int i; i >= 0 && i < getNbSommets();
	  @			getSommet(i).equals(sommets[i]));
	  @ ensures (nbSommets < 5) ==>
	  @		(getNbPetitsPolygones() == \old(getNbPetitsPolygones()) + 1);
	  @ ensures (nbSommets >= 5) ==>
	  @		(getNbGrandsPolygones() == \old(getNbGrandsPolygones()) + 1);
	  @*/
	public Polygone(Point[] sommets, int nbSommets) {
		this.sommets = new Point[nbSommets];
		for (int i = 0; i < nbSommets; i++) {
			this.sommets[i] = sommets[i];
		}
		this.nbSommets = nbSommets;
		compteurDInstances++;
		monNom = "polygone#" + compteurDInstances;
		if (nbSommets < 5) {
			nbPetitsPolygones++;
		} else {
			nbGrandsPolygones++;
		}
	}


	/**
	 *  Fixe les coordonnees du sommet de numero numSommet du polygone a la valeur
	 *  des coordonnees du point unPoint.
	 *
	 * @param  numSommet  numero du sommet a modifier
	 * @param  unPoint    point specifiant les nouvelles coordonnees du sommet a
	 *      modifier.
	 */
	/*@
	  @ requires (numSommet >= 0) && (numSommet < getNbSommets());
	  @ requires unPoint != null;
	  @ ensures getSommet(numSommet).equals(unPoint);
	  @*/
	public void setSommet(int numSommet, Point unPoint) {
		sommets[numSommet] = unPoint;
	}


	/**
	 *  Renvoie une instance de la classe Point dont les coordonnees sont celles du
	 *  sommet de numero numSommet.
	 *
	 * @param  numSommet  numero du sommet
	 * @return            le sommet de numero numSommet du polygone
	 */
	/*@
	  @ requires (numSommet >= 0) && (numSommet < getNbSommets());
	  @ ensures \result != null;
	  @ pure
	  @*/
	public Point getSommet(int numSommet) {
		return sommets[numSommet];
	}


	/**
	 *  Renvoie le nombre de sommets du polygone.
	 *
	 * @return    le nombre de sommets du polygone
	 */
	/*@
	  @ ensures \result > 0;
	  @ pure
	  @*/
	public int getNbSommets() {
		return nbSommets;
	}


	/**
	 *  Renvoie le nombre de sommets du polygone.
	 *
	 * @return    le nombre de sommets du polygone
	 */
	/*@
	  @ ensures \result != null;
	  @ pure
	  @*/
	public String getNom() {
		return monNom;
	}


	/**
	 *  Calcule et renvoie le périmètre de ce polygone
	 *
	 * @return    le périmètre de ce polygone
	 */
	/*@
	  @ ensures \result
	  @		== (\sum int i; i >= 0 && i < getNbSommets();
	  @			  getSommet(i).distance(getSommet((i + 1) % getNbSommets())));
	  @ pure
	  @*/
	public double perimetre() {
		double lg;

		lg = 0;
		for (int i = 0; i < getNbSommets(); i++) {
			Point p1;
			Point p2;

			p1 = getSommet(i);
			if (i == (getNbSommets() - 1)) {
				p2 = getSommet(0);
			} else {
				p2 = getSommet(i + 1);
			}
			lg += p1.distance(p2);
		}
		return lg;
	}


	/**
	 *  Renvoie un polygone de meme rayon que ce polygone et dont le centre est
	 *  obtenu par symetrie par rapport a l'axe des ordonnees
	 *
	 * @return    Un polygone symétrique
	 */
	/*@
	  @ ensures \fresh(\result);
	  @ ensures \result.getNbSommets() == getNbSommets();
	  @ ensures (\forall int i; i >= 0 && i < getNbSommets();
	  @			\result.getSommet(i).equals(getSommet(i).symetrie()));
	  @ pure
	  @*/
	public Polygone symetrie() {
		Point[] symSommets;

		symSommets = new Point[getNbSommets()];
		for (int i = 0; i < getNbSommets(); i++) {
			symSommets[i] = getSommet(i).symetrie();
		}

		return new Polygone(symSommets, getNbSommets());
	}


	/**
	 *  Affiche ce polygone dans le contexte graphique spécifié
	 *
	 * @param  g  contexte graphique dans lequel afficher
	 */
	/*@
	  @ requires g != null;
	  @*/
	public void paint(Graphics g) {
		for (int i = 0; i < getNbSommets(); i++) {
			Segment s;
			Point p1;
			Point p2;

			p1 = getSommet(i);
			if (i == (getNbSommets() - 1)) {
				p2 = getSommet(0);
			} else {
				p2 = getSommet(i + 1);
			}
			s = new Segment(p1, p2);
			s.paint(g);
		}
	}


	/**
	 *  Compare ce polygone avec l'objet spécifié et renvoie vrai si cet objet est
	 *  un Polygone de memes sommets.
	 *
	 * @param  obj  L'objet a comparer avec ce polygone.
	 * @return      true si l'objet spécifié est un polygone de memes sommets ;
	 *      false sinon.
	 */
	/*@ also
	  @ 	requires !(obj instanceof Polygone);
	  @ 	ensures !\result;
	  @ also
	  @ 	requires obj instanceof Polygone;
	  @	ensures \result <==> (((Polygone) obj).getNbSommets() == getNbSommets()
	  @			      && (\forall int i; i >= 0 && i < getNbSommets();
	  @					((Polygone) obj).getSommet(i).equals(getSommet(i))));
	  @ also
	  @	ensures \result ==> hashCode() == ((Polygone) obj).hashCode();
	  @ pure
	  @*/
	public boolean equals(Object obj) {
		if (obj instanceof Polygone) {
			Polygone autrePolygone = (Polygone) obj;
			boolean pareil = (getNbSommets() == autrePolygone.getNbSommets());

			for (int i = 0; i < getNbSommets() && pareil; i++) {
				pareil = autrePolygone.getSommet(i).equals(getSommet(i));
			}
			return pareil;
		}

		return false;
	}


	/**
	 *  Renvoie un clone de ce polygone
	 *
	 * @return    un clone de ce polygone
	 */
	/*@ also
	  @ ensures \fresh(\result);
	  @ ensures \result.equals(this);
	  @ pure
	  @*/
	public Object clone() {
		Polygone result;
		try {
			result = (Polygone) super.clone();
		} catch (CloneNotSupportedException e) {
			// Impossible car Object implemente clone
			throw new InternalError(e.toString());
		}

		result.sommets = (Point[]) result.sommets.clone();
		return result;
	}


	/**
	 *  Renvoie le code de hashage pour ce polygone
	 *
	 * @return    Le code de hashage de ce polygone.
	 */
	/*@ also
	  @ requires (\forall int i; i >= 0 && i < getNbSommets();
	  @					getSommet(i).getX() == 0
	  @					&& getSommet(i).getY() == 0);
	  @ ensures \result == 0;
	  @ pure
	  @*/
	public int hashCode() {
		int res = 0;

		for (int i = 0; i < getNbSommets(); i++) {
			res += 31 * getSommet(i).hashCode();
		}

		return res;
	}



	/**
	 *  Renvoie une chaine de caractères représentant ce polygone.
	 *
	 * @return    une chaine de caractères contenant les caractéristiques de ce
	 *      polygone.
	 */
	/*@ also
	  @ ensures \result != null;
	  @ ensures (\forall int i; i >= 0 && i < getNbSommets();
	  @		\result.indexOf(getSommet(i).toString()) >= 0);
	  @ pure
	  @*/
	public String toString() {
		String res = getNom() + ":[" + getSommet(0);

		for (int i = 1; i < getNbSommets(); i++) {
			res += ", " + getSommet(i);
		}

		return res + "]";
	}
}

