import java.util.*;
import java.math.BigInteger;

class Split {
    // OVERVIEW: Record type for returning a split.
    public /*@non_null@*/ SpeciesSet left;
    public /*@non_null@*/ SpeciesSet right;

    public Split (/*@non_null@*/ SpeciesSet left, /*@non_null@*/ SpeciesSet right) {
	this.left = left; this.right = right;
    }

    public String toString () {
	return "< " + left.toString () + ", " + right.toString () + ">";
    }
}

public class AllTrees {
    // OVERVIEW: AllTrees provides the allTreesRoot method that returns the set of all possible
    //    trees that can be produced from s with a given root.
    
    private static Split [] allSplits (SpeciesSet s)
	//@ensures \nonnullelements (\result)
	// EFFECTS: Returns an array of splits showing all possible ways of splitting the s into two groups.
	//     e.g., allSplits ({a, b, c}) = [<{a, b, c}, {}>, <{a, b}, {c}>, <{a}, {b, c}>, <{a, c}, {b}>]
    {
	if (s == null) { return null; }

	// There are 2^n groups, we can enumerate them using the bits
	BigInteger numgroups = BigInteger.valueOf (2).pow (s.size ()); //@nowarn Null ;
	//@assume numgroups != null; // ESC/Java spec for BigInteger is missing, need to assume.

	int numiters = numgroups.intValue () / 2;
	//@assume numiters >= 0;

	Split [] result = new Split [numiters];
	Species [] elements = s.toArray ();

	BigInteger b = BigInteger.ZERO; 
	//@assume b != null;

	for (int i = 0; i < numiters; i++) {
	    SpeciesSet left = new SpeciesSet ();
	    SpeciesSet right = new SpeciesSet ();
	    
	    for (int bitno = 0; bitno < numgroups.bitLength () - 1; bitno++) {
		//@assume bitno < elements.length ;
		
		if (b.bitLength () >= bitno && b.testBit (bitno)) {
		    right.insert (elements [bitno]);
		} else {
		    left.insert (elements [bitno]);
		}
	    }

	    result[i] = new Split (left, right); //@nowarn IndexNegative, IndexTooBig
	    b = b.add (BigInteger.ONE);
	    //@assume b != null
	}

	return result; 
    } //@nowarn Post // can't establish no null elements postcondition

    public static /*@non_null@*/ SpeciesTreeSet allTreesRoot (SpeciesSet s, /*@non_null@*/ Species r) {
	// EFFECTS: Returns the set of all trees with root r, and leaves s.
	SpeciesTreeSet res = new SpeciesTreeSet ();

	if (s == null || s.isEmpty ()) {
	    res.insert (new SpeciesTree (r, null, null));
	    return res;
	} 

	Split [] splits = allSplits (s);

	if (splits == null) {
	    System.err.println ("BUG: no splits (shouldn't happen when s is non-empty)");
	    System.exit (1);
	}

	for (int i = 0; i < splits.length; i++) {
	    Split current = splits[i];
	    SpeciesTreeSet ltrees = allTrees (current.left);
	    SpeciesTreeSet rtrees = allTrees (current.right);
	    
	    Enumeration liter = ltrees.elements ();
	    
	    if (!ltrees.isEmpty ()) {
		while (liter.hasMoreElements ()) {
		    SpeciesTree ltree = (SpeciesTree) liter.nextElement ();
		    
		    if (!rtrees.isEmpty ()) {
			Enumeration riter = rtrees.elements ();
			
			while (riter.hasMoreElements ()) {
			    SpeciesTree rtree = (SpeciesTree) riter.nextElement ();
			    res.insert (new SpeciesTree (r, ltree, rtree));
			}
		    } else {
			res.insert (new SpeciesTree (r, ltree, null));
		    }
		}
	    } else {
		System.err.println ("BUG: left trees cannot be empty!");
		System.exit (1);
	    }
	}

	return res;
    }

    private static /*@non_null@*/ SpeciesTreeSet allTrees (SpeciesSet s) {
	// EFFECTS: Returns the set of all possible trees that can be produced from s.

	if (s == null || s.isEmpty ()) {
	    return new SpeciesTreeSet ();
	} 

	SpeciesTreeSet res = new SpeciesTreeSet ();
	Species [] elements = s.toArray ();
	
	if (elements.length == 1) {
	    res.insert (new SpeciesTree (elements[0]));
	} else {
	    for (int i = 0; i < elements.length; i++) {
		// Create a new set with all elements except this one.
		SpeciesSet rest = new SpeciesSet (s);
		rest.remove (elements[i]); 
		res.union (allTreesRoot (rest, elements[i]));
	    }
	}
	
	return res;
    }
}