package org.reactivecouchbase.json.mapping;

import org.reactivecouchbase.functional.Option;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public abstract class JsResult<T> implements Iterable<T> {
    public abstract T get();

    public abstract Option<T> getOpt();

    public Option<T> asOpt() {
        return getOpt();
    }

    public abstract JsResult<T> getOrElse(JsResult<T> result);

    public abstract T getValueOrNull();

    public abstract T getValueOrElse(T result);

    public abstract T getValueOrElse(Throwable result);

    public abstract <B> JsResult<B> map(Function<T, B> map);

    public abstract <B> JsResult<B> flatMap(Function<T, JsResult<B>> map);

    public abstract JsResult<T> filter(Function<T, Boolean> predicate);

    public abstract JsResult<T> filterNot(Function<T, Boolean> predicate);

    public abstract JsResult<T> filter(Function<T, Boolean> predicate, List<Throwable> errors);

    public abstract JsResult<T> filterNot(Function<T, Boolean> predicate, List<Throwable> errors);

    public abstract JsResult<T> filter(Function<T, Boolean> predicate, Throwable errors);

    public abstract JsResult<T> filterNot(Function<T, Boolean> predicate, Throwable errors);

    public abstract boolean hasErrors();

    public abstract boolean isErrors();

    public abstract boolean isSuccess();

    public abstract int countErrors();

    public abstract Option<JsError<T>> asError();

    public abstract Option<JsSuccess<T>> asSuccess();

    public JsError<T> toError() {
        return asError().get();
    }

    public JsSuccess<T> toSuccess() {
        return asSuccess().get();
    }

    public static <T> JsResult<T> error(Throwable t) {
        return new JsError<>(t);
    }

    public static <T> JsResult<T> success(T t) {
        return new JsSuccess<>(t);
    }

    public Option<Throwable> onError() {
        if (isErrors()) {
            return Option.some(asError().get().firstError());
        }
        return Option.none();
    }

    public List<Throwable> onErrors() {
        if (isErrors()) {
            return Collections.unmodifiableList(asError().get().errors);
        }
        return Collections.emptyList();
    }

    public Option<T> onSuccess() {
        if (isSuccess()) {
            return Option.some(get());
        }
        return Option.none();
    }

    public abstract T orError(Throwable t);

    public T recover(Function<JsError, T> block) {
        if (isSuccess()) {
            return get();
        } else {
            return block.apply(asError().get());
        }
    }

    public <R> R fold(Function<JsError, R> onError, Function<T, R> onSuccess) {
        if(isSuccess()) {
            return onSuccess.apply(get());
        } else {
            return onError.apply(asError().get());
        }
    }

    private static <T> JsResult<T> populateErrs(JsResult<T> finalResult, JsResult<?>... results) {
        List<Throwable> throwables = new ArrayList<>();
        for (JsResult<?> res : results) {
            if (res.isErrors()) {
                throwables.addAll(res.asError().get().errors.stream().collect(Collectors.toList()));
            }
        }
        if (throwables.isEmpty() && finalResult.isSuccess()) {
            return new JsSuccess<T>(finalResult.asSuccess().get().get());
        } else {
            // should never happens
        }
        return new JsError<T>(throwables);
    }
}
@@ -1915,32 +1915,371 @@
 ss%3C%3E(t);%0A    %7D%0A%0A
+    public %3CX%3E X fold(Function%3CJsError%3CT%3E, X%3E onError, Function%3CJsSuccess%3CT%3E, X%3E onSuccess) %7B%0A        if (isErrors()) %7B%0A            return onError.apply(this.toError());%0A        %7D%0A        return onSuccess.apply(this.toSuccess());%0A    %7D%0A%0A    public %3CX%3E X transform(Function%3CJsResult%3CT%3E, X%3E trans) %7B%0A        return trans.apply(this);%0A    %7D%0A%0A
     public Optio

Add fold and transform