package delegation;

import tester.Tester;

class Examples {
    IPet foot = new Dog("Foot", 44);
        IPet footOlder = new Dog("Foot", 45);
    IPet hek = new Cat("Hek", 408);
        IPet hekOlder = new Cat("Hek", 409);
    IPet unpet = new NoPet();
    IPet stephenDog = new Dog("Stephen", 93);

    Person horb = new Person("Horb", hek, 9);
    Person horbOlder = new Person("Horb", hekOlder, 10);
    Person minge = new Person("Minge", unpet, 3);
    Person mingeOlder = new Person("Minge", unpet, 4);
    Person wrist = new Person("Wrist", foot, 60);
    Person wristOlder = new Person("Wrist", footOlder, 61);
    Person stephenPerson = new Person("Stephen", stephenDog, 88);
    
    ILoPerson unpopulated = new MtLoPerson();
    ILoPerson onlyHorb = new ConsLoPerson(horb, unpopulated);
    ILoPerson mingeAndHorb = new ConsLoPerson(minge, onlyHorb);
    ILoPerson mingeAndHorbOneYearLater =
                    new ConsLoPerson(mingeOlder,
                                    new ConsLoPerson(horbOlder, unpopulated));
    ILoPerson mingeAndStephen = new ConsLoPerson(minge, new ConsLoPerson(stephenPerson, unpopulated));
    
    boolean testPetOlder(Tester t) {
        return t.checkExpect(foot.older(), footOlder)
                        && t.checkExpect(hek.older(), hekOlder)
                        && t.checkExpect(unpet, unpet);
    }
    
    boolean testPersonOlder(Tester t) {
        return t.checkExpect(horb.older(), horbOlder)
                        && t.checkExpect(minge.older(), mingeOlder)
                        && t.checkExpect(wrist.older(), wristOlder);
    }
    
    boolean testPetGetName(Tester t) {
        return t.checkExpect(hek.getName(), new YesString("Hek"))
                        && t.checkExpect(unpet.getName(), new NoString())
                        && t.checkExpect(foot.getName(), new YesString("Foot"));
    }
    
    boolean testPersonSamePetName(Tester t) {
        return t.checkExpect(horb.samePetName("Hek"), true)
                        && t.checkExpect(horb.samePetName("Minkus"), false)
                        && t.checkExpect(minge.samePetName("8"), false);
    }
    
    boolean testILoPersonOlder(Tester t) {
        return t.checkExpect(unpopulated.older(), unpopulated)
                        && t.checkExpect(mingeAndHorb.older(), mingeAndHorbOneYearLater);
    }
    
    boolean testPetHumanAge(Tester t) {
        return t.checkExpect(hek.humanAge(), 2448)
                        && t.checkExpect(stephenDog.humanAge(), 651)
                        && t.checkExpect(unpet.humanAge(), 0);
    }
    
    boolean testPersonPetHasSameName(Tester t) {
        return t.checkExpect(wrist.petHasSameName(), false) &&
                        t.checkExpect(minge.petHasSameName(), false) &&
                        t.checkExpect(stephenPerson.petHasSameName(), true);
        
    }
    
    boolean testAnyNarcissists(Tester t) {
        return t.checkExpect(mingeAndHorbOneYearLater.anyNarcissists(), false) &&
                        t.checkExpect(mingeAndStephen.anyNarcissists(), true);
    }
}

// A list of people.
interface ILoPerson {
    // Increase each member person's and their pet's ages by one.
    ILoPerson older();

    // Does anyone share a name with their pet?
    boolean anyNarcissists();
}

class MtLoPerson implements ILoPerson {
    public ILoPerson older() { return this; };
    
    public boolean anyNarcissists() { return false; };
}

class ConsLoPerson implements ILoPerson {
    Person first;
    ILoPerson rest;
    
    ConsLoPerson(Person first, ILoPerson rest) {
        this.first = first;
        this.rest = rest;
    }
    
    public ILoPerson older() {
        return new ConsLoPerson(this.first.older(), this.rest.older());
    }
    
    public boolean anyNarcissists() {
        return this.first.petHasSameName() || this.rest.anyNarcissists();
    }
}

//a pet owner
class Person {
    String name;
    IPet pet;
    int age; // in years

    Person(String name, IPet pet, int age) {
        this.name = name;
        this.pet = pet;
        this.age = age;
    }
    
    // Increase the age of a person and their pet by one year.
    Person older() {
        return new Person(this.name, this.pet.older(), this.age + 1);
    }
    
    // Do they have a pet (and not an unpet)?
    boolean hasPet() {
        return this.pet.isPet();
    }
    
    // Whether a person's pet's name's the same as the given string.
    boolean samePetName(String s) {
        return this.hasPet() && s.equals(this.pet.getName().unwrap());
    }
    
    // Return whether they've the same name their pet.
    boolean petHasSameName() {
        return this.hasPet() && this.name.equals(this.pet.getName().unwrap());
    }
}

//a pet
interface IPet {
    // Increase the age of a pet by one year.
    IPet older();
    boolean isPet();
    // Return the name of the pet (maybe).
    IMaybeString getName();
    // Age of pet in human years.
    int humanAge();
    
    // Does the pet have the given name?
    boolean hasName(String name);
}

//a pet cat
class Cat implements IPet {
    String name;
    int age; // in years

    Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Cat older() {
        return new Cat(this.name, this.age + 1);
    }
    
    public boolean isPet() { return true; }
    
    public IMaybeString getName() {
        return new YesString(this.name);
    }
    
    public int humanAge() {
        return this.age * 6;
}
}

//a pet dog
class Dog implements IPet {
    String name;
    int age;  // in years

    Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    
    public Dog older() {
        return new Dog(this.name, this.age + 1);
    }


    public boolean isPet() { return true; }
    
    public IMaybeString getName() {
        return new YesString(this.name);
    }
    
    public int humanAge() {
        return this.age * 7;
    }
}

//no pet
class NoPet implements IPet {
    public NoPet older() { return this; }
    public boolean isPet() { return false; }
    public IMaybeString getName() { return new NoString(); }
    public int humanAge() { return 0; }
}

// Maybe a string.
interface IMaybeString {
    boolean isString();
    String unwrap();
}

// No string.
class NoString implements IMaybeString {
    public boolean isString() { return false; }
    
    // This is an exception and should cause the program to crash, but I don't know how to do that cleanly.
    public String unwrap() { int i = 0/0; return "what have you done 😔"; };
}

// Yes string.
class YesString implements IMaybeString {
    String s;
    
    YesString(String s) { this.s = s; }
    
    public boolean isString() { return true; }
    public String unwrap() { return this.s; }
} 
