package net.ranides.assira.generic;

import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.reflection.IClass;

import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
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 java.util.stream.Stream;

/**
 * It is equivalent to {@link Optional}, but implements {@link java.util.Set} interface, which means
 * it can be used as collection with zero ore one element.
 *
 * It would be great to extend Optional, but unfortunatelly it is final
 *
 * @param <V> V
 */
public abstract class Ref<V> extends AbstractSet<V> implements Serializable {

    private static final Ref EMPTY = new Simple(null);

    public static <Q> Ref<Q> empty() {
        return EMPTY;
    }

    public static <Q> Ref<Q> empty(Supplier<? extends RuntimeException> message) {
        return message == null ? empty() : new Custom<>(null, message);
    }

    public static <Q> Ref<Q> of(Q value) {
        return new Simple<>(value);
    }

    public static <Q> Ref<Q> of(Q value, Supplier<? extends RuntimeException> message) {
        return message == null ? new Simple<>(value) : new Custom<>(value, message);
    }

    public static <Q> Ref<Q> ofNullable(Q value) {
        return value != null ? new Simple<>(value) : empty();
    }

    public static <Q> Ref<Q> ofNullable(Q value, Supplier<? extends RuntimeException> message) {
        return message == null ? new Simple<>(value) : new Custom<>(value, message);
    }

    public static <Q> Ref<Q> ofOptional(Optional<Q> value) {
        return value.isPresent() ? new Simple<>(value.get()) : empty();
    }

    public static <Q> Ref<Q> ofOptional(Optional<Q> value, Supplier<? extends RuntimeException> message) {
        return message == null ? ofOptional(value) : new Custom<>(value.orElse(null), message);
    }

    public static <Q> Ref<Q> ofOptional(Ref<Q> value) {
        return value.isPresent() ? new Simple<>(value.get()) : empty();
    }

    public static <Q> Ref<Q> ofOptional(Ref<Q> value, Supplier<? extends RuntimeException> message) {
        return message == null ? ofOptional(value) : new Custom<>(value.orElse(null), message);
    }

    public abstract Supplier<? extends RuntimeException> message();

    public abstract Ref<V> withMessage(Supplier<? extends RuntimeException> message);

    @Override
    public abstract Iterator<V> iterator();

    @Override
    public final int size() {
        return isPresent() ? 1 : 0;
    }

    /**
     * Converts this object into ordinary "optional" class.
     *
     * @return optional
     */
    public final Optional<V> asOptional() {
        return isPresent() ? Optional.of(get()) : Optional.empty();
    }

    /**
     * Converts this object into ordinary "Stream" class.
     *
     * @return optional
     */
    public final Stream<V> asStream() {
        return isPresent() ? Stream.of(get()) : Stream.empty();
    }

    /**
     * Converts this object into "CQuery" class.
     *
     * @return optional
     */
    public final CQuery<V> asQuery() {
        return CQuery.from().optional(this);
    }

    /**
     * Returns binded value.
     * For example: simple wrapper will return stored value, EntryWrapper will read from Map.
     *
     * @return value value
     */
    public abstract V get();

    /**
     * Casts this Wrapper to new type: it is safe, checked cast.
     * It does not create new wrapper, it is only cast operation.
     *
     * @param type type
     * @param <R>  R
     * @return this
     */
    public abstract <R> Ref<R> cast(Class<R> type);

    /**
     * Casts this Wrapper to new type: it is safe, checked cast.
     * It does not create new wrapper, it is only cast operation.
     *
     * @param type type
     * @param <R>  R
     * @return this
     */
    public final <R> Ref<R> cast(IClass<R> type) {
        return cast(type.raw());
    }

    /**
     * Checks if value is not "null"
     *
     * @return boolean
     */
    public abstract boolean isPresent();

    /**
     * Returns true if optional does not contain value
     *
     * @return boolean
     */
    public final boolean isAbsent() {
        return !isPresent();
    }

    /**
     * Executes consumer with value stored inside optional if it is non-empty.
     * Does nothing if optional is empty.
     *
     * @param consumer consumer
     */
    public final void ifPresent(Consumer<? super V> consumer) {
        if (isPresent()) {
            consumer.accept(get());
        }
    }

    public final void ifPresentOrElse(Consumer<? super V> action, Runnable emptyAction) {
        if (isPresent()) {
            action.accept(get());
        } else {
            emptyAction.run();
        }
    }

    /**
     * Executes consumer if there is no value inside optional.
     * Does nothing if optional is not empty.
     *
     * @param consumer consumer
     */
    public final void ifAbsent(Runnable consumer) {
        if (isAbsent()) {
            consumer.run();
        }
    }

    /**
     * Returns value stored in optional or "other" if optional is empty.
     *
     * @param other other
     * @return value
     */
    public final V orElse(V other) {
        return isPresent() ? get() : other;
    }

    /**
     * Returns value stored in optional or computes "other" if optional is empty.
     *
     * @param other other
     * @return value
     */
    public final V orElseGet(Supplier<? extends V> other) {
        return isPresent() ? get() : other.get();
    }

    /**
     * Returns value stored in optional or throws computed exception if optional is empty.
     *
     * @param onEmpty onEmpty
     * @param <X>     X
     * @return value
     * @throws X if empty
     */
    public final <X extends Throwable> V orElseThrow(Supplier<? extends X> onEmpty) throws X {
        if (isPresent()) {
            return get();
        } else {
            throw onEmpty.get();
        }
    }


    /**
     * Returns optional with value stored inside it or with value generated by supplier
     *
     * @param supplier supplier
     * @return optional
     */
    public abstract Ref<V> or(Supplier<? extends Ref<? extends V>> supplier);

    /**
     * Returns optional with value stored inside it or with value generated by supplier
     *
     * @param supplier supplier
     * @return optional
     */
    public abstract Ref<V> orOptional(Supplier<? extends Optional<? extends V>> supplier);

    /**
     * Returns new optional with stored value if it matches predicate
     *
     * @param predicate predicate
     * @return optional
     */
    public abstract Ref<V> filter(Predicate<? super V> predicate);

    /**
     * Returns new optional with stored value converted by provided mapping function
     *
     * @param mapper mapper
     * @param <U>    U
     * @return optional
     */
    public abstract <U> Ref<U> map(Function<? super V, ? extends U> mapper);

    /**
     * Returns new optional with stored value converted by provided mapping function
     *
     * @param mapper mapper
     * @param <U>    U
     * @return optional
     */
    public abstract <U> Ref<U> flatMap(Function<? super V, ? extends Ref<? extends U>> mapper);

    private static boolean isNoMessage(Supplier<? extends RuntimeException> message) {
        return message == null;
    }

    private static final class Simple<V> extends Ref<V> {

        private final V value;

        private Simple(V value) {
            this.value = value;
        }

        @Override
        public Supplier<? extends RuntimeException> message() {
            return null;
        }

        @Override
        public Ref<V> withMessage(Supplier<? extends RuntimeException> message) {
            if (message != null) {
                return new Custom<>(value, message);
            } else {
                return this;
            }
        }

        @Override
        public Iterator<V> iterator() {
            return isPresent() ? IteratorUtils.singletonIterator(value) : Collections.emptyIterator();
        }

        @Override
        public V get() {
            if (isPresent()) {
                return value;
            } else {
                throw new NoSuchElementException();
            }
        }

        @Override
        public <R> Ref<R> cast(Class<R> type) {
            if (isAbsent() || type.isInstance(value)) {
                return (Ref) this;
            } else {
                return empty();
            }
        }

        @Override
        public boolean isPresent() {
            return value != null;
        }

        @Override
        public Ref<V> or(Supplier<? extends Ref<? extends V>> supplier) {
            if (isPresent()) {
                return this;
            } else {
                return new Simple<>(supplier.get().orElse(null));
            }
        }

