package ru.yandex.dispenser.validation.client;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

/**
 * Result.
 *
 * @author Dmitriy Timashov <dm-tim@yandex-team.ru>
 */
public interface Result<T> {

    static <I> Result<I> success(I result) {
        return new Result<>() {
            @Override
            public <R> R match(Cases<I, R> cases) {
                return cases.success(result);
            }

            @Override
            public <R> R match(Function<I, R> onSuccess, Function<ErrorCollection, R> onFailure) {
                return onSuccess.apply(result);
            }

            @Override
            public <O> Result<O> apply(Function<I, O> map) {
                return Result.success(map.apply(result));
            }

            @Override
            public <O> Mono<Result<O>> applyMono(Function<I, Mono<O>> map) {
                return map.apply(result).map(Result::success);
            }

            @Override
            public <O> Flux<Result<O>> applyFlux(Function<I, Flux<O>> map) {
                return map.apply(result).map(Result::success);
            }

            @Override
            public <O> Result<O> andThen(Function<I, Result<O>> map) {
                return map.apply(result);
            }

            @Override
            public <O> Mono<Result<O>> andThenMono(Function<I, Mono<Result<O>>> map) {
                return map.apply(result);
            }

            @Override
            public <O> Flux<Result<O>> andThenFlux(Function<I, Flux<Result<O>>> map) {
                return map.apply(result);
            }

            @Override
            public Result<I> andThenDo(Function<I, Result<Void>> map) {
                return map.apply(result).match(v -> this, Result::failure);
            }

            @Override
            public void doOnSuccess(Consumer<I> consumer) {
                consumer.accept(result);
            }

            @Override
            public void doOnFailure(Consumer<ErrorCollection> consumer) {
            }

            @Override
            public <O> Result<O> cast(Class<O> clazz) {
                return apply(clazz::cast);
            }

            @Override
            public Result<Void> toVoid() {
                return Result.success(null);
            }

            @Override
            public boolean isSuccess() {
                return true;
            }

            @Override
            public boolean isFailure() {
                return false;
            }

            @Override
            public int hashCode() {
                return Objects.hash(result);
            }

            @SuppressWarnings("unchecked")
            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (!(obj instanceof Result)) {
                    return false;
                }
                final Result<Object> r = (Result<Object>) obj;
                return r.match(new Cases<>() {
                    @Override
                    public Boolean success(Object otherResult) {
                        return Objects.equals(result, otherResult);
                    }

                    @Override
                    public Boolean failure(ErrorCollection error) {
                        return false;
                    }
                });
            }

            @Override
            public String toString() {
                return "Result::success{"
                        + "result=" + result
                        + '}';
            }
        };
    }

    static <I> Result<I> failure(ErrorCollection error) {
        Objects.requireNonNull(error, "Error is required.");
        return new Result<>() {
            @Override
            public <R> R match(Cases<I, R> cases) {
                return cases.failure(error);
            }

            @Override
            public <R> R match(Function<I, R> onSuccess, Function<ErrorCollection, R> onFailure) {
                return onFailure.apply(error);
            }

            @SuppressWarnings("unchecked")
            @Override
            public <O> Result<O> apply(Function<I, O> map) {
                return (Result<O>) this;
            }

            @SuppressWarnings("unchecked")
            @Override
            public <O> Mono<Result<O>> applyMono(Function<I, Mono<O>> map) {
                return Mono.just((Result<O>) this);
            }

            @SuppressWarnings("unchecked")
            @Override
            public <O> Flux<Result<O>> applyFlux(Function<I, Flux<O>> map) {
                return Flux.just((Result<O>) this);
            }

            @SuppressWarnings("unchecked")
            @Override
            public <O> Result<O> andThen(Function<I, Result<O>> map) {
                return (Result<O>) this;
            }

            @SuppressWarnings("unchecked")
            @Override
            public <O> Mono<Result<O>> andThenMono(Function<I, Mono<Result<O>>> map) {
                return Mono.just((Result<O>) this);
            }

            @SuppressWarnings("unchecked")
            @Override
            public <O> Flux<Result<O>> andThenFlux(Function<I, Flux<Result<O>>> map) {
                return Flux.just((Result<O>) this);
            }

            @Override
            public Result<I> andThenDo(Function<I, Result<Void>> map) {
                return this;
            }

            @Override
            public void doOnSuccess(Consumer<I> consumer) {
            }

            @Override
            public void doOnFailure(Consumer<ErrorCollection> consumer) {
                consumer.accept(error);
            }

            @SuppressWarnings("unchecked")
            @Override
            public <O> Result<O> cast(Class<O> clazz) {
                return (Result<O>) this;
            }

            @Override
            public Result<Void> toVoid() {
                return Result.failure(error);
            }

            @Override
            public boolean isSuccess() {
                return false;
            }

            @Override
            public boolean isFailure() {
                return true;
            }

            @Override
            public int hashCode() {
                return Objects.hash(error);
            }

            @SuppressWarnings("unchecked")
            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (!(obj instanceof Result)) {
                    return false;
                }
                final Result<Object> result = (Result<Object>) obj;
                return result.match(new Cases<>() {
                    @Override
                    public Boolean success(Object result) {
                        return false;
                    }

                    @Override
                    public Boolean failure(ErrorCollection otherError) {
                        return Objects.equals(error, otherError);
                    }
                });
            }

            @Override
            public String toString() {
                return "Result::failure{"
                        + "error=" + error
                        + '}';
            }
        };
    }

    static <U, V> Result<Tuple2<U, V>> zip(Tuple2<Result<U>, Result<V>> results) {
        return results.getT1().match(new Cases<>() {
            @Override
            public Result<Tuple2<U, V>> success(U resultLeft) {
                return results.getT2().match(new Cases<>() {
                    @Override
                    public Result<Tuple2<U, V>> success(V resultRight) {
                        return Result.success(Tuples.of(resultLeft, resultRight));
                    }

                    @Override
                    public Result<Tuple2<U, V>> failure(ErrorCollection error) {
                        return Result.failure(error);
                    }
                });
            }

            @Override
            public Result<Tuple2<U, V>> failure(ErrorCollection errorLeft) {
                return results.getT2().match(new Cases<>() {
                    @Override
                    public Result<Tuple2<U, V>> success(V resultRight) {
                        return Result.failure(errorLeft);
                    }

                    @Override
                    public Result<Tuple2<U, V>> failure(ErrorCollection errorRight) {
                        return Result.failure(ErrorCollection.builder().add(errorLeft).add(errorRight).build());
                    }
                });
            }
        });
    }

    <R> R match(Cases<T, R> cases);

    <R> R match(Function<T, R> onSuccess, Function<ErrorCollection, R> onFailure);

    <R> Result<R> apply(Function<T, R> map);

    <R> Mono<Result<R>> applyMono(Function<T, Mono<R>> map);

    <R> Flux<Result<R>> applyFlux(Function<T, Flux<R>> map);

    <R> Result<R> andThen(Function<T, Result<R>> map);

    <R> Mono<Result<R>> andThenMono(Function<T, Mono<Result<R>>> map);

    <R> Flux<Result<R>> andThenFlux(Function<T, Flux<Result<R>>> map);

    Result<T> andThenDo(Function<T, Result<Void>> map);

    void doOnSuccess(Consumer<T> consumer);

    void doOnFailure(Consumer<ErrorCollection> consumer);

    <R> Result<R> cast(Class<R> clazz);

    Result<Void> toVoid();

    boolean isSuccess();

    boolean isFailure();

    interface Cases<I, O> {

        O success(I result);

        O failure(ErrorCollection error);

    }

}
