////////////////////////////////////////////////////////////////
//
// Lab7
//
////////////////////////////////////////////////////////////////

public class Lab7 {

    public static void main (String[] args) {
	new IStack0Test (new ListStack0()).go();
	new IStack0Test (new ArrayStack0()).go();
	new IStackTest (new ListStack()).go();
	new IStackTest (new ArrayStack()).go();
    }

}

////////////////////////////////////////////////////////////////
//
// ISelector
//
////////////////////////////////////////////////////////////////

interface ISelector {
    boolean select (Object x);   // do we want the given object?
}

////////////////////////////////////////////////////////////////
//
// IComparator
//
// This is essentially the same as java.util.Comparator,
// which ProfessorJ probably doesn't support.
//
////////////////////////////////////////////////////////////////

interface IComparator {

    // Returns a negative int if x is regarded as less than y,
    // zero if x is regarded as equal to y,
    // a positive int if x is regarded as greater than y.

    int compare (Object x, Object y);
}

////////////////////////////////////////////////////////////////
//
// IRange
//
////////////////////////////////////////////////////////////////

interface IRange {

    // Do any elements remain to be generated?
    boolean hasMore(); 

    // Return the current element being generated.
    Object current(); 

    // Skip the current element and go on to the remaining ones, if any.
    void next(); 

}

////////////////////////////////////////////////////////////////
//
// AList - reusable classes for representing lists of objects
//
// We have been developing these classes throughout the semester.
//
////////////////////////////////////////////////////////////////

abstract class AList {

    // Is this list empty?
    public abstract boolean isEmpty ();

    // Assuming this list is not empty, what is its first element?
    public abstract Object first ();

    // Assuming this list is not empty, what are its remaining elements?
    public abstract AList rest ();

    // Returns an IRange that generates the elements of this list in order.
    public IRange iterator () {
        return new ListRanger (this);
    }

    // How many elements are in this list?
    public int length () {
        if (this.isEmpty())
	    return 0;
	else return 1 + this.rest().length();
    }

    // Return the elements of this list as a list in reverse order.
    public AList reverse () {
	IRange ir = this.iterator();
	AList result = new MTList();
	for ( ; ir.hasMore(); ir.next()) {
	    result = new ConsList (ir.current(), result);
	}
	return result;
    }

    // Does this list contain the given object?
    public boolean contains (Object x) {
	boolean result = false;
	for (IRange ir = this.iterator(); ir.hasMore(); ir.next()) {
	    if (ir.current().equals (x))
		result = true;
	}
	return result;
    }

    // Is this list equal to that one?
    public boolean equals (Object that) {
        IRange ir1 = this.iterator();
        IRange ir2 = ((AList) that).iterator();
	boolean equalSoFar = true;

	for ( ; equalSoFar && ir1.hasMore() && ir2.hasMore(); ) {
	    if (ir1.current() != ir2.current())
		equalSoFar = false;
	    ir1.next();
	    ir2.next();
	}

	// The loop terminated because at least one of these is true:
	// -- equalSoFar is false (we found unequal elements)
	// -- ir1 has no more elements to generate
	// -- ir2 has no more elements to generaet

	if (ir1.hasMore() != ir2.hasMore())
	    return false;
	else return equalSoFar;
    }

    // Return the selected elements of this list.
    public abstract AList filter (ISelector is);

    // Return the elements of this list in the specified order.
    public abstract AList sort (IComparator ic);

    // Insert the given object in front of the first element of
    // this list that is larger than it according to the IComparator.
    abstract AList insert (Object x, IComparator ic);
}

class MTList extends AList {
    public MTList () { }

    public boolean isEmpty () { return true; }

    public Object first () { throw new RuntimeException (ERRORMSG); }
    public AList rest () { throw new RuntimeException (ERRORMSG); }

    // This is how constants are declared in Java.
    static final String ERRORMSG
	= "The first and rest methods aren't defined on empty lists.";

    public AList filter (ISelector is) { return this; }

    public AList sort (IComparator ic) { return this; }

    AList insert (Object x, IComparator ic) {
        return new ConsList (x, this);
    }
}

class ConsList extends AList {
    Object fst;    // the first element of this list
    AList rst;     // the rest of this list

    public ConsList (Object fst, AList rst) {
        this.fst = fst;
        this.rst = rst;
    }

    public boolean isEmpty () { return false; }

    public Object first () { return this.fst; }
    public AList rest () { return this.rst; }

    public AList filter (ISelector is) {
	if (is.select (this.fst))
	    return new ConsList (this.fst, this.rst.filter (is));
	else return this.rst.filter(is);
    }

    public AList sort (IComparator ic) {
	return this.rst.sort (ic).insert (this.fst, ic);
    }

    AList insert (Object x, IComparator ic) {
	if (ic.compare (x, this.fst) <= 0)
	    return new ConsList (x, this);
	else return new ConsList (this.fst, this.rst.insert (x, ic));
    }
}

class ListRanger implements IRange {

    AList contents;  // the list of elements that remain to be generated

    ListRanger (AList contents) {
	this.contents = contents;
    }

    public boolean hasMore () {
        return ! this.contents.isEmpty();
    }

    public Object current () {
	return this.contents.first();
    }

    public void next () {
        this.contents = this.contents.rest();
    }

}

////////////////////////////////////////////////////////////////
//
// tests for AList and ListRanger
//
////////////////////////////////////////////////////////////////

class TestLists implements Testable {

    String s1 = "one";
    String s2 = "two";
    String s3 = "three";
    String s4 = "four";
    String s5 = "five";

    TestLists () { }

    void go () {
        Tester tester = new Tester ();
        tester.runTests (this);
    }

