package org.fungsi;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static java.util.Objects.requireNonNull;

public interface Either<L, R> {
	public static <L, R> Either<L, R> left(L left) { return new Left<>(left); }
	public static <L, R> Either<L, R> right(R right) { return new Right<>(right); }

	public static <T> Either<T, Throwable> success(T value) { return left(value); }
	public static <T> Either<T, Throwable> failure(Throwable cause) { return right(cause); }
	public static <T> T unsafe(Either<T, Throwable> e) {
        return e.fold(Function.identity(), r -> {
            throw Throwables.propagate(r);
        });
	}

    public static <L, R> Either<L, R> of(Optional<L> left, Supplier<R> right) {
        return left.<Either<L, R>>map(Either::left).orElseGet(() -> Either.right(right.get()));
    }

    public static <L, R> Either<L, R> of(Supplier<L> left, Optional<R> right) {
        return right.<Either<L, R>>map(Either::right).orElseGet(() -> Either.left(left.get()));
    }

    public static <T> Either<T, Unit> of(Optional<T> option) {
        return of(option, Unit.instance());
    }
    public static <T> Either<T, Unit> either(Optional<T> option) {
        return of(option, Unit.instance());
    }

    public static <T> Either<T, Unit> ofNullable(T left) {
        if (left == null) return Unit.right();
        return Either.left(left);
    }

	L left();
	boolean isLeft();
	R right();
	boolean isRight();

	<LL, RR> Either<LL, RR> bind(Function<L, Either<LL, RR>> left, Function<R, Either<LL, RR>> right);

	<T> T fold(Function<L, T> left, Function<R, T> right);

	default <LL, RR> Either<LL, RR> map(Function<L, LL> left, Function<R, RR> right) {
		return bind(left.andThen(Either::left), right.andThen(Either::right));
	}

	default <LL> Either<LL, R> leftFlatMap(Function<L, Either<LL, R>> left) {
		return bind(left, Either::right);
	}

	default <LL> Either<LL, R> leftMap(Function<L, LL> left) {
		return leftFlatMap(left.andThen(Either::left));
	}

	default Either<L, R> leftFilter(Supplier<R> otherwise, Predicate<L> fn) {
		return leftFlatMap(l -> fn.test(l) ? left(l) : right(otherwise.get()));
	}

    default Either<L, R> leftFilter(R otherwise, Predicate<L> fn) {
        return leftFilter(() -> otherwise, fn);
    }

    default Optional<L> leftOption() {
        return this.<Optional<L>>fold(Optional::of, x -> Optional.empty());
    }

    default Either<L, R> leftFallback(Supplier<Either<L, R>> fn) {
        return bind(Either::left, r -> fn.get());
    }

    default L leftOrElse(Supplier<L> fn) {
        return fold(Function.identity(), r -> fn.get());
    }

    default L leftOrThrow(Supplier<Throwable> fn) {
        return fold(Function.identity(), r -> {
            throw Throwables.propagate(fn.get());
        });
    }

	default <RR> Either<L, RR> rightFlatMap(Function<R, Either<L, RR>> right) {
		return bind(Either::left, right);
	}

	default <RR> Either<L, RR> rightMap(Function<R, RR> right) {
		return rightFlatMap(right.andThen(Either::right));
	}

	default Either<L, R> rightFilter(Supplier<L> otherwise, Predicate<R> fn) {
		return rightFlatMap(r -> fn.test(r) ? right(r) : left(otherwise.get()));
	}

    default Either<L, R> rightFilter(L otherwise, Predicate<R> fn) {
        return rightFilter(() -> otherwise, fn);
    }

    default Optional<R> rightOption() {
        return this.<Optional<R>>fold(x -> Optional.empty(), Optional::of);
    }

    default Either<L, R> rightFallback(Supplier<Either<L, R>> fn) {
        return bind(l -> fn.get(), Either::right);
    }

