package abstraction;

import tester.Tester;

class Examples {

    ILoStrings someWords = new ConsString(
        "feldspar",
        new ConsString(
            "assignment",
            new ConsString("curb", new LastString("carpet"))
        )
    );

    boolean tests(Tester t) {
        return (
            t.checkExpect(someWords.earliest(new OrderByShortness()), "curb") &&
            t.checkExpect(
                someWords.earliest(new OrderByAlpha()),
                "assignment"
            ) &&
            t.checkExpect(someWords.earliest(new OrderByOrder()), "feldspar") &&
            t.checkExpect(
                someWords.earliest(new OrderByLongness()),
                "assignment"
            ) &&
            t.checkExpect(
                someWords.earliest(new OrderByUnalpha()),
                "feldspar"
            ) &&
            t.checkExpect(someWords.earliest(new OrderByDisorder()), "carpet")
        );
    }
}

interface IStringsBefore {
    boolean before(String s1, String s2);
}

// Negates an ordering.
class Disorder implements IStringsBefore {

    IStringsBefore ordering;

    Disorder(IStringsBefore ordering) {
        this.ordering = ordering;
    }

    public boolean before(String s1, String s2) {
        return !this.ordering.before(s1, s2);
    }
}

class OrderByShortness implements IStringsBefore {

    public boolean before(String s1, String s2) {
        return s1.length() <= s2.length();
    }
}

class OrderByAlpha implements IStringsBefore {

    public boolean before(String s1, String s2) {
        return s1.compareTo(s2) <= 0;
    }
}

class OrderByOrder implements IStringsBefore {

    public boolean before(String s1, String s2) {
        return true;
    }
}

class OrderByLongness implements IStringsBefore {

    public boolean before(String s1, String s2) {
        return new Disorder(new OrderByShortness()).before(s1, s2);
    }
}

class OrderByUnalpha implements IStringsBefore {

    public boolean before(String s1, String s2) {
        return new Disorder(new OrderByAlpha()).before(s1, s2);
    }
}

class OrderByDisorder implements IStringsBefore {

    public boolean before(String s1, String s2) {
        return new Disorder(new OrderByOrder()).before(s1, s2);
    }
}

// A non-empty list of strings.
interface ILoStrings {
    String val(); // Gets the value.
    String earliest(IStringsBefore ordering); // Gets the earliest in a list of strings.
    String earliestHelper(IStringsBefore ordering, String prev);
}

abstract class ALoStrings implements ILoStrings {

    String val;

    ALoStrings(String val) {
        this.val = val;
    }

    public String val() {
        return this.val;
    }
}

class ConsString extends ALoStrings {

    ILoStrings nxt;

    ConsString(String val, ILoStrings nxt) {
        super(val);
        this.nxt = nxt;
    }

    public String earliest(IStringsBefore ordering) {
        return ordering.before(this.val, this.nxt.val())
            ? this.nxt.earliestHelper(ordering, this.val)
            : this.nxt.earliest(ordering);
    }

    public String earliestHelper(IStringsBefore ordering, String prev) {
        return new ConsString(prev, this.nxt).earliest(ordering);
    }
}

class LastString extends ALoStrings {

    LastString(String val) {
        super(val);
    }

    public String earliest(IStringsBefore ordering) {
        return this.val;
    }

    public String earliestHelper(IStringsBefore ordering, String prev) {
        return prev;
    }
}
