package example.a_a_monads.part01;

import static example.a_a_monads.part01.Example2.Sheep.*;
import static haskell.prelude.Maybe.*;
import haskell.lang.AbstractData;
import haskell.prelude.Function;
import haskell.prelude.IO;
import haskell.prelude.Maybe;
import haskell.prelude.String;
import haskell.prelude.text.Show;

public class Example2 {

    /**
     * data Sheep = Sheep { name :: String, mother :: Maybe Sheep, father :: Maybe Sheep }
     */
    static class Sheep extends AbstractData<Sheep> implements 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;
        }

        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 Function<Sheep, Maybe<Sheep>> mother() {
            return new Function<Sheep, Maybe<Sheep>>("mother") {
                public Maybe<Sheep> apply(Sheep sheep) {
                    return sheep.mother;
                }
            };
        }

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

        // ---- instance Show Sheep

        /**
         * show :: Sheep -> String
         * show s = show (name s)
         */
        public String _show_() {
            return name().apply(this)._show_();
        }

    }

    /**
     * maternalGrandfather :: Sheep -> Maybe Sheep
     * maternalGrandfather s = do
     *   m <- mother s
     *   father m
     */
    static Function<Sheep, Maybe<Sheep>> maternalGrandfather() {
        return new Function<Sheep, Maybe<Sheep>>("maternalGrandfather") {
            public Maybe<Sheep> apply(Sheep s) {
                /*return Maybe.return_(s)
                        ._bind_(mother())
                        ._bind_(father());*/
                final Maybe<Sheep> m = Maybe.eval(mother(), s);
                return Maybe.eval(father(), m);
            }
        };
    }

    /**
     * fathersMaternalGrandmother :: Sheep -> Maybe<Sheep>
     * fathersMaternalGrandmother s = do
     *   f  <- father s
     *   gm <- mother f
     *   mother gm
     */
    static Function<Sheep, Maybe<Sheep>> fathersMaternalGrandmother() {
        return new Function<Sheep, Maybe<Sheep>>("fathersMaternalGrandmother") {
            public Maybe<Sheep> apply(Sheep s) {
                /*return Maybe.return_(s)
                        ._bind_(father())
                        ._bind_(mother())
                        ._bind_(mother());*/
                final Maybe<Sheep> f  = Maybe.eval(father(), s);
                final Maybe<Sheep> gm = Maybe.eval(mother(), f);
                return Maybe.eval(mother(), gm);
            }
        };
    }

    /**
     * mothersPaternalGrandfather :: Sheep -> Maybe<Sheep>
     * mothersPaternalGrandfather s = do
     *   m  <- mother s
     *   gf <- father m
     *   father gf
     */
    static Function<Sheep, Maybe<Sheep>> mothersPaternalGrandfather() {
        return new Function<Sheep, Maybe<Sheep>>("mothersPaternalGrandfather") {
            @Override
            public Maybe<Sheep> apply(Sheep s) {
                /*return Maybe.return_(s)
                        ._bind_(mother())
                        ._bind_(father())
                        ._bind_(father());*/
                final Maybe<Sheep> m  = Maybe.eval(mother(), s);
                final Maybe<Sheep> gf = Maybe.eval(father(), m);
                return Maybe.eval(father(), gf);
            }
        };
    }

    /**
     * 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));
    }

}
