package com.loopperfect.buckaroo.io; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.loopperfect.buckaroo.Either; import com.loopperfect.buckaroo.Unit; import org.jparsec.functors.Map3; import org.jparsec.functors.Map4; import org.jparsec.functors.Map5; import java.io.IOException; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @FunctionalInterface public interface IO<T> { T run(final IOContext context); default <U> IO<U> flatMap(final Function<T, IO<U>> f) { Preconditions.checkNotNull(f); return context -> { Preconditions.checkNotNull(context); final T t = run(context); return f.apply(t).run(context); }; } default <U> IO<U> map(final Function<T, U> f) { Preconditions.checkNotNull(f); return flatMap(x -> IO.value(f.apply(x))); } default <U> IO<U> then(final IO<U> io) { Preconditions.checkNotNull(io); return flatMap(ignored -> io); } default IO<Unit> ignore() { return then(noop()); } default <U> IO<U> ifThenElse(final Predicate<T> condition, final Function<T, IO<U>> then, final Function<T, IO<U>> otherwise) { Preconditions.checkNotNull(condition); Preconditions.checkNotNull(then); Preconditions.checkNotNull(otherwise); return context -> { Preconditions.checkNotNull(context); final T t = run(context); if (condition.test(t)) { return then.apply(t).run(context); } else { return otherwise.apply(t).run(context); } }; } default IO<T> until(final Predicate<T> condition) { Preconditions.checkNotNull(condition); return context -> { Preconditions.checkNotNull(context); T t = run(context); while (!condition.test(t)) { t = run(context); } return t; }; } default IO<T> fallback(final Predicate<T> condition, final Function<T, IO<T>> retry) { Preconditions.checkNotNull(condition); Preconditions.checkNotNull(retry); return context -> { final T t = run(context); if (condition.test(t)) { return retry.apply(t).run(context); } return t; }; } static <T> IO<T> of(IO<T> io) { return io; } static <T> IO<T> value(final T value) { Preconditions.checkNotNull(value); return context -> value; } static IO<Unit> println(final Object x) { Preconditions.checkNotNull(x); return context -> { Preconditions.checkNotNull(context); context.console().println(x.toString()); return Unit.of(); }; } static IO<Optional<String>> read() { return context -> { Preconditions.checkNotNull(context); return context.console().readln(); }; } static IO<Optional<IOException>> createDirectory(final String path) { Preconditions.checkNotNull(path); return context -> { Preconditions.checkNotNull(context); return context.fs().createDirectory(path); }; } static IO<Optional<IOException>> writeFile(final String path, final String content, final boolean overwrite) { Preconditions.checkNotNull(path); Preconditions.checkNotNull(content); return context -> { Preconditions.checkNotNull(context); return context.fs().writeFile(path, content, overwrite); }; } static IO<Optional<IOException>> writeFile(final String path, final String content) { return writeFile(path, content, false); } static IO<Optional<IOException>> deleteFile(final String path){ return context -> { Preconditions.checkNotNull(context); return context.fs().deleteFile(path); }; } static IO<Either<IOException, String>> readFile(final String path) { Preconditions.checkNotNull(path); return context -> { Preconditions.checkNotNull(context); return context.fs().readFile(path); }; } static IO<Either<IOException, ImmutableList<String>>> listFiles(final String path) { Preconditions.checkNotNull(path); return context -> { Preconditions.checkNotNull(context); return context.fs().listFiles(path); }; } static IO<Unit> noop() { return context -> Unit.of(); } static <T> IO<ImmutableList<T>> sequence(final ImmutableList<IO<T>> ts) { Preconditions.checkNotNull(ts); return context -> { Preconditions.checkNotNull(context); final ImmutableList.Builder builder = ImmutableList.builder(); for (final IO<T> t : ts) { final T r = t.run(context); builder.add(r); } return builder.build(); }; } static <A, B, T> IO<T> sequence(final IO<A> a, final IO<B> b, final BiFunction<A, B, T> selector) { Preconditions.checkNotNull(a); Preconditions.checkNotNull(b); Preconditions.checkNotNull(selector); return context -> { Preconditions.checkNotNull(context); return selector.apply(a.run(context), b.run(context)); }; } static <A, B, C, T> IO<T> sequence(final IO<A> a, final IO<B> b, final IO<C> c, final Map3<A, B, C, T> selector) { Preconditions.checkNotNull(a); Preconditions.checkNotNull(b); Preconditions.checkNotNull(c); Preconditions.checkNotNull(selector); return context -> { Preconditions.checkNotNull(context); return selector.map(a.run(context), b.run(context), c.run(context)); }; } static <A, B, C, D, T> IO<T> sequence(final IO<A> a, final IO<B> b, final IO<C> c, final IO<D> d, final Map4<A, B, C, D, T> selector) { Preconditions.checkNotNull(a); Preconditions.checkNotNull(b); Preconditions.checkNotNull(c); Preconditions.checkNotNull(d); Preconditions.checkNotNull(selector); return context -> { Preconditions.checkNotNull(context); return selector.map(a.run(context), b.run(context), c.run(context), d.run(context)); }; } static <A, B, C, D, E, T> IO<T> sequence(final IO<A> a, final IO<B> b, final IO<C> c, final IO<D> d, final IO<E> e, final Map5<A, B, C, D, E, T> selector) { Preconditions.checkNotNull(a); Preconditions.checkNotNull(b); Preconditions.checkNotNull(c); Preconditions.checkNotNull(d); Preconditions.checkNotNull(e); Preconditions.checkNotNull(selector); return context -> { Preconditions.checkNotNull(context); return selector.map(a.run(context), b.run(context), c.run(context), d.run(context), e.run(context)); }; } static <T, U> Function<IO<T>, IO<U>> lift(final Function<T, U> f) { Preconditions.checkNotNull(f); return x -> { Preconditions.checkNotNull(x); return x.map(f); }; } }