package com.github.peterpaul.fn;

import com.github.peterpaul.fn.equals.Equals;
import com.github.peterpaul.fn.reciters.OptionReciter;
import com.github.peterpaul.fn.validations.Validations;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.util.Objects;

public abstract class Option<T> implements Recitable<T> {
    @Nonnull
    public static <T> Option<T> of(@CheckForNull T item) {
        if (item == null) {
            return none();
        } else {
            return new Some<>(item);
        }
    }

    @Nonnull
    public static <T> Option<T> some(@Nonnull T item) {
        return new Some<>(item);
    }

    @Nonnull
    @SuppressWarnings("unchecked")
    public static <T> Option<T> none() {
        return (Option<T>) None.NONE;
    }

    @Nonnull
    public T get() {
        throw new NullPointerException();
    }

    @Nonnull
    public T or(@Nonnull T defaultValue) {
        return defaultValue;
    }

    @Nonnull
    public T or(@Nonnull Supplier<T> defaultValue) {
        return defaultValue.get();
    }

    @Nonnull
    public T orThrow(@Nonnull RuntimeException e) {
        throw e;
    }

    @Nonnull
    public T orThrow(@Nonnull Supplier<RuntimeException> e) {
        throw e.get();
    }

    @Nonnull
    public <R> Option<R> map(@Nonnull Function<? super T, ? extends R> mapper) {
        return Option.none();
    }

    @Nonnull
    public <R> Option<R> flatMap(@Nonnull Function<T, Option<R>> mapper) {
        return Option.none();
    }

    @Nonnull
    public Option<T> filter(@Nonnull Predicate<T> predicate) {
        return Option.none();
    }

    @Nonnull
    public Option<T> peek(@Nonnull Consumer<? super T> consumer) {
        return this;
    }

    public boolean isPresent() {
        return false;
    }

    @Nonnull
    @Override
    public Reciter<T> reciter() {
        return new OptionReciter<>(this);
    }

    private static final class Some<T> extends Option<T> {
        private final T value;

        public Some(@Nonnull T value) {
            this.value = Validations.notNull(value);
        }

        @Nonnull
        @Override
        public T get() {
            return value;
        }

        @Nonnull
        @Override
        public T or(@Nonnull T defaultValue) {
            return value;
        }

        @Nonnull
        @Override
        public T or(@Nonnull Supplier<T> defaultValue) {
            return value;
        }

        @Nonnull
        @Override
        public T orThrow(@Nonnull RuntimeException e) {
            return value;
        }

        @Nonnull
        @Override
        public T orThrow(@Nonnull Supplier<RuntimeException> e) {
            return value;
        }

        @Nonnull
        @Override
        public <R> Option<R> map(@Nonnull Function<? super T, ? extends R> mapper) {
            return Option.of(mapper.apply(value));
        }

        @Nonnull
        @Override
        public <R> Option<R> flatMap(@Nonnull Function<T, Option<R>> mapper) {
            return mapper.apply(value);
        }

        @Nonnull
        @Override
        public Option<T> filter(@Nonnull Predicate<T> predicate) {
            return predicate.apply(value)
                    ? this
                    : Option.<T>none();
        }

        @Nonnull
        @Override
        public Option<T> peek(@Nonnull Consumer<? super T> consumer) {
            consumer.consume(value);
            return this;
        }

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

        @Nonnull
        @Override
        public String toString() {
            return "some(" +
                    "value=" + value +
                    ')';
        }

        @Override
        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
        public boolean equals(Object o) {
            return Equals.equals(this, o, new Equals.EqualsChecker<Some<T>>() {
                @Override
                public boolean isEqualTo(@Nonnull Some<T> other) {
                    return Objects.equals(value, other.value);
                }
            });
        }

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

    private static final class None<T> extends Option<T> {
        static final None NONE = new None();

        @Nonnull
        @Override
        public String toString() {
            return "none";
        }
    }
}

@@ -1382,32 +1382,168 @@
 .none();%0A    %7D%0A%0A
+    @Nonnull%0A    public abstract %3CR%3E R map(@Nonnull Function%3C? super T, ? extends R%3E mapper, @Nonnull Supplier%3C? extends R%3E supplier);%0A%0A
     @Nonnull%0A   
@@ -1606,32 +1606,32 @@
 n%3CR%3E%3E mapper) %7B%0A
-
         return O
@@ -3024,32 +3024,236 @@
 e));%0A        %7D%0A%0A
+        @Nonnull%0A        @Override%0A        public %3CR%3E R map(@Nonnull Function%3C? super T, ? extends R%3E mapper, @Nonnull Supplier%3C? extends R%3E supplier) %7B%0A            return mapper.apply(value);%0A        %7D%0A%0A
         @Nonnull
@@ -4748,32 +4748,32 @@
 ng toString() %7B%0A
-
             retu
@@ -4788,21 +4788,220 @@
 %22;%0A        %7D
+%0A%0A        @Nonnull%0A        @Override%0A        public %3CR%3E R map(@Nonnull Function%3C? super T, ? extends R%3E mapper, @Nonnull Supplier%3C? extends R%3E supplier) %7B%0A            return supplier.get();%0A        %7D
 %0A    %7D%0A%7D%0A

Add map method that returns R.