/*
    +--------------------------+
    | interface ISame          |
    +--------------------------+
    | boolean same(Object obj) |
    +--------------------------+
              / \
               |
       - - - - - 
       |           
+-------------+ 
| Person      |   
+-------------+  
| String name |  
| int phone   |  
+-------------+  

           +-----------+                
           | 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 Person implements ISame{
  String name;
  int phone;
  
  Person(String name, int phone){
    this.name = name; 
    this.phone = phone;
  }

  // effect: change the phone number for this person to the given number
  void newPhone(int newNumber){
    this.phone = newNumber;
  }

  // is this the same person as the given object?
  boolean same(Object obj){
    if (obj instanceof Person)
      return this.samePerson((Person)obj);
         else
        return false;
/* alternately:
        throw new ClasCastException(
          "Cannot compare a Book with other object");
*/
  }

  // is this the same person as the given person?
  boolean samePerson(Person that){
    return 
      this.name.equals(that.name)
    && this.phone == that.phone;
  }
}


interface ILoObject extends ISame{
  // to compute the size of this list
  int size();

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

  // is this the same as the given object?
  boolean same(Object obj);

  // change the phone number for the person with the given name 
  ILoObject changePhone(String name, int phone);

  // give a new phone number to the person with the given name 
  // effect: the person's phone number changes wherever person is referenced
  void newPhone(String name, int phone);

  // remove the person with the given name from this list
  // effect: the person will no longer be in this list
  void removePerson(String name);
}

class MTLoObject implements ILoObject{
  MTLoObject() {}

  int size(){ return 0; }

  boolean contains (Object that){
    return false;
  }

  // is this the same as the given object?
  boolean same(Object obj){ return obj instanceof MTLoObject; }

  // change the phone number for the person with the given name 
  ILoObject changePhone(String name, int phone){
    return this;
  }

  // give a new phone number to the person with the given name 
  // effect: the person's phone number changes wherever person is referenced
  void newPhone(String name, int phone){}

  // remove the person with the given name from this list
  // effect: the person will no longer be in this list
  void removePerson(String name){}
}

class ConsLoObject implements ILoObject{
  Object first;
  ILoObject rest;

  ConsLoObject(Object first, ILoObject rest){
    this.first = first;
    this.rest = 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);
  }

  // is this the same as the given object?
  boolean same(Object obj){ 
    if (obj instanceof ConsLoObject)
      return this.sameConsLoObject(((ConsLoObject)obj));
  }

  // is this the same as the given ConsLoObject?
  boolean sameConsLoObject(ConsLoObject that){ 
    return ((ISame)this.first).same(that.first) &&
           this.rest.same(that.rest);
  }
 

  // change the phone number for the person with the given name 
  ILoObject changePhone(String name, int phone){
    if (((Person)this.first).name.equals(name))
      return new ConsLoObject(new Person(name, phone),
                           this.rest);
    else
      return new ConsLoObject(this.first, 
                           this.rest.changePhone(name, phone));
  }

  // give a new phone number to the person with the given name 
  // effect: the person's phone number changes wherever person is referenced
  void newPhone(String name, int phone){
    if (((Person)this.first).name.equals(name))
      ((Person)this.first).newPhone(phone);
    else
      this.rest.changePhone(name, phone);
  }

  // remove the person with the given name from this list
  // effect: the person will no longer be in this list
  void removePerson(String name){
    if (((Person)this.first).name.equals(name))
      if (this.rest instanceof ConsLoObject){
        this.first = ((ConsLoObject)this.rest).first;
        this.rest = ((ConsLoObject)this.rest).rest;
      }
      else 
        signalError();
      
    else
      if (this.rest instanceof MTLoObject)
        signalError();
      else
      	((ConsLoObject)this.rest).removeAfter(name, this);
  }

  // remove the given from this rest of the list, knowing original list
  // effect: the given person will no longer be in the original list
  void removeAfter(String name, ConsLoObject acc){
    if (((Person)this.first).name.equals(name))
      if (this.rest instanceof MTLoObject)
        acc.rest = new MTLoObject();
      else{
        acc.first = ((ConsLoObject)this.rest).first;
        acc.rest = ((ConsLoObject)this.rest).rest;
      }
   else
     if (this.rest instanceof MTLoObject)
       signalError();
     else
      ((ConsLoObject)this.rest).removeAfter(name, this);
  }

  int error = 0;
  void signalError(){ error = error + 1; }
}


 

class Examples{
  Examples () {}

  Person bill = new Person("Bill", 2345);
  Person matt = new Person("Matt", 1234);
  Person anne = new Person("Anne", 7896);
  Person john = new Person("John", 8866);
  Person jane = new Person("Jane", 3456);
  
