package me.elrod.pureio;

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

/**
 * Here we demonstrate how you might use the concept of a coproduct to
 * combine grammars of various types of I/O.
 *
 * In this particular case, we build up a coproduct over {@link PureConsoleIO}
 * and {@link PureFileIO}.
 *
 * Using coproducts causes the trampolining technique to no longer work (and
 * it would be nice to find a solution to this in the future), so we don't make
 * use of {@link PureConsoleIOT} here.
 *
 * This structure makes use of {@link Either} and is very similar to
 * <code>Either&lt;PureConsoleIO<A>, PureFileIO<A>&gt;</code> except that we
 * write some useful instances over it. That is,
 * <code>A =&gt; F[A] \/ G[A]</code> is a functor, given functors F and G.
 * We can then construct a free monad over this to combine both grammars.
 *
 * In reality, coproducts, of course, generalize to any two functors (or
 * foldables or traversables, or comonads). But in Java, we are only able to
 * write one very specific instance of this construct.
 */
public class ConsoleFileCoproduct<A> {
    private Either<PureConsoleIO<A>, PureFileIO<A>> x;

    public ConsoleFileCoproduct(Either<PureConsoleIO<A>, PureFileIO<A>> either) {
        this.x = either;
    }

    /**
     * Lift a PureConsoleIO.
     *
     * <br>
     * <code>
     * left :: f a -&gt; Coproduct f g a    <br>
     * left = Coproduct . Left
     * </code>
     */
    public static <A> ConsoleFileCoproduct<A> left(PureConsoleIO<A> p) {
        return new ConsoleFileCoproduct<A>(Either.left(p));
    }

    /**
     * Lift a PureFileIO.
     *
     * <br>
     * <code>
     * right :: g a -&gt; Coproduct f g a    <br>
     * right = Coproduct . Right
     * </code>
     */
    public static <A> ConsoleFileCoproduct<A> right(PureFileIO<A> f) {
        return new ConsoleFileCoproduct<A>(Either.right(f));
    }

    /**
     * Catamorphism over the coproduct.
     *
     * <br>
     * <code>
     * coproduct :: (f a -&gt; b) -&gt; (g a -&gt; b) -&gt;
     * Coproduct f g a -&gt; b
     * <br>
     * coproduct f g = either f g . getCoproduct
     * </code>
     */
    public <X> X cata(Function<PureConsoleIO<A>, X> p, Function<PureFileIO<A>, X> f) {
        return this.x.cata(p, f);
    }

    /**
     * Functor map over the coproduct.
     *
     * <br>
     * <code>
     * instance (Functor f, Functor g) =&gt; Functor (Coproduct f g) where
     * fmap f = Coproduct . coproduct (Left . fmap f) (Right . fmap f)
     * </code>
     **/
    public <B> ConsoleFileCoproduct<B> map(Function<A, B> f) {
        return new ConsoleFileCoproduct<B>(
            this.cata(
                new Function<PureConsoleIO<A>, Either<PureConsoleIO<B>, PureFileIO<B>>>() {
                    public Either<PureConsoleIO<B>, PureFileIO<B>> apply(PureConsoleIO<A> a) {
                        return Either.left(a.map(f));
                    }
                },
                new Function<PureFileIO<A>, Either<PureConsoleIO<B>, PureFileIO<B>>>() {
                    public Either<PureConsoleIO<B>, PureFileIO<B>> apply(PureFileIO<A> a) {
                        return Either.right(a.map(f));
                    }
                }));
    }
}

@@ -281,29 +281,33 @@
  %7B@link 
-PureConsoleIO
+TerminalOperation
 %7D%0A * and
@@ -314,26 +314,29 @@
  %7B@link 
-PureFileIO
+FileOperation
 %7D.%0A *%0A *
@@ -507,29 +507,33 @@
  %7B@link 
-PureConsoleIO
+TerminalOperation
 T%7D here.
@@ -628,36 +628,43 @@
 &lt;
-PureConsoleIO%3CA%3E, PureFileIO
+TerminalOperation%3CA%3E, FileOperation
 %3CA%3E&
@@ -1161,36 +1161,43 @@
 her%3C
-PureConsoleIO%3CA%3E, PureFileIO
+TerminalOperation%3CA%3E, FileOperation
 %3CA%3E%3E
@@ -1244,36 +1244,43 @@
 her%3C
-PureConsoleIO%3CA%3E, PureFileIO
+TerminalOperation%3CA%3E, FileOperation
 %3CA%3E%3E
@@ -1344,29 +1344,33 @@
  Lift a 
-PureConsoleIO
+TerminalOperation
 .%0A     *
@@ -1550,29 +1550,33 @@
 A%3E left(
-PureConsoleIO
+TerminalOperation
 %3CA%3E p) %7B
@@ -1665,26 +1665,29 @@
  Lift a 
-PureFileIO
+FileOperation
 .%0A     *
@@ -1871,26 +1871,29 @@
 %3E right(
-PureFileIO
+FileOperation
 %3CA%3E f) %7B
@@ -2244,37 +2244,41 @@
 ta(Function%3C
-PureConsoleIO
+TerminalOperation
 %3CA%3E, X%3E p, F
@@ -2281,34 +2281,37 @@
 p, Function%3C
-PureFileIO
+FileOperation
 %3CA%3E, X%3E f) %7B
@@ -2765,433 +2765,475 @@
 ion%3C
-PureConsoleIO%3CA%3E, Either%3CPureConsoleIO%3CB%3E, PureFileIO%3CB%3E%3E%3E() %7B%0A                    public Either%3CPureConsoleIO%3CB%3E, PureFileIO%3CB%3E%3E apply(PureConsoleIO%3CA%3E a) %7B%0A                        return Either.left(a.map(f));%0A                    %7D%0A                %7D,%0A                new Function%3CPureFileIO%3CA%3E, Either%3CPureConsoleIO%3CB%3E, PureFileIO%3CB%3E%3E%3E() %7B%0A                    public Either%3CPureConsoleIO%3CB%3E, PureFileIO%3CB%3E%3E apply(PureFileIO
+TerminalOperation%3CA%3E, Either%3CTerminalOperation%3CB%3E, FileOperation%3CB%3E%3E%3E() %7B%0A                    public Either%3CTerminalOperation%3CB%3E, FileOperation%3CB%3E%3E apply(TerminalOperation%3CA%3E a) %7B%0A                        return Either.left(a.map(f));%0A                    %7D%0A                %7D,%0A                new Function%3CFileOperation%3CA%3E, Either%3CTerminalOperation%3CB%3E, FileOperation%3CB%3E%3E%3E() %7B%0A                    public Either%3CTerminalOperation%3CB%3E, FileOperation%3CB%3E%3E apply(FileOperation
 %3CA%3E 

Define the coproduct over the functorial ADT, not its free monad