package example.a_a_monads.part01;

import static example.a_a_monads.part01.Example1.Sheep.*;
import static haskell.prelude.Maybe.*;
import haskell.lang.Data;
import haskell.lang.Exp;
import haskell.prelude.AbstractShow;
import haskell.prelude.Function;
import haskell.prelude.Function2;
import haskell.prelude.IO;
import haskell.prelude.Maybe;
import haskell.prelude.String;
import haskell.prelude.text.Show;

public class Example1 {

    /**
     * data Sheep =
     *   Sheep {
     *     name   :: String,
     *     mother :: Maybe Sheep,
     *     father :: Maybe Sheep
     *   }
     */
    static class Sheep
            extends AbstractShow<Sheep>
            implements Data<Sheep>, Show<Sheep> {

        final String name;
        final Maybe<Sheep> mother;
        final Maybe<Sheep> father;

        Sheep(final String name,
                final Maybe<Sheep> mother, final Maybe<Sheep> father) {
            this.name = name;
            this.mother = mother;
            this.father = father;
        }

        Sheep(final Exp<Sheep> expression) {
            super(expression);
            this.name = null;
            this.mother = null;
            this.father = null;
        }

        public static Sheep Sheep_(
                final java.lang.String name,
                final Maybe<Sheep> mother,
                final Maybe<Sheep> father) {
            return Sheep_(String.valueOf(name), mother, father);
        }

        public static Sheep Sheep_(
                final String name,
                final Maybe<Sheep> mother,
                final Maybe<Sheep> father) {
            return new Sheep(name, mother, father);
        }

        public static Function<Sheep, String> name() {
            return new Function<Sheep, String>() {
                public String apply(Sheep sheep) {
                    return sheep.name;
                }
            };
        }

        public static String name(final Sheep sheep) {
            return sheep.eval().name;
        }

        public static Function<Sheep, Maybe<Sheep>> mother() {
            return new Function<Sheep, Maybe<Sheep>>("mother") {
                public Maybe<Sheep> apply(Sheep sheep) {
                    return sheep.mother;
                }
            };
        }

        public static Maybe<Sheep> mother(final Sheep sheep) {
            return sheep.eval().mother;
        }

        public static Function<Sheep, Maybe<Sheep>> father() {
            return new Function<Sheep, Maybe<Sheep>>("father") {
                public Maybe<Sheep> apply(Sheep sheep) {
                    return sheep.father;
                }
            };
        }

        public static Maybe<Sheep> father(final Sheep sheep) {
            return sheep.eval().father;
        }

    }

    /**
     * comb :: Maybe a -> (a -> Maybe b) -> Maybe b
     * comb Nothing _  = Nothing
     * comb (Just x) f = f x
     */
    static <A, B> Function2<Maybe<A>, Function<A, Maybe<B>>, Maybe<B>> comb() {
        return new Function2<Maybe<A>, Function<A, Maybe<B>>, Maybe<B>>("comb") {
            public Maybe<B> apply(Maybe<A> m, Function<A, Maybe<B>> f) {
                if (m.isNothing()) {
                    return Nothing();
                } else {
                    return f.apply(m._just_());
                }
            }
        };
    }

    static <A, B> Maybe<B> comb(
            final Maybe<A> m, final Function<A, Maybe<B>> k) {
        if (m.isNothing()) {
            return Nothing();
        } else {
            return k.apply(m._just_());
        }
    }

    /**
     * maternalGrandfather :: Sheep -> Maybe Sheep
     * maternalGrandfather s = (Just s) `comb` mother `comb` father
     */
    static Function<Sheep, Maybe<Sheep>> maternalGrandfather() {
        return new Function<Sheep, Maybe<Sheep>>("maternalGrandfather") {
            public Maybe<Sheep> apply(Sheep s) {
                return Just(s)
                        .apply(Example1.<Sheep, Sheep>comb(), mother())
                        .apply(Example1.<Sheep, Sheep>comb(), father());
            }
        };
    }

    static Maybe<Sheep> maternalGrandfather(final Sheep s) {
        return Just(s)
                .apply(Example1.<Sheep, Sheep>comb(), mother())
                .apply(Example1.<Sheep, Sheep>comb(), father());
    }

    /**
     * fathersMaternalGrandmother :: Sheep -> Maybe<Sheep>
     * fathersMaternalGrandmother s = (Just s) `comb` father `comb` mother `comb` mother
     */
    static Function<Sheep, Maybe<Sheep>> fathersMaternalGrandmother() {
        return new Function<Sheep, Maybe<Sheep>>("fathersMaternalGrandmother") {
            @Override
            public Maybe<Sheep> apply(Sheep s) {
                return Just(s)
                        .apply(Example1.<Sheep, Sheep>comb(), father())
                        .apply(Example1.<Sheep, Sheep>comb(), mother())
                        .apply(Example1.<Sheep, Sheep>comb(), mother());
            }
        };
    }

    /**
     * mothersPaternalGrandfather :: Sheep -> Maybe<Sheep>
     * mothersPaternalGrandfather s = (Just s) `comb` mother `comb` father `comb` father
     */
    static Function<Sheep, Maybe<Sheep>> mothersPaternalGrandfather() {
        return new Function<Sheep, Maybe<Sheep>>("mothersPaternalGrandfather") {
            @Override
            public Maybe<Sheep> apply(Sheep s) {
                return Just(s)
                        .apply(Example1.<Sheep, Sheep>comb(), mother())
                        .apply(Example1.<Sheep, Sheep>comb(), father())
                        .apply(Example1.<Sheep, Sheep>comb(), father());
            }
        };
    }

    /**
     * breedSheep :: Sheep
     * breadSheep = ...
     */
    static Sheep breedSheep() {
        final Maybe<Sheep> Nothing = Maybe.<Sheep>Nothing();
        final Sheep adam   = Sheep_("Adam", Nothing, Nothing);
        final Sheep eve    = Sheep_("Eve", Nothing, Nothing);
        final Sheep uranus = Sheep_("Uranus", Nothing, Nothing);
        final Sheep gaea   = Sheep_("Gaea", Nothing, Nothing);
        final Sheep kronos = Sheep_("Kronos", Just(gaea), Just(uranus));
        final Sheep holly  = Sheep_("Holly", Just(eve), Just(adam));
        final Sheep roger  = Sheep_("Roger", Just(eve), Just(kronos));
        final Sheep molly  = Sheep_("Molly", Just(holly),Just(roger));
        return Sheep_("Dolly", Just(molly), Nothing);
    }

    public static void main(java.lang.String[] args) {
        final Sheep dolly = breedSheep();
        IO.print(maternalGrandfather().apply(dolly));
    }

}