    public void tests (Tester t) {
        AList mt1 = new MTList();
	AList mt2 = new MTList();

	AList l1 = new ConsList (s1, mt1);
	AList l2 = new ConsList (s2, mt2);

	AList lst5 = new ConsList (s5, mt2);
	AList lst4 = new ConsList (s4, lst5);
	AList lst3 = new ConsList (s3, lst4);
	AList lst2 = new ConsList (s2, lst3);
	AList lst1 = new ConsList (s1, lst2);

	AList list5 = new ConsList (s5, mt1);
	AList list4 = new ConsList (s4, list5);
	AList list3 = new ConsList (s4, list4);  // note difference!
	AList list2 = new ConsList (s2, list3);
	AList list1 = new ConsList (s1, list2);

	// s1 through s5 in alphabetical order
	AList alpha = new ConsList (s2, mt1);
	alpha = new ConsList (s3, alpha);
	alpha = new ConsList (s1, alpha);
	alpha = new ConsList (s4, alpha);
	alpha = new ConsList (s5, alpha);

	t.test (mt1.isEmpty(), true);
	t.test (mt2.isEmpty(), true);
	t.test (l1.isEmpty(), false);
	t.test (lst1.isEmpty(), false);

	t.test (l1.first(), s1);
	t.test (lst2.first(), s2);

	t.test (l1.rest().isEmpty(), true);
	t.test (lst4.rest().isEmpty(), false);
	t.test (lst4.rest().first(), s5);

	IRange ir0 = mt2.iterator();
	t.test (ir0.hasMore(), false);

	IRange ir1 = lst1.iterator();
	t.test (ir1.hasMore(), true);
	t.test (ir1.current(), s1);
	t.test (ir1.current(), s1);
	ir1.next();
	t.test (ir1.hasMore(), true);
	t.test (ir1.current(), s2);
	ir1.next();
	t.test (ir1.hasMore(), true);
	t.test (ir1.current(), s3);
	ir1.next();
	t.test (ir1.hasMore(), true);
	t.test (ir1.current(), s4);
	ir1.next();
	t.test (ir1.hasMore(), true);
	t.test (ir1.current(), s5);
	t.test (ir1.current(), s5);
	ir1.next();
	t.test (ir1.hasMore(), false);

	t.test (mt1.length(), 0);
	t.test (lst5.length(), 1);
	t.test (lst4.length(), 2);
	t.test (lst1.length(), 5);

	t.test (mt1.equals (mt2), true);
	t.test (l1.equals (l1), true);
	t.test (l1.equals (l2), false);
	t.test (lst4.equals (list4), true);
	t.test (lst3.equals (list3), false);
	t.test (lst1.equals (list1), false);
	t.test (list1.equals (list1), true);

	t.test (list1.rest().equals (list2), true);
	t.test (list3.rest().equals (lst4), true);

	t.test (mt1.reverse().equals (mt2), true);
	t.test (l1.reverse().first(), s1);
	t.test (l1.reverse().length(), 1);
	t.test (lst1.reverse().first(), s5);
	t.test (lst1.reverse().reverse().equals (lst1), true);

	t.test (mt1.contains (s1), false);
	t.test (l1.contains (s1), true);
	t.test (l2.contains (s1), false);
	t.test (lst1.contains (s1), true);
	t.test (lst1.contains (s5), true);
	t.test (list1.contains (s3), false);

	ISelector is1 = new ISelector () {
		public boolean select (Object x) { return true; }
	    };
	ISelector is2 = new ISelector () {
		public boolean select (Object x) { return false; }
	    };
	ISelector is3 = new ISelector () {
		public boolean select (Object x) {
		    return ((String) x).charAt (0) == 'f';
		}
	    };
	t.test (mt1.filter (is1).isEmpty(), true);
	t.test (lst1.filter (is1).equals (lst1), true);
	t.test (lst1.filter (is2).equals (mt1), true);
	t.test (lst1.filter (is3).length(), 2);
	t.test (lst1.filter (is3).first(), s4);
	t.test (lst1.filter (is3).rest().first(), s5);

	IComparator ic1 = new IComparator () {
		public int compare (Object x, Object y) {
		    return ((String) x).compareTo (y);
		}
	    };
	IComparator ic2 = new IComparator () {
		public int compare (Object x, Object y) {
		    return ((String) y).compareTo (x);
		}
	    };
	t.test (mt1.sort (ic1).isEmpty(), true);
	t.test (lst1.sort (ic1).length(), 5);
	t.test (lst1.sort (ic1).equals (alpha), true);
	t.test (lst1.sort (ic2).equals (alpha.reverse()), true);
    }
}

////////////////////////////////////////////////////////////////
//
// Tester
//
////////////////////////////////////////////////////////////////

interface Testable {
    void tests (Tester t);
}

class Tester {

    int n;
    int errors;

    Tester () { this.n = 1; this.errors = 0; }

    void runTests (Testable f) {
	n = 1;
	try {
	    f.tests (this);
	}
	catch (Throwable e) {
	    System.out.println ("Threw exception during test " + n);
	    System.out.println (e);
	}
	finally {
	    done();
	}
    }

    void test (boolean b1, boolean b2) {
	test (b1 == b2);
    }

    void test (int i, int j) {
	test (i == j);
    }

    void test (Object s1, String s2) {
	test (s2.equals (s1));
    }

    void test (boolean b) {
	if (! b) {
	    System.out.println ("***** Failed test " + n + " *****");
	    errors = errors + 1;
	}
	n = n + 1;
    }

    void done () {
	if (errors > 0)
	    System.out.print ("Failed " + errors + " out of ");
        else
	    System.out.print ("Passed all ");
	System.out.println (n + " tests.");
    }

}

////////////////////////////////////////////////////////////////
//
// ATune
//
////////////////////////////////////////////////////////////////

// An ATune represents an audio recording of some kind.

abstract class ATune {

    String title;          // the name of the recording
    String artist;         // the perpetrator of the recording
    int[] bits;            // the content of the recording

}

// A MIDI is an ATune in Musical Instrument Digital Interface format.
// See http://en.wikipedia.org/wiki/MIDI_file for more details.
//
// Example:
// int[] bits1 = new int[914] {1297377380, 6, 1, 67128660, 1919614976, ...};
// ATune theLeaRig = new MIDI ("The Lea Rig", "Robert Burns", bits1);