    default R rightOrElse(Supplier<R> fn) {
        return fold(l -> fn.get(), Function.identity());
    }

    default R rightOrThrow(Supplier<Throwable> fn) {
        return fold(l -> {
            throw Throwables.propagate(fn.get());
        }, Function.identity());
    }

	default Either<L, R> ifLeft(Consumer<L> fn) {
		return leftFlatMap(l -> {
			fn.accept(l);
			return Either.this;
		});
	}

	default Either<L, R> ifRight(Consumer<R> fn) {
		return rightFlatMap(r -> {
			fn.accept(r);
			return Either.this;
		});
	}

	default Either<R, L> swap() {
		return bind(Either::right, Either::left);
	}

	default Matcher<L, R> match(Consumer<L> fn) {
		return new Matcher<>(this, fn);
	}

	default <T> FoldToRight<R, T> foldLeft(Function<L, T> fn) {
		return new Folder<L, R, T>(this).setLeft(fn);
	}

	default <T> FoldToLeft<L, T> foldRight(Function<R, T> fn) {
		return new Folder<L, R, T>(this).setRight(fn);
	}

	static final class Left<L, R> implements Either<L, R> {

		final L value;

		Left(L value) {
			this.value = value;
		}

		@Override
		public L left() {
			return value;
		}

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

		@Override
		public R right() {
			throw new IllegalStateException();
		}

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

		@Override
		public <LL, RR> Either<LL, RR> bind(Function<L, Either<LL, RR>> left, Function<R, Either<LL, RR>> right) {
			return left.apply(value);
		}

		@Override
		public <T> T fold(Function<L, T> left, Function<R, T> right) {
			return left.apply(value);
		}
	}

	static final class Right<L, R> implements Either<L, R> {

		final R value;

		Right(R value) {
			this.value = value;
		}

		@Override
		public L left() {
			throw new IllegalStateException();
		}

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

		@Override
		public R right() {
			return value;
		}

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

		@Override
		public <LL, RR> Either<LL, RR> bind(Function<L, Either<LL, RR>> left, Function<R, Either<LL, RR>> right) {
			return right.apply(value);
		}

		@Override
		public <T> T fold(Function<L, T> left, Function<R, T> right) {
			return right.apply(value);
		}
	}

	@SuppressWarnings("UnusedParameters")
	public static <L, R> Either<L, R> left(L left, Class<R> ignored) { return left(left); }
	@SuppressWarnings("UnusedParameters")
	public static <L, R> Either<L, R> right(Class<L> ignored, R right) { return right(right); }

	public static final class Matcher<L, R> {
		private final Either<L, R> value;
		private final Consumer<L> left;

		Matcher(Either<L, R> value, Consumer<L> left) {
			this.value = value;
			this.left = left;
		}

		public Either<L, R> then(Consumer<R> right) {
			value.ifLeft(left);
			value.ifRight(right);
			return value;
		}
	}

	public static interface FoldToLeft<L, T> {
		T thenLeft(Function<L, T> fn);
	}

	public static interface FoldToRight<R, T> {
		T thenRight(Function<R, T> fn);
	}

	public static final class Folder<L, R, T> implements FoldToLeft<L, T>, FoldToRight<R, T> {
		private final Either<L, R> value;
		private Function<L, T> left;
		private Function<R, T> right;

		Folder(Either<L, R> value) {
			this.value = value;
		}

		Folder<L, R, T> setLeft(Function<L, T> left) {
			this.left = left;
			return this;
		}

		Folder<L, R, T> setRight(Function<R, T> right) {
			this.right = right;
			return this;
		}

		T proceed() {
			return value.fold(requireNonNull(left, "left"), requireNonNull(right, "right"));
		}

		@Override
		public T thenLeft(Function<L, T> fn) {
			return setLeft(fn).proceed();
		}

		@Override
		public T thenRight(Function<R, T> fn) {
			return setRight(fn).proceed();
		}
	}
}