  ILoObject mtphlist = 
            new MTLoObject();
  ILoObject family = 
            new ConsLoObject(bill,
            new ConsLoObject(matt,
            new ConsLoObject(anne,
                           mtphlist)));

  ILoObject friends = 
            new ConsLoObject(anne,
            new ConsLoObject(john,
                           mtphlist));

  ILoObject work = 
            new ConsLoObject(john,
            new ConsLoObject(jane,
                           mtphlist));

  boolean testSize1 = 
    this.mtphlist.size() == 0;
  boolean testSize2 = 
    this.family.size() == 3;

  boolean testContains1 = 
    this.mtphlist.contains(this.bill) == 
      false;
  boolean testContains2 = 
    this.family.contains(this.matt) == 
      true;
  boolean testContains3 = 
    this.friends.contains(bill) == 
      false;

  boolean testChangePhone1 = 
    this.family.changePhone("Anne", 4444).same(
    new  ConsLoObject(bill,
            new ConsLoObject(matt,
            new ConsLoObject(new Person("Anne", 4444),
                             mtphlist))));

  boolean testChangePhone2 = 
    this.friends.same(
            new ConsLoObject(new Person("Anne", 4444),
            new ConsLoObject(john,
                           mtphlist)));

  // tests for the method newPhone in the class Person
  boolean testNewPhone(){
    Person rick = new Person("Rick", 3344);
    Person alie = new Person("Alie", 7777);
    rick.newPhone(5566);
    return rick.samePerson(new Person("Rick", 5566)) &&
           alie.samePerson(new Person("Alie", 7777));
  }

  boolean testNewPhoneResult = this.testNewPhone();

  // tests for removal of a person from a list
  boolean testRemovePerson1(){
    ILoObject list1 = 
            new ConsLoObject(bill,
            new ConsLoObject(anne,
            new ConsLoObject(matt,
                           mtphlist)));

    ILoObject list2 = 
            new ConsLoObject(anne,
            new ConsLoObject(john,
                           mtphlist));
    list1.removePerson("Anne");
    return list1.contains(anne) == false &&
           list2.contains(anne) == true;
  }

  boolean testRemovePerson2(){
    ILoObject list1 = 
            new ConsLoObject(bill,
            new ConsLoObject(anne,
            new ConsLoObject(matt,
                           mtphlist)));

    ILoObject list2 = 
            new ConsLoObject(bill,
            new ConsLoObject(john,
                           mtphlist));
    list1.removePerson("Bill");
    return list1.contains(bill) == false &&
           list2.contains(bill) == true;
  }

  boolean testRemovePerson3(){
    ILoObject list1 = 
            new ConsLoObject(bill,
            new ConsLoObject(anne,
            new ConsLoObject(matt,
                           mtphlist)));

    ILoObject list2 = 
            new ConsLoObject(anne,
            new ConsLoObject(matt,
                           mtphlist));
    list1.removePerson("Matt");
    return list1.contains(matt) == false &&
           list2.contains(matt) == true;
  }

  boolean testRemovePersonResult1 = testRemovePerson1();
  boolean testRemovePersonResult2 = testRemovePerson2();
  boolean testRemovePersonResult3 = testRemovePerson3();

  boolean testRemovePerson4(){
    ILoObject mtphlist = 
            new MTLoObject();
    ILoObject work = 
            new ConsLoObject(bill,
            new ConsLoObject(matt,
            new ConsLoObject(anne,
                           mtphlist)));

    ILoObject friends = 
            new ConsLoObject(john,
                           work);

    work.removePerson("Matt");
    return work.same(new ConsLoObject(bill
                     new ConsLoObject(anne,
                     mtphlist))) &&
           friends.same(new ConsLoObject(john,
                        new ConsLoObject(bill,
                        new ConsLoObject(matt,
                        new ConsLoObject(anne,
                        mtphlist)))));
  }

  boolean testRemovePersonResult4 = testRemovePerson4();

/*
Before:

   friends               work
     |                     |
     |                     |
     |          +-----+    |       +-----+           +-----+           +-----+
     v          |     v    v       |     v           |     v           |     v
+-------------+ |  +-------------+ | +-------------+ | +-------------+ | +----------+   
| first: john | |  | first: bill | | | first: matt | | | first: anne | | | mtphlist |  
| rest: --------+  | rest: --------+ | rest: --------+ | rest: --------+ +----------+  
+-------------+    +-------------+   +-------------+   +-------------+      


After:

   friends               work
     |                     |
     |                     |
     |          +-----+    |       +-----+           +-----+
     v          |     v    v       |     v           |     v
+-------------+ |  +-------------+ | +-------------+ | +----------+   
| first: john | |  | first: bill | | | first: anne | | | mtphlist |  
| rest: --------+  | rest: --------+ | rest: --------+ +----------+  
+-------------+    +-------------+   +-------------+      

*/

  
}
 
