package haskell.prelude;

import haskell.lang.Data;
import haskell.prelude.text.Show;

/**
 * data Maybe a = Nothing | Just a
 *     deriving (Eq, Ord, Read, Show)
 */
public class Maybe<A>
        extends AbstractOrd<Maybe<A>>
        implements Data<Maybe<A>>,
                Eq<Maybe<A>>,
                Ord<Maybe<A>>,
                //Read<Maybe<A>>,
                Show<Maybe<A>>,
                Functor<A, Maybe<A>>,
                Monad<A, Maybe<A>> {

    final A x;

    Maybe(final java.lang.String name, final A x) {
        super(name);
        this.x = x;
    }

    @Override
    protected java.lang.String $description() {
        final A x = eval().x;
        if (isNothing()) {
            return $name();
        } else if (x instanceof Show) {
            return $name() + ' ' + ((Show<?>) x)._show_();
        } else {
            return $name() + ' ' + x;
        }
    }

    // ---- class Maybe a

    /**
     * Nothing
     */
    public static <A> Maybe<A> Nothing() {
        return new Maybe<A>("Nothing", null);
    }

    public boolean isNothing() {
        return (eval().x == null);
    }

    /**
     * Just a
     */
    public static <A> Function<A, Maybe<A>> Just() {
        return new Function<A, Maybe<A>>("Just") {
            @Override
            public Maybe<A> apply(final A x) {
                return Just(x);
            }
        };
    }

    public static <A> Maybe<A> Just(final A x) {
        return new Maybe<A>("Just", x);
    }

    public boolean isJust() {
        return (eval().x != null);
    }

    public A _just_() {
        return eval().x;
    }

    /**
     * maybe :: b -> (a -> b) -> Maybe a -> b
     */
    public static <A, B>
            Function3<B, Function<A, B>, Maybe<A>, B> maybe() {
        return new Function3<B, Function<A, B>, Maybe<A>, B>("maybe") {
            @Override
            public B apply(
                    final B n, final Function<A, B> f, final Maybe<A> m) {
                return maybe(n, f, m);
            }
        };
    }

    /**
     * maybe n f Nothing  = n
     * maybe n f (Just x) = f x
     */
    public static <A, B> B maybe(
            final B n, final Function<A, B> f, final Maybe<A> m) {
        if (m.isNothing()) {
            return n;
        } else {
            return f.apply(m.eval().x);
        }
    }

    // ---- instance Eq Maybe

    protected Eq.Support<Maybe<A>> _Eq_() {
        // TODO Auto-generated method stub
        return null;
    }

    // ---- instance Ord Maybe

    protected Ord.Support<Maybe<A>> _Ord_() {
        // TODO Auto-generated method stub
        return null;
    }

    // ---- instance Read Maybe

    // ---- instance Show Maybe

    // ---- instance Functor Maybe

    static class FunctorSupport<A, B>
            extends Functor.Support<A, B, Maybe<A>, Maybe<B>> {

        /**
         * fmap f Nothing  = Nothing
         * fmap f (Just x) = Just (f x)
         */
        public Maybe<B> fmap(final Function<A, B> f, final Maybe<A> m) {
            if (m.isNothing()) {
                return Nothing();
            } else {
                return Just(f.apply(m.eval().x));
            }
        }

    }

    // ---- instance Monad Maybe

    public static <A, B> Maybe<B> eval(
            final Function<A, Maybe<B>> k, final A x) {
        return return_(x).eval()._bind_(k);
    }

    public static <A, B> Maybe<B> eval(
            final Function<A, Maybe<B>> k, final Maybe<A> m) {
        return m.eval()._bind_(k);
    }

    public <B> Maybe<B> _bind_(final Function<A, Maybe<B>> k) {
        return Maybe.<A, B>Monad2().bind(this, k);
    }

    public <B> Maybe<B> _bind__(final Maybe<B> k) {
        return Maybe.<A, B>Monad2().bind_(this, k);
    }

    public static <A> Maybe<A> return_(final A x) {
        return Maybe.<A>Monad().return_(x);
    }

    public static <A> Monad.Support<A, A, Maybe<A>, Maybe<A>> Monad() {
        return new MonadSupport<A, A>();
    }

    public static <A, B> Monad.Support<A, B, Maybe<A>, Maybe<B>> Monad2() {
        return new MonadSupport<A, B>();
    }

    static class MonadSupport<A, B>
            extends Monad.Support<A, B, Maybe<A>, Maybe<B>> {

        /**
         * Nothing  >>= k = Nothing
         * (Just x) >>= k = k x
         */
        public Maybe<B> bind(final Maybe<A> m, final Function<A, Maybe<B>> k) {
            if (m.isNothing()) {
                return Nothing();
            } else {
                return k.apply(m.eval().x);
            }
        }

        /**
         * return = Just
         */
        public Maybe<A> return_(final A x) {
            return Just(x);
        }

        /**
         * fail s = Nothing
         */
        @Override
        public Maybe<A> fail(final List<Char> s) {
            return Nothing();
        }

    }

    public int compareTo(Maybe<A> o) {
        // TODO Auto-generated method stub
        return 0;
    }

}
