import java.util.Iterator;
import OclCollections.*;
import static OclCollections.Collections.*;

public class FonctionsEnsembles<T extends Comparable<T>> {
    
    ///////////////////////////////////////////////////////////////////////

    /** Determine l'element maximum parmi un ensemble d'entiers non
     * negatifs.  Les elements n'etant jamais negatifs, il est donc
     * possible, et acceptable, de retourner 0 si l'ensemble est vide
     * (de la meme facon que la somme des elements d'un ensemble vide
     * donne 0).
     *
     * @param ens l'ensemble a traiter
     * @return l'element maximum parmi les divers elements de l'ensemble ens
     */
    static public Integer maximum( final Set<Integer> ens ) {
   	Integer leMax = 0;
	for( Integer x: ens ) {
	    if ( x > leMax ) {
		leMax = x;
	    }
	}
	return leMax;
    }

    static public Integer maximum_( final Set<Integer> ens ) {
	assert ens != null;
	assert ens.forAll( new Expression<Integer,Boolean>() {
	    public Boolean eval( Integer x ) {
		return x >= 0;
	    } } );

 	return ens.iterate( 
			   0,
			   new BinaryExpression<Integer,Integer>() { 
	    public Integer eval( Integer e, Integer leMax ) {
		return e > leMax ? e : leMax;
	    } } );

    }


    //
    ///////////////////////////////////////////////////////////////////////

    /** Determine l'element minimum parmi un ensemble d'elements, de
     * type arbitraire, mais qui peuvent etre compares entre eux.
     *
     * @param ens l'ensemble a traiter
     * @return l'element minimum parmi les divers elements de l'ensemble ens
     */
    static public <T extends Comparable<T>> T minimum( final Set<T> ens ) {
	T leMin = unElement(ens);
	for( T x: ens ) {
	    if ( x.compareTo(leMin) < 0 ) {
		leMin = x;
	    }
	}
	return leMin;
    }

    static private <T> T unElement( Set<T> s ) {
        for( T e : s ) {
            return e;
        }
        return null;
    }

    static public <T extends Comparable<T>> T minimum_( final Set<T> ens ) {
 	assert ens != null && ens.size() != 0;

 	return ens.iterate( 
			   unElement_(ens), 
			   new BinaryExpression<T,T>() { 
	    public T eval( T e, T leMin ) {
		return e.compareTo(leMin) <= 0 ? e : leMin;
	    } } );
    }

    static private <T> T unElement_( Set<T> s ) {
        assert s != null && s.size() != 0;

	return 
	    s.any( new Expression<T,Boolean>() {
		public Boolean eval( T x ) {
		    return true;
		} } );
    }



    //
    ///////////////////////////////////////////////////////////////////////

    /** Retourne une sequence, ordonnee (en ordre croissant),
     * contenant chacun des elements de l'ensemble de depart.
     *
     * @param ens l'ensemble a traiter
     * @return la sequence ordonnee avec tous les elements de l'ensemble ens
     */
    static public <T extends Comparable<T>> Sequence<T> elementsOrdonnes( final Set<T> ens ) {
	Sequence<T> seq = emptySequence();
	Set<T> nonSelectionnes = ens;
	while ( nonSelectionnes.size() != 0 ) {
	    T x = FonctionsEnsembles.minimum( nonSelectionnes );
	    seq = seq.append( x );
	    nonSelectionnes = nonSelectionnes.excluding( x );
	}
	return seq;
    }


    //
    ///////////////////////////////////////////////////////////////////////

    /** Etant donne un ensemble d'ensembles, retourne un ensemble qui
     * contient les elements qui sont communs a chacun des ensembles.
     *
     * @param ens l'ensemble a traiter
     * @return l'ensemble des elements communs a chacun des ensembles de l'ensemble ens
     */
    static public <T> Set<T> elementsCommuns( final Set<Set<T>> ens ) {
	if ( ens.isEmpty() ) {
	    return emptySet();
	}

	Set<T> res = unElement(ens);
	for( Set<T> e : ens ) {
	    res = res.intersection(e);
	}
	return res;
    }

    @SuppressWarnings("unchecked")
	static public <T> Set<T> elementsCommuns_( final Set<Set<T>> ens ) {
	assert ens != null;

	return 
	    ens.isEmpty()   
	    ? ((Set<T>) emptySet())
	    : ens.iterate( unElement(ens),
			   new BinaryExpression<Set<T>,Set<T>>() {
		public Set<T> eval( Set<T> e, Set<T> communs ) {
		    return communs.intersection(e);
		} } );
    }


    //
    ///////////////////////////////////////////////////////////////////////

    /** Etant donne un ensemble d'entiers arbitraires, retourne le
     * sous-ensemble des elements qui sont compris entre deux bornes.
     *
     * @param ens l'ensemble a traiter
     * @param inf la borne inferieure
     * @param sup  la borne superieure
     * @return l'ensemble des chaines pour les elements positifs de ens
     */
    static public Set<Integer> elementsDansIntervalle( final Set<Integer> ens, final Integer inf, final Integer sup ) {
	Set<Integer> res = emptySet();
	for( Integer x: ens ) {
	    if  ( inf <= x && x <= sup ) {
		res = res.including( x );
	    }
	}
	return res;
    }

    static public Set<Integer> elementsDansIntervalle_( final Set<Integer> ens, final Integer inf, final Integer sup ) {
	assert ens != null;

	return ens
	    .select( new Expression<Integer,Boolean>() {
		public Boolean eval( Integer x ) { 
		    return inf <= x && x <= sup; 
		} } );
		      
    }


    //
    ///////////////////////////////////////////////////////////////////////

    /** Etant donne un ensemble d'entiers arbitraires, retourne un
     * ensemble contenant la chaine (toString) pour les differents
     * entiers de l'ensemble de depart qui sont positifs.
     *
     * @param ens l'ensemble a traiter
     * @return l'ensemble des chaines pour les elements positifs de ens
     */
    static public Set<String> stringDesPositifs( final Set<Integer> ens ) {
	assert ens != null;

	return ens
	    .select( new Expression<Integer,Boolean>() {
		public Boolean eval( Integer x ) { 
		    return x > 0; 
		} } )
	    .collect( new Expression<Integer,String>() {
		public String eval( Integer x ) {
		    return x.toString();
		} } )
            .asSet();
    }
}
