package generics;

import tester.Tester;

class Examples {

    ILo<Integer> mint = new Mt<Integer>();
    ILo<Integer> lint = new Cons<Integer>(
        1,
        new Cons<Integer>(92, new Cons<Integer>(-12, mint))
    );
    ILo<Integer> lint2 = new Cons<Integer>(1, new Cons<Integer>(1, mint));

    ILo<String> mstr = new Mt<String>();
    ILo<String> hello = new Cons<String>("Hello, ", mstr);
    ILo<String> world = new Cons<String>("world.", mstr);
    ILo<String> ajcmsdj = new Cons<String>("ajcmsdj", mstr);
    ILo<String> helloWorld = new Cons<String>(
        "Hello, ",
        new Cons<String>("world.", mstr)
    );
    ILo<String> helloAjcmsdj = new Cons<String>("Hello, ", ajcmsdj);
    ILo<String> h = new Cons<String>(
        "h",
        new Cons<String>("hh", new Cons<String>("hhhh", mstr))
    );
    ILo<String> hh = new Cons<String>(
        "hh",
        new Cons<String>("hhhh", new Cons<String>("h", mstr))
    );
    ILo<String> hhh = new Cons<String>(
        "a",
        new Cons<String>("b", new Cons<String>("c", mstr))
    );
    ILo<String> hhhh = new Cons<String>(
        "b",
        new Cons<String>("a", new Cons<String>("c", mstr))
    );

    boolean testILoAppend(Tester t) {
        return (
            t.checkExpect(hello.append(world), helloWorld) &&
            t.checkExpect(lint.append(mint), lint) &&
            t.checkExpect(mint.append(lint), lint)
        );
    }

    boolean testILoArmax(Tester t) {
        return (
            t.checkExpect(helloWorld.argmax(new StrLen()), "Hello, ") &&
            t.checkExpect(helloAjcmsdj.argmax(new StrLen()), "Hello, ") &&
            t.checkExpect(lint2.argmax(new IntSize()), 1) &&
            t.checkExpect(lint.argmax(new IntSize()), 92)
        );
    }

    boolean testILoInsertSorted(Tester t) {
        return (
            t.checkExpect(
                mstr.insertSorted(new CompStrLen(), "h"),
                new Cons<String>("h", mstr)
            ) &&
            t.checkExpect(
                hello.insertSorted(new CompStrLen(), "s"),
                new Cons<String>("s", hello)
            ) &&
            t.checkExpect(
                h.insertSorted(new CompStrLen(), "hhh"),
                new Cons<String>(
                    "h",
                    new Cons<String>(
                        "hh",
                        new Cons<String>("hhh", new Cons<String>("hhhh", mstr))
                    )
                )
            ) &&
            t.checkExpect(
                hello.insertSorted(new CompStrLen(), "hello"),
                new Cons<String>("hello", hello)
            )
        );
    }

    boolean testILoSort(Tester t) {
        return (
            t.checkExpect(h.sort(new CompStrLen()), h) &&
            t.checkExpect(mstr.sort(new CompStrLen()), mstr) &&
            t.checkExpect(
                helloWorld.sort(new CompStrLen()),
                new Cons<String>("world.", new Cons<String>("Hello, ", mstr))
            ) &&
            t.checkExpect(hh.sort(new CompStrLen()), h) &&
            t.checkExpect(mstr.sort(new CompStrAlp()), mstr) &&
            t.checkExpect(hhh.sort(new CompStrAlp()), hhh) &&
            t.checkExpect(hhhh.sort(new CompStrAlp()), hhh) &&
            t.checkExpect(hh.sort(new Comp<String>(new StrLen())), h) &&
            t.checkExpect(
                lint.sort(new Comp<Integer>(new IntSize())),
                new Cons<Integer>(
                    -12,
                    new Cons<Integer>(1, new Cons<Integer>(92, mint))
                )
            )
        );
    }
}

// Generic function.
interface IFunc<X, Y> {
    Y apply(X input);
}

// Extracts the value of an input.
interface IEvaluator<X> extends IFunc<X, Integer> {}

// Calculate the length of a string.
class StrLen implements IEvaluator<String> {

    public Integer apply(String input) {
        return input.length();
    }
}

// Calculate the size of an integer.
class IntSize implements IEvaluator<Integer> {

    public Integer apply(Integer input) {
        return input;
    }
}

// Compares two values.
interface IComparator<A> {
    // Returns a negative number if a1 is "smaller" than a2,
    // 0 if they are considered equal,
    // and a positive number of a1 is "bigger" than a2.
    int compare(A a1, A a2);
}

// Compare string lengths.
class CompStrLen implements IComparator<String> {

    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

// Compare strings alphabetically.
class CompStrAlp implements IComparator<String> {

    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
}

// Compare the sizes of two things with the given Evaluator.
class Comp<A> implements IComparator<A> {

    IEvaluator<A> eval;

    Comp(IEvaluator<A> eval) {
        this.eval = eval;
    }

    public int compare(A a1, A a2) {
        return eval.apply(a1) - eval.apply(a2);
    }
}

// A list of A.
interface ILo<A> {
    // Append a list.
    ILo<A> append(ILo<A> a);
    // Find the largest from the evaluator.
    A argmax(IEvaluator<A> eval);
    A argmaxHelper(IEvaluator<A> eval, A prev, Integer prevVal);
    // Sort the list with the comparator.
    ILo<A> sort(IComparator<A> comp);
    ILo<A> sortHelper(IComparator<A> comp, ILo<A> sorted);
    // Add an element to the correct place in a sorted list.
    ILo<A> insertSorted(IComparator<A> comp, A a);
}

class Cons<A> implements ILo<A> {

    A first;
    ILo<A> rest;

    Cons(A first, ILo<A> rest) {
        this.first = first;
        this.rest = rest;
    }

    public ILo<A> append(ILo<A> a) {
        return new Cons<A>(this.first, this.rest.append(a));
    }

    public A argmax(IEvaluator<A> eval) {
        return argmaxHelper(eval, this.first, eval.apply(this.first));
    }

    public A argmaxHelper(IEvaluator<A> eval, A prev, Integer prevVal) {
        A now = this.first;
        Integer nowVal = eval.apply(now);

        return prevVal >= nowVal
            ? this.rest.argmaxHelper(eval, prev, prevVal)
            : this.rest.argmaxHelper(eval, now, nowVal);
    }

    public ILo<A> sort(IComparator<A> comp) {
        return this.rest.sortHelper(comp, new Cons<A>(this.first, new Mt<A>()));
    }

    public ILo<A> sortHelper(IComparator<A> comp, ILo<A> sorted) {
        return this.rest.sortHelper(
                comp,
                sorted.insertSorted(comp, this.first)
            );
    }

    public ILo<A> insertSorted(IComparator<A> comp, A a) {
        return comp.compare(a, this.first) <= 0
            ? new Cons<A>(a, this)
            : new Cons<A>(this.first, this.rest.insertSorted(comp, a));
    }
}

class Mt<A> implements ILo<A> {

    public ILo<A> append(ILo<A> a) {
        return a;
    }

    public A argmax(IEvaluator<A> eval) {
        throw new UnsupportedOperationException("No max of empty list.");
    }

    public A argmaxHelper(IEvaluator<A> eval, A prev, Integer prevVal) {
        return prev;
    }

    public ILo<A> sort(IComparator<A> comp) {
        return this;
    }

    public ILo<A> sortHelper(IComparator<A> comp, ILo<A> sorted) {
        return sorted;
    }

    public ILo<A> insertSorted(IComparator<A> comp, A a) {
        return new Cons<A>(a, this);
    }
}
