package me.elrod.pureio;

import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * A trampoline is a sum type with two basic constructors: Pure and
 * Suspend. A Pure is a leaf value produced at the end of a tree of
 * computations; A Suspend holds a computation that can be resumed.
 *
 * We use {@link Either<A, B>} to construct the tree.
 */
public abstract class Trampoline<A> {
    private Trampoline() {}

    public abstract <R> R cata(
        final Function<A, R> pure,
        final Function<Identity<Trampoline<A>>, R> suspend,
        final BiFunction<Trampoline<A>, Function<A, Trampoline<R>>, R> flatmap);

    public abstract Either<Identity<Trampoline<A>>, A> resume();

    private static final class Suspend<A> extends Trampoline<A> {
        private final Identity<Trampoline<A>> suspension;
        private Suspend(final Identity<Trampoline<A>> s) {
            this.suspension = s;
        }
        public <R> R cata(
            final Function<A, R> pure,
            final Function<Identity<Trampoline<A>>, R> suspend,
            final BiFunction<Trampoline<A>, Function<A, Trampoline<R>>, R> flatmap) {
            return suspend.apply(this.suspension);
        }
        public Either<Identity<Trampoline<A>>, A> resume() {
            return Either.left(this.suspension);
        }
    }

    private static final class Pure<A> extends Trampoline<A> {
        private final A value;
        private Pure(final A x) {
            this.value = x;
        }
        public <R> R cata(
            final Function<A, R> pure,
            final Function<Identity<Trampoline<A>>, R> suspend,
            final BiFunction<Trampoline<A>, Function<A, Trampoline<R>>, R> flatmap) {
            return pure.apply(this.value);
        }
        public Either<Identity<Trampoline<A>>, A> resume() {
            return Either.right(this.value);
        }
    }

    private static final class FlatMap<A, B> extends Trampoline<A> {
        private final Trampoline<A> sub;
        private final Function<A, Trampoline<B>> k;

        private FlatMap(final Trampoline<A> sub, final Function<A, Trampoline<B>> k) {
            this.sub = sub;
            this.k = k;
        }

        public <R> R cata(
            final Function<A, R> pure,
            final Function<Identity<Trampoline<A>>, R> suspend,
            final BiFunction<Trampoline<A>, Function<A, Trampoline<R>>, R> flatmap) {
            return flatmap.apply(this.sub, this.k);
        }

        public Either<Identity<Trampoline<A>>, A> resume() {
            return null;
            /*sub.cata(
                // Pure
                v -> k.apply(v).resume(),
                // Suspend
                k2 -> Either.left(new Identity<Trampoline<A>>() {
                        public Trampoline<A> run() {
                            return flatmap(k2.run(), k);
                        }
                    }),
                // FlatMap
                (b, g) -> (new FlatMap<Trampoline<A>, Object>(
                               b,
                               x -> new FlatMap<Trampoline<A>, Object>(g.apply(x), k)
                               )).resume());*/
        }
    }

    public static <A> Trampoline<A> pure(final A x) {
        return new Pure<A>(x);
    }

    public static <A> Trampoline<A> suspend(final Identity<Trampoline<A>> x) {
        return new Suspend<A>(x);
    }

    public static <A, B> Trampoline<A> flatmap(final Trampoline<A> sub, final Function<A, Trampoline<B>> k) {
        return new FlatMap<A, B>(sub, k);
    }

    // This is taken almost directly from FJ for now.
    // Credit:
    // https://github.com/functionaljava/functionaljava/blob/master/core/src/main/java/fj/control/Trampoline.java
    public A run() {
        Trampoline<A> current = this;
        while (true) {
            final Either<Identity<Trampoline<A>>, A> x = current.resume();
            // TODO: Can we use Either#cata here?
            if (x.isLeft()) {
                Either.LeftP<Identity<Trampoline<A>>, A> y = x.projectLeft();
                current = y.unsafeValue().run();
            } else {
                // We hit the end of the tree
                Either.RightP<Identity<Trampoline<A>>, A> y = x.projectRight();
                return y.unsafeValue();
            }
        }
    }
}

@@ -454,77 +454,14 @@
 act 
-%3CR%3E R cata(%0A        final Function%3CA, R%3E pure,%0A        final Function
+Either
 %3CIde
@@ -486,128 +486,197 @@
 %3E%3E, 
-R%3E suspend,%0A        final BiFunction%3CTrampoline%3CA%3E, Function%3CA, Trampoline%3CR%3E%3E, R%3E flatmap);%0A%0A    public abstract Either
+A%3E resume();%0A%0A    private static abstract class Normal%3CA%3E extends Trampoline%3CA%3E %7B%0A        public abstract %3CR%3E R normalCata(%0A            final Function%3CA, R%3E pure,%0A            final Function
 %3CIde
@@ -693,36 +693,42 @@
 poline%3CA%3E%3E, 
-A%3E resume();
+R%3E suspend);%0A    %7D
 %0A%0A    privat
@@ -763,34 +763,30 @@
 %3CA%3E extends 
-Trampoline
+Normal
 %3CA%3E %7B%0A      
@@ -952,33 +952,39 @@
    public %3CR%3E R 
-c
+normalC
 ata(%0A           
@@ -1077,92 +1077,8 @@
 pend
-,%0A            final BiFunction%3CTrampoline%3CA%3E, Function%3CA, Trampoline%3CR%3E%3E, R%3E flatmap
 ) %7B%0A
@@ -1308,34 +1308,30 @@
 %3CA%3E extends 
-Trampoline
+Normal
 %3CA%3E %7B%0A      
@@ -1440,33 +1440,39 @@
    public %3CR%3E R 
-c
+normalC
 ata(%0A           
@@ -1565,92 +1565,8 @@
 pend
-,%0A            final BiFunction%3CTrampoline%3CA%3E, Function%3CA, Trampoline%3CR%3E%3E, R%3E flatmap
 ) %7B%0A
@@ -1733,32 +1733,39 @@
        %7D%0A    %7D%0A%0A
+    /*%0A
     private stat
@@ -2438,18 +2438,16 @@
         
-/*
 sub.cata
@@ -3021,18 +3021,16 @@
 sume());
-*/
 %0A       
@@ -3033,24 +3033,31 @@
      %7D%0A    %7D
+%0A    */
 %0A%0A    public
@@ -3250,32 +3250,39 @@
 d%3CA%3E(x);%0A    %7D%0A%0A
+    /*%0A
     public stati
@@ -3418,24 +3418,31 @@
 b, k);%0A    %7D
+%0A    */
 %0A%0A    // Thi

Fix build, prepare to split things into Normal and Codense.