////////////////////////////////////////////////////////////////
// File lab6-part2.java

// ------------------------------------------------------------------
// interface to represent a method from object to boolean
interface IObj2Bool{
  boolean select(Object obj);
}

// ------------------------------------------------------------------
//   Functional (External) Iterator Pattern 
interface IRange {

    // move the cursor one forward, assuming it is not empty 
    void next(); 
    
    // select the item to which the cursor points, assuming it is not empty
    Object current(); 
    
    // test whether there are more items in the range 
    boolean hasMore(); 
}

// ------------------------------------------------------------------
// classes to represent an arbitrary list of objects
abstract class ALoObj{ }

class MTLoObj extends ALoObj{
  MTLoObj() {}
}

class ConsLoObj extends ALoObj{
  Object first;
  ALoObj rest;

  ConsLoObj(Object first, ALoObj rest){
    this.first = first;
    this.rest = rest;
  }
}

// ------------------------------------------------------------------
// interface to represent an arbitrary stack of objects
interface IStack{ 

  // determine whether this is an empty stack
  boolean empty();

  // push a new object onto the stack
  void push(Object obj);

  // pop (remove) the top item off the stack
  void pop();

  // produce the top item on the stack
  Object top();
  
}

class ListStack implements IStack{
  ALoObj list;

  ListStack(ALoObj list){
    this.list = list;
  }

  // determine whether this is an empty stack
  boolean empty(){
    return (list instanceof MTLoObj);
  }

  // push a new object onto the stack
  void push(Object obj){
    list = new ConsLoObj(obj, list);
  }

  // pop (remove) the top item off the stack
  void pop(){
    list = ((ConsLoObj)list).rest;
  }

  // produce the top item on the stack
  Object top(){
    return ((ConsLoObj)list).first;
  } 
}

// ------------------------------------------------------------------
//   Functional (External) Iterator Pattern:
//        iterator for a list of objects 

class ListRange implements IRange {

    // -------------------------------------------------------- 
    // Member data
    ALoObj ptr; /* reference to this list */
    
    //-------------------------------------------------------- 
    // Constructor
    ListRange(ALoObj aList) { this.ptr = aList; }
        
    //--------------------------------------------------------
    // Methods to implement the IRange interface
    void next() {
        this.ptr = ((ConsLoObj)this.ptr).rest; 
    }

    Object current() {
        return ((ConsLoObj)this.ptr).first; 
    }
    
    boolean hasMore() {
        return (this.ptr instanceof ConsLoObj); 
    }
}


// ------------------------------------------------------------------
// objects to keep in the list
class Book {
  String title;
  String author;
  int price;

  Book(String title, String author, int price) {
    this.title = title;
    this.author = author;
    this.price = price;
  }

}


// ------------------------------------------------------------------
// the client class that uses the list of objects, the iterator, and the IObj2Bool interface
class TestClass{

  TestClass(){}

  // externally defined recursive filter      
  ALoObj filter(IRange it, IObj2Bool io2b){

    // not empty?
    if (it.hasMore()){
      // remember local value and advance the iterator
      Object obj = it.current();
      it.next();

      // select this item?, add it and go on
      if (io2b.select(obj))
        return new ConsLoObj(obj, filter(it, io2b));

      // not selected, go on
      else
        return filter(it, io2b);
    }
    // empty clause
    else
      return new MTLoObj();
  }

  // external iterative filter
  ALoObj iterFilter(IRange it, IObj2Bool io2b){
    
    // empty clause
    ALoObj result = new MTLoObj();

    // traverse the list
    for (IRange r = it; r.hasMore(); r.next()){
      if (io2b.select(r.current()))
        result = new ConsLoObj(r.current(), result);
    }

    // return the result - note that it is in reverse order
    return result;
  }

  // list reversal using the ListStack and the ListRange iterator
  ALoObj reverse(IRange it){
    // start with an empty stack
    ListStack s = new ListStack(new MTLoObj());

    // push each new item onto the stack
    for (IRange r = it; r.hasMore(); r.next()){
      s.push(r.current());
    }

    // return the contents of the stack
    return ((ListStack)s).list;
  }

}

// book selector by price: cheaper than given price
class BookCheaperThan implements IObj2Bool{
  int price;

  BookCheaperThan(int price){
    this.price = price;
  }  

  // select Book object with cheaper price that this.price
  boolean select(Object obj){
    return (((Book)obj).price) < this.price;
  }
}

//  Book b1 = new Book("1", "2", 34);
// 
// Book b2 = new Book("3", "4", 32);
// 
// Book b3 = new Book("HtDP", "mf", 60);
// 
// ALoObj mt = new MTLoObj();
// 
// ALoObj list1 = new ConsLoObj(b1, mt);
// 
// ALoObj list2 = new ConsLoObj(b2, new ConsLoObj(b3, list1));
// 
// TestClass test = new TestClass();
// 
// test.filter(new ListRange(list2), new BookCheaperThan(35));
// 
// test.filter(new ListRange(list1), new BookCheaperThan(30));
// 
// test.iterFilter(new ListRange(list2), new BookCheaperThan(35));
// 
// test.iterFilter(new ListRange(list1), new BookCheaperThan(30));
// 
// 
//  


.filter test..test.filter(new ListRange(list2), new BookCheaperThan(35));.new ConsLoObj(b2, new ConsLoObj(b1, mt)).
.filter test..test.filter(new ListRange(list1), new BookCheaperThan(30));.mt.


.filter test..test.iterFilter(new ListRange(list2), new BookCheaperThan(35));.new ConsLoObj(b1, new ConsLoObj(b2, mt)).
.filter test..test.iterFilter(new ListRange(list1), new BookCheaperThan(30));.mt.


.filter test..test.iterFilter(new ListRange(list2), new BookCheaperThan(35));.test.reverse(new ListRange(new ConsLoObj(b2, new ConsLoObj(b1, mt)))).


.filter test..test.reverse(new ListRange(list2)).new ConsLoObj(b1, new ConsLoObj(b3, new ConsLoObj(b2, mt))).