class MIDI extends ATune {

    MIDI (String title, String artist, int[] bits) {
        this.title = title;
        this.artist = artist;
        this.bits = bits;
    }

}

// A MP3 is an ATune in MPEG Audio Layer 3 format.
// See http://en.wikipedia.org/wiki/MP3 for more details.
//
// Example:
// int[] bits2 = new int[729612] {1229206275, 0, 24532048, 1160839168 ...};
// ATune elegie = new MP3 ("Elegie", "Jules Massenet", bits2);

class MP3 extends ATune {

    MP3 (String title, String artist, int[] bits) {
        this.title = title;
        this.artist = artist;
        this.bits = bits;
    }

}

// A WAV is an ATune in WAVEform audio format.  For more details, see
// http://www.wordiq.com/cgi-bin/knowledge/lookup.cgi?title=WAV
//
// Example:
// int[] bits3 = new int [9746111] {1380533830, 4107948546, 1463899717, ...};
// ATune storekeeper = new WAV ("Storekeeper", "James McMurtry", bits3);

class WAV extends ATune {

    WAV (String title, String artist, int[] bits) {
        this.title = title;
        this.artist = artist;
        this.bits = bits;
    }

}

////////////////////////////////////////////////////////////////
//
// IStack0
//
////////////////////////////////////////////////////////////////

// An IStack0 represents a mutable stack of objects.

interface IStack0 {

    // Is this stack empty?
    boolean isEmpty ();

    // Pushes the given object onto this stack.
    void push (Object x);

    // Pops the topmost object off this stack and returns it.
    Object pop ();
}

// An IStack0Test tests an implementation of IStack0.
//
// Examples:
// new IStack0Test (new ListStack0()).go();
// new IStack0Test (new ArrayStack0()).go();

class IStack0Test implements Testable {

    IStack0 stk;  // the IStack0 to be tested

    IStack0Test (IStack0 stk) { this.stk = stk; }

    // The IStack0 to be tested should be empty when go is called.

    void go () {
	Tester tester = new Tester ();
	tester.runTests (this);
    }

    String s1 = "one";
    String s2 = "two";
    String s3 = "three";
    String s4 = "four";
    String s5 = "five";
    int big = 1000;

    public void tests (Tester t) {
	t.test (stk.isEmpty(), true);
	stk.push (s1);
	t.test (stk.isEmpty(), false);
	t.test (stk.pop(), s1);
	t.test (stk.isEmpty(), true);
	stk.push (s1);
	stk.push (s2);
	stk.push (s3);
	t.test (stk.pop(), s3);
	t.test (stk.pop(), s2);
	t.test (stk.pop(), s1);
	t.test (stk.isEmpty(), true);
	stk.push (s5);
	stk.push (s4);
	for (int i = 0; i < big; i = i + 1)
	    stk.push (s1);
	for (int i = 0; i < big; i = i + 1) {
	    t.test (stk.isEmpty(), false);
	    t.test (stk.pop(), s1);
	}
	t.test (stk.isEmpty(), false);
	t.test (stk.pop(), s4);
	t.test (stk.isEmpty(), false);
	t.test (stk.pop(), s5);
	t.test (stk.isEmpty(), true);
    }
}

////////////////////////////////////////////////////////////////
//
// Two different implementations of IStack0
//
////////////////////////////////////////////////////////////////

// A ListStack0 represents its state as an AList.
//
// Example:
// IStack0 s0 = new ListStack0();

class ListStack0 implements IStack0 {

    // state.first() is the topmost element.
    AList state;

    ListStack0 () { this.state = new MTList(); }

    public boolean isEmpty () {
        return this.state.isEmpty();
    }

    public void push (Object x) {
	this.state = new ConsList (x, this.state);
    }

    public Object pop () {
	Object x = this.state.first();
	this.state = this.state.rest();
	return x;
    }

}


// An ArrayStack0 represents its state as an array.
//
// Example:
// IStack0 s0 = new ArrayStack0();

class ArrayStack0 implements IStack0 {

    // the number of elements in the stack
    int count;

    // state [count-1] is the topmost element
    Object[] state;

    // Constants.

    // the initial size of the state array
    static final int INITIAL_CAPACITY = 5;

    // the expansion factor to use when the state overflows
    static final int EXPANSION_FACTOR = 2;

    ArrayStack0 () {
	this.count = 0;
	this.state = new Object[INITIAL_CAPACITY];
    }

    public boolean isEmpty () {
        return this.count == 0;
    }

    public void push (Object x) {
	if (count >= state.length)
	    this.expandCapacity();
	this.state[this.count] = x;
	this.count = this.count + 1;
    }

    public Object pop () {
	Object x = this.state[this.count-1];
	this.count = this.count - 1;
	return x;
    }

    // Increases the capacity of this stack's state array.

    void expandCapacity () {
	Object[] oldarray = this.state;
	this.state = new Object [EXPANSION_FACTOR * this.count];
	for (int i = 0; i < this.count; i = i + 1)
	    this.state[i] = oldarray[i];
    }

}

////////////////////////////////////////////////////////////////
//
// IStack
//
////////////////////////////////////////////////////////////////

// An IStack represents a mutable stack of objects.

interface IStack extends IStack0 {

    // Returns an IRange that generates the elements of this stack,
    // in order beginning with the topmost.
    IRange iterator();
}

// An IStackTest tests an implementation of IStack.
//
// Examples:
// new IStackTest (new ListStack()).go();
// new IStackTest (new ArrayStack()).go();

class IStackTest extends IStack0Test implements Testable {

    IStackTest (IStack stk) { super (stk); }

    // The IStack to be tested should be empty when go is called.

    void go () {
	Tester tester = new Tester ();
	tester.runTests (this);
    }

    public void tests (Tester t) {
	super.tests(t);
	stk.push (s5);
	stk.push (s4);
	for (int i = 0; i < big; i = i + 1)
	    stk.push (s1);
	IRange ir = ((IStack) stk).iterator();
	for (int i = 0; i < big; i = i + 1) {
	    t.test (ir.hasMore());
	    t.test (ir.current(), s1);
	    ir.next();
	}
	t.test (ir.hasMore(), true);
	t.test (ir.current(), s4);
	ir.next();
	t.test (ir.hasMore(), true);
	t.test (ir.current(), s5);
	ir.next();
	t.test (ir.hasMore(), false);
    }
}

////////////////////////////////////////////////////////////////
//
// Two different implementations of IStack0
//
////////////////////////////////////////////////////////////////

// A ListStack represents its state as an AList.
//
// Example:
// IStack stk = new ListStack();

class ListStack extends ListStack0 implements IStack {

    ListStack () { this.state = new MTList(); }

    public IRange iterator () {
	return new ListRanger (this.state);
    }
}


// An ArrayStack represents its state as an array.
//
// Example:
// IStack stk = new ArrayStack();

class ArrayStack extends ArrayStack0 implements IStack {

    ArrayStack () {
	this.count = 0;
	this.state = new Object[INITIAL_CAPACITY];
    }

    public IRange iterator () {
	return new ReverseArrayRange (this.trimmedState());
    }

    // Returns a trimmed copy of this stack's state array.

    Object[] trimmedState () {
	Object[] newarray = new Object [this.count];
	for (int i = 0; i < this.count; i = i + 1)
	    newarray[i] = this.state[i];
	return newarray;
    }
}

// A ReverseArrayRange generates the elements of an array in reverse order.

class ReverseArrayRange implements IRange {

    Object[] things;       // the objects to be generated
    int count;             // how many objects remain to be generated

    ReverseArrayRange (Object[] things) {
	this.things = things;
	this.count = things.length;
    }

    public boolean hasMore() { return this.count > 0; }

    public Object current() { return this.things [count - 1]; }

    public void next() { this.count = this.count - 1; }

}

