package Result;
import java.util.Optional;
import java.util.function.*;
import java.util.stream.Stream;
import java.util.Iterator;

public sealed interface Result<T,E> 
    extends Iterator<T>, Iterable<T> permits OkResult, ErrResult
{
    static <A,B> Result<A,B> Ok(A v) {
        return new OkResult<A,B>(v);
    }
    static <A,B> Result<A,B> OkNullable(A v) {
        if (v!=null) return new OkResult<A,B>(v);
        else return new ErrResult<A,B>(Optional.empty());
    }
    static <A,B> Result<A,B> Err(B e) {
        return new ErrResult<A,B>(Optional.ofNullable(e));
    }
    static <A,B> Result<A,B> Err() {
        return new ErrResult<A,B>(Optional.empty());
    }    

    default boolean is_ok() {
        return switch (this) {
        case OkResult(var _) -> true;
        default -> false;
        };
    }
    default boolean is_err() { return !is_ok(); }

    default <U> Result<U,E> map(Function<? super T,? extends U> f) {
        return switch (this) {
        case OkResult(var v) -> OkNullable(f.apply(v));
        case ErrResult(var e) -> new ErrResult<U,E>(e);
        };
    }
    default <U> Result<U,E> bind(Function<? super T, ? extends Result<U,E>> f) {
        switch (this) {
        case OkResult(var v):
            var x = f.apply(v);
            if (x==null) return Err(); else return x;
        case ErrResult(var e): return new ErrResult<U,E>(e);        
        }
    }
    default <U> Result<U,E> flatMap(Function<? super T, ? extends Result<U,E>> f) {    return bind(f); } // alias
    default <U> U if_else(Function<? super T, ? extends U> fok,
                          Function<Optional<E>, ? extends U> fer) {
        return switch (this) {
        case OkResult(var v) -> fok.apply(v);
        case ErrResult(var e) -> fer.apply(e);
        };
    }
    default void if_do(Consumer<? super T> f) {
        switch (this) {
        case OkResult(var v) : f.accept(v);
        default: return;
        }
    }
    default void if_else_do(Consumer<? super T> f, Consumer<Optional<E>> fe) {
        switch (this) {
        case OkResult(var v) -> f.accept(v);
        case ErrResult(var e) -> fe.accept(e);
        }
    }
    default T orElse(T opt) {
        switch (this) {
        case OkResult(var v) : return v;
        default :
            if (opt!=null) return opt;
            else throw new NullPointerException("from Result.orElse");
        }
    }
    default T get() {
        switch (this) {
        case OkResult(var v) : return v;
        default: throw new java.util.NoSuchElementException("from Result.get");
        }
    }
    default T getOr(Function<Optional<E>,? extends T> f) {
        switch (this) {
        case OkResult(var v): return v;
        case ErrResult(var e):
            var x = f.apply(e);
            if (x!=null) return x;
            else throw new NullPointerException("from Result.getOr");
        }
    }

    default Result<T,E> or(Result<T,E> other) {
        return switch (this) {
        case OkResult(var v) -> this;
        default -> other;
        };
    }

    default <W> Result<Pair<T,W>,E> zip(Result<W,E> other) {
        var pair = new Pair<Result<T,E>,Result<W,E>>(this,other);
        return switch (pair) {
        case Pair(OkResult(var v), OkResult(var w)) ->
            new OkResult<Pair<T,W>,E>(new Pair<T,W>(v,w));
        case Pair(ErrResult(var e),_) ->
            new ErrResult<Pair<T,W>,E>(e);
        case Pair(_,ErrResult(var e)) ->
            new ErrResult<Pair<T,W>,E>(e);        
        };
    }

    default <U,W> Result<U,E> chain(Result<W,E> other, BiFunction<? super T,? super W,? extends U> bf) {
        var pair = new Pair<Result<T,E>,Result<W,E>>(this,other);
        return
        switch (pair) {
        case Pair(OkResult(var v), OkResult(var w)) ->
            new OkResult<U,E>(bf.apply(v,w));
        case Pair(ErrResult(var e), _) ->
            new ErrResult<U,E>(e);
        case Pair(_, ErrResult(var e)) ->
            new ErrResult<U,E>(e);        
        };
    }

    static <U,A,B,F> Result<U,F> map2(Result<Pair<A,B>,F> r, BiFunction<? super A, ? super B, ? extends U> f) {
        return switch (r) {
        case OkResult(Pair(var a, var b)) ->
            new OkResult<U,F>(f.apply(a,b));
        case ErrResult(var e) ->
            new ErrResult<U,F>(e);
        };
    }

    static <A,F> Result<A,F> flatten(Result<Result<A,F>,F> r) {
        return switch (r) {
        case OkResult(var v) -> v;
        case ErrResult(var e) -> new ErrResult<A,F>(e);
        };
    }

    default Optional<T> to_optional() {
        return switch (this) {
        case OkResult(var v) -> Optional.ofNullable(v);
        default -> Optional.empty();
        };
    }

    default Stream<T> stream() {
        return switch (this) {
        case OkResult(var v) -> Stream.of(v);
        default -> Stream.empty();
        };        
    }

    static <U> Result<U,RuntimeException> on_except(Supplier<? extends U> s) {
        Result<U,RuntimeException> answer;
        try {
            var x = s.get();
            if (x==null)
                answer = new ErrResult<U,RuntimeException>(Optional.of(new NullPointerException("from Result.on_except")));
            else answer = new OkResult<U,RuntimeException>(x);
        }
        catch (RuntimeException re) {
            answer = new ErrResult<U,RuntimeException>(Optional.of(re));
        }
        return answer;
    }

    static <U> Result<U,String> on_behalf(Supplier<? extends U> s) {
        Result<U,String> answer;
        try {
            var x = s.get();
            if (x==null)
                answer = new ErrResult<U,String>(Optional.of("null pointer caught by on_behalf"));
            else answer = new OkResult<U,String>(x);
        }
        catch (Exception re) {
            answer = new ErrResult<U,String>(Optional.of(re.toString()));
        }
        return answer;
    }    

    default boolean hasNext() {
        return is_ok();
    }
    default T next() {
        return get();
    }
    default Iterator<T> iterator() { return this; }
    
}//Result

