// this assignment is comprised of P16.9, P16.10 and P16.12

// Test.java

public class Test
{
   public static void main(String[] args)
   {
      LispList test = new EmptyList();
      System.out.print("[" + test + "] ");
      System.out.println("... expected: []");
      System.out.print(test.length() + " ");
      System.out.println("... expected: 0");
      System.out.print(test.contains("E") + " ");
      System.out.println("... expected: false");

      LispList test2 = new NonEmptyList("A", new EmptyList());
      System.out.print(test2);
      System.out.println("... expected: A");
      System.out.print(test2.length());
      System.out.println("... expected: 1");
      System.out.print(test2.contains("B"));
      System.out.println("... expected: false");
      System.out.print(test2.contains("A"));
      System.out.println("... expected: true");

      LispList test3 = new NonEmptyList("A", new NonEmptyList("B", new NonEmptyList("C", new EmptyList())));
      System.out.print(test3);
      System.out.println("... expected: A B C");
      System.out.print(test3.length());
      System.out.println("... expected: 3");
      System.out.print(test3.contains("E"));
      System.out.println("... expected: false");
      System.out.print(test3.contains("C"));
      System.out.println("... expected: true");
      System.out.print(test3.contains("B"));
      System.out.println("... expected: true");
      System.out.print(test3.contains("U"));
      System.out.println("... expected: false");

      LispList test4 = LispList.NIL.cons("Q").cons("J").cons("M").cons("B").cons("J");
      System.out.print(test4);
      System.out.println("... expected: J B M D Q");
      System.out.print(test4.length());
      System.out.println("... expected: 5");
      System.out.print(test4.contains("J"));
      System.out.println("... expected: true");
      System.out.print(test4.contains("H"));
      System.out.println("... expected: false");

      try { 
        System.out.println( LispList.NIL.head() ); 
      } catch (Exception e) {
        System.out.println( "We should never ask for that..." );  
      }
      
   }
}

// LispList.java

import java.util.*;

public interface LispList{
  public static LispList NIL = new EmptyList();
  boolean isEmpty();
  Object head() throws CrazyWorldException;
  LispList tail() throws CrazyWorldException;
  String toString();
  int length();
  boolean contains(Object obj);
  LispList cons(Object a);
}

// NonEmptyList.java

import java.util.*;

public class NonEmptyList implements LispList{
  Object head;
  LispList tail;
  public NonEmptyList(Object first, LispList rest){
    this.head = first;
    this.tail = rest;
  }
  
  public boolean isEmpty(){
    return false;
  }
  
  public Object head(){
    return this.head;
  }
  
  public LispList tail(){
    return this.tail;
  }
  
  public NonEmptyList cons(Object a){
    return new NonEmptyList(a, this);
  }
  
  public int length(){
    int count = 1;
    try { 
      LispList a = tail(); 
      while (!a.isEmpty()){
        count+=1;
        a = a.tail(); 
      }
      return count;
      // return 1 + tail().length(); 
    } catch (CrazyWorldException e) { 
      return  -1; // I don't like this   
    }
  }
  
  public boolean contains(Object obj){
    if (this.head.equals(obj))
      return true;
    else
      if (this.tail==null) return false;
      else return this.tail.contains(obj);
  }
    
  public String toString(){
    return head() + " " + tail().toString();
  } 
}

// EmptyList.java

import java.util.*;

public class EmptyList implements LispList{
  
  public EmptyList(){}
  
  public boolean isEmpty(){
    return true;
  }
  
  public LispList head() throws CrazyWorldException {
    throw new CrazyWorldException(); 
  }
  
  public LispList tail() throws CrazyWorldException {
    throw new CrazyWorldException(); 
  }
  
  public int length(){
    return 0;
  }
  Object head;
  LispList tail;
  public boolean contains(Object obj){
    return false;
  }
  
  public LispList cons(Object a){
    return new NonEmptyList(a, this);
  }
   
  public String toString(){
    return "";
  } 
   
}

// CrazyWorldException.java 

public class CrazyWorldException extends Exception {
  
}

/* Please understand the above is just a possible approach. It is in fact
   a solution provided by a student in a prior semester. The UIs can grade
   this in two ways and both ways are fine as long as they care to provide 
   the feedback fast enough so you can then come for a makeup if need be. 

   One way is to count each problem as 33%. 

   The other one is to enumerate the individual features (25% each): 

   (a) LispList
   (b) NonEmptyList
   (c) EmptyList 
   (d) Test.java 

   The key is to provide fast feedback based on what was discused in lab.    

 */