package haskell.prelude;

import haskell.prelude.text.Show;

import java.io.InputStreamReader;
import java.io.Reader;

/**
 * newtype IO a = IO ...
 */
public class IO<A>
        extends AbstractShow<IO<A>>
        implements Functor<A, IO<A>>,
                Monad<A, IO<A>> {

    final A x;

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

    static final IO<Unit> IOUnit = new IO<Unit>("IO ()", Unit.Unit_());

    // ---- instance Functor IO

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

        /**
         * fmap f x = x >>= (return . f)
         */
        public IO<B> fmap(final Function<A, B> f, final IO<A> x) {
            return x._bind_(IO.<B>Monad().return_()._dot_(f));
        }

    }

    // ---- instance Monad IO

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

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

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

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

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

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

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

        public IO<B> bind(final IO<A> m, final Function<A, IO<B>> k) {
            return k.apply(m.x);
        }

        public IO<A> return_(final A x) {
            return new IO<A>("IO", x);
        }

    }

    /**
     * putChar :: Char -> IO ()
     * putChar = primPutChar
     */
    public static IO<Unit> putChar(final Char c) {
        return putChar(c.value());
    }

    public static IO<Unit> putChar(final char c) {
        System.out.print(c);
        return IOUnit;
    }

    /**
     * putStr :: String -> IO ()
     * putStr s = mapM_ putChar s
     */
    public static Function<List<Char>, IO<Unit>> putStr() {
        return new Function<List<Char>, IO<Unit>>("putStr") {
            @Override
            public IO<Unit> apply(final List<Char> x) {
                return putStr(x);
            }
        };
    }

    public static IO<Unit> putStr(final List<Char> s) {
        StringBuilder sb = new StringBuilder();
        List<Char> node = s;
        while (node.isCons()) {
            char c = node._head_().value();
            if (c == '\n') {
                putStrLn(sb.toString());
                sb.setLength(0);
            } else {
                sb.append(c);
            }
            node = node._tail_();
        }
        return putStr(sb.toString());
    }

    public static IO<Unit> putStr(final java.lang.String s) {
        System.out.print(s);
        return IOUnit;
    }

    /**
     * putStrLn :: String -> IO ()
     * putStrLn s =
     *   do
     *     putStr s
     *     putStr "\n"
     */
    public static IO<Unit> putStrLn(final List<Char> s) {
        putStr(s);
        return putStrLn("");
    }

    public static IO<Unit> putStrLn(final java.lang.String s) {
        System.out.println(s);
        return IOUnit;
    }

    /**
     * print :: Show a => a -> IO ()
     */
    public static <A extends Show<?>> Function<A, IO<Unit>> print() {
        return new Function<A, IO<Unit>>("print") {
            @Override
            public IO<Unit> apply(A x) {
                return print(x);
            }
        };
    }

    /**
     * print x = putStrLn (show x)
     */
    public static <A extends Show<?>> IO<Unit> print(final A x) {
        return putStrLn(x._show_());
    }

    /**
     * getContents :: IO String
     */
    public static IO<List<Char>> getContents() {
        final Reader reader = new InputStreamReader(System.in);
        return new IO<List<Char>>("IO String", String.valueOf("IO String", reader));
    }

}