        @Override
        public Ref<V> orOptional(Supplier<? extends Optional<? extends V>> supplier) {
            if (isPresent()) {
                return this;
            } else {
                return new Simple<>(supplier.get().orElse(null));
            }
        }

        @Override
        public Ref<V> filter(Predicate<? super V> predicate) {
            if (isAbsent() || predicate.test(value)) {
                return this;
            } else {
                return empty();
            }
        }

        @Override
        public <U> Ref<U> map(Function<? super V, ? extends U> mapper) {
            if (isAbsent()) {
                return (Ref) this;
            } else {
                return new Simple<>(mapper.apply(value));
            }
        }

        @Override
        public <U> Ref<U> flatMap(Function<? super V, ? extends Ref<? extends U>> mapper) {
            if (isAbsent()) {
                return (Ref) this;
            } else {
                return new Simple<>(mapper.apply(value).orElse(null));
            }
        }

    }

    private static final class Custom<V> extends Ref<V> {

        private final Supplier<? extends RuntimeException> message;

        private final V value;

        private Custom(V value, Supplier<? extends RuntimeException> message) {
            Objects.requireNonNull(message);
            this.value = value;
            this.message = message;
        }

        @Override
        public Supplier<? extends RuntimeException> message() {
            return message;
        }

        public Ref<V> withMessage(Supplier<? extends RuntimeException> message) {
            if (message == this.message) {
                return this;
            }
            if (message == null) {
                return new Simple<>(value);
            }
            return new Custom<>(value, message);
        }

        @Override
        public Iterator<V> iterator() {
            return new CustomIterator<>(message, value);
        }

        @Override
        public V get() {
            if (isPresent()) {
                return value;
            } else {
                throw message.get();
            }
        }

        @Override
        public <R> Ref<R> cast(Class<R> type) {
            if (isAbsent()) {
                return (Ref) this;
            } else {
                return new Custom<>(type.isInstance(value) ? type.cast(value) : null, message);
            }
        }

        @Override
        public boolean isPresent() {
            return value != null;
        }

        @Override
        public Ref<V> or(Supplier<? extends Ref<? extends V>> supplier) {
            if (isPresent()) {
                return this;
            } else {
                return new Custom<>(supplier.get().orElse(null), message);
            }
        }

        @Override
        public Ref<V> orOptional(Supplier<? extends Optional<? extends V>> supplier) {
            if (isPresent()) {
                return this;
            } else {
                return new Custom<>(supplier.get().orElse(null), message);
            }
        }

        @Override
        public Ref<V> filter(Predicate<? super V> predicate) {
            if (isAbsent() || predicate.test(value)) {
                return this;
            } else {
                return new Custom<>(null, message);
            }
        }

        @Override
        public <U> Ref<U> map(Function<? super V, ? extends U> mapper) {
            if (isAbsent()) {
                return (Ref) this;
            } else {
                return new Custom<>(mapper.apply(value), message);
            }
        }

        @Override
        public <U> Ref<U> flatMap(Function<? super V, ? extends Ref<? extends U>> mapper) {
            if (isAbsent()) {
                return (Ref) this;
            } else {
                return new Custom<>(mapper.apply(value).orElse(null), message);
            }
        }

    }

    private static class CustomIterator<V> implements Iterator<V> {

        private final Supplier<? extends RuntimeException> message;

        private V value;

        public CustomIterator(Supplier<? extends RuntimeException> message, V value) {
            this.message = message;
            this.value = value;
        }

        public boolean hasNext() {
            return value != null;
        }

        @Override
        public V next() {
            if (value != null) {
                V out = value;
                value = null;
                return out;
            }
            RuntimeException cause = message.get();
            NoSuchElementException exception = new NoSuchElementException(cause.getMessage());
            exception.initCause(cause);
            throw exception;
        }

    }
}
