/*
    +--------------------------+
    | interface ISame          |
    +--------------------------+
    | boolean same(Object obj) |
    +--------------------------+
              / \
               |
       - - - - - - - - - - - 
       |                   |
+--------------+    +-------------+
| Book         |    | Author      |
+--------------+    +-------------+
| String title |    | String name |
| int year     |    | int year    |
+--------------+    +-------------+

           +-----------+                
           | ILoObject |<------------+
           +-----------+             |
           +-----------+             |
                / \                  |
                ---                  |
                 |                   |
       -------------------           |
       |                 |           |
+------------+    +----------------+ |
| MTLoObject |    | ConsLoObject   | |
+------------+    +----------------+ |
+------------+  +-| Object first   | |
                | | ILoObject rest |-+
                | +----------------+ 
                |
                |   +--------------------------+
                |   | interface ISame          |
                |   +--------------------------+
                |   | boolean same(Object obj) |
                |   +--------------------------+
                |             / \
                |              |
                |    - - - - - - 
                v    |
          +------------+
          | Object     |
          +------------+
          | Type ???   |
          | ...        |
          +------------+
*/

interface ISame{
  // is this the same as the given object?
  boolean same(Object obj);
}

class Book implements ISame{
  String title;
  int year;
  
  Book(String title, int year){
    this.title = title; 
    this.year = year;
  }

  boolean same(Object obj){
    if (obj instanceof Book)
      return this.same((Book)obj);
         else
        return false;
/* alternately:
        throw new ClasCastException(
          "Cannot compare a Book with other object");
*/
  }

  boolean same(Book that){
    return 
      this.title.equals(that.title)
    && this.year == that.year;
  }
}

class Author implements ISame{
  String name;
  int year;
  
  Author(String name, int year){
    this.name = name; 
    this.year = year;
  }

  boolean same(Object obj){
    if (obj instanceof Author)
      return this.same((Author)obj);
    else
      return false;
/* alternately:
        throw new ClasCastException(
          "Cannot compare an Author with other object");
*/
  }

  boolean same(Author that){
    return 
      this.name.equals(that.name)
    && this.year == that.year;
  }
}

// functional iterator for a linear traversal of a data structure
interface Traversal{
  
  // is there a current element available in the structure
  boolean hasMore();

  // produce the current element of the structure
  Object current();

  // produce an iterator for the rest of this structure
  Traversal advance();  
}

interface ILoObject extends Traversal{

  // is there a current element available in the structure
  boolean hasMore();

  // produce the current element of the structure
  Object current();

  // produce an iterator for the rest of this structure
  Traversal advance();  

  // to compute the size of this list
  int size();

  // is the given book in this list?
  boolean contains (Object that);
}

class MTLoObject implements ILoObject{
  MTLoObject() {}

  // is there a current element available in the structure
  boolean hasMore(){ return false; }

  // produce the current element of the structure
  Object current(){ return "Error"; }
  // throw new NoSuchElementException("Cannot produce current element in an empty list"); }

  // produce an iterator for the rest of this structure
  Traversal advance(){ return this; }
    // throw new NoSuchElementException("Error - cannot advance in an empty list"); }  

  int size(){ return 0; }

  boolean contains (Object that){
    return false;
  }
}

class ConsLoObject implements ILoObject{
  Object first;
  ILoObject rest;

  ConsLoObject(Object first, ILoObject rest){
    this.first = first;
    this.rest = rest;
  }

  // is there a current element available in the structure
  boolean hasMore(){ return true; }

  // produce the current element of the structure
  Object current(){ return this.first; }

  // produce an iterator for the rest of this structure
  Traversal advance(){ return this.rest; }

/* TEMPLATE:
... this.first ...                 -- Object
... this.rest ...                  -- ILoObject
... this.rest.size() ...           -- int
... this.rest.contains( Object ) ... -- boolean
*/
  int size(){ 
    return 1 + this.rest.size(); 
  }

  boolean contains (Object that){
    return ((ISame)this.first).same(that)
        || this.rest.contains(that);
  }
}

class Examples{
  Examples () {}

  Book b1 = new Book("DVC", 2003);
  Book b2 = new Book("LPP", 1942);
  Book b3 = new Book("HtDP", 2001);
  
  ILoObject mtbooks = 
            new MTLoObject();
  ILoObject booklist = 
            new ConsLoObject(b1,
            new ConsLoObject(b2,
                           mtbooks));

  boolean testSizeBook1 = 
    mtbooks.size() == 0;
  boolean testSizeBook2 = 
    booklist.size() == 2;

  boolean testContainsBook1 = 
    this.mtbooks.contains(this.b1) == 
      false;
  boolean testContainsBook2 = 
    this.booklist.contains(this.b2) == 
      true;
  boolean testContainsBook3 = 
    this.booklist.contains(b3) == 
      false;

  Author a1 = new Author("DB", 1956);
  Author a2 = new Author("StEx", 1900);
  Author a3 = new Author("MF", 1972);
  
  ILoObject mtauthors = 
            new MTLoObject();
  ILoObject authorlist = 
            new ConsLoObject(a1,
            new ConsLoObject(a2,
                           mtauthors));

  boolean testSizeAuthor1 = 
    mtauthors.size() == 0;
  boolean testSizeAuthor2 = 
    authorlist.size() == 2;

  boolean testContainsAuthor1 = 
    this.mtauthors.contains(this.a1) == 
      false;
  boolean testContainsAuthor2 = 
    this.authorlist.contains(this.a2) == 
      true;
  boolean testContainsAuthor3 =
    this.authorlist.contains(a3) == 
      false;

  // compute the size of the data set given by the traversal
  int size(Traversal t){
    if (t.hasMore())
       return 1 +
              size(t.advance());
    else
      return 0;  }

  // does the data set given by the traversal contain the given book?
  boolean containsBook(Book b, Traversal t){
    if (t.hasMore())
       return ((Book)t.current()).same(b) ||
              containsBook(b, t.advance());
    else
      return false;
  }

  // does the data set given by the traversal contain the given object?
  boolean contains(Object obj, Traversal t){
    if (t.hasMore())
       return ((ISame)t.current()).same(obj) ||
              contains(obj, t.advance());
    else
      return false;
  }

  boolean testSizeBookT1 = 
    this.size(this.mtbooks) == 0;
  boolean testSizeBookT2 = 
    this.size(this.booklist) == 2;

  boolean testContainsBookT1 = 
    this.containsBook(this.b1, this.mtbooks) == 
      false;
  boolean testContainsBookT2 = 
    this.containsBook(this.b2, this.booklist) == 
      true;
  boolean testContainsBookT3 = 
    this.containsBook(b3, this.booklist) == 
      false;

  boolean testContainsT1 = 
    this.contains(this.b1, this.mtbooks) == 
      false;
  boolean testContainsT2 = 
    this.contains(this.b2, this.booklist) == 
      true;
  boolean testContainsT3 = 
    this.contains(b3, this.booklist) == 
      false;

  boolean testSizeAuthorT1 = 
    this.size(this.mtauthors) == 0;
  boolean testSizeAuthorT2 = 
    this.size(this.authorlist) == 2;


}
