package ru.yandex.webmaster3.core.util.enums;

import org.jetbrains.annotations.Nullable;
import ru.yandex.common.util.functional.Function;
import ru.yandex.common.util.functional.Functions;

import java.util.Set;

/**
 * Set of {@link IntEnum}. It is not {@link Set} actually because it has not {@link Set#size()}.
 *
 * @author Stepan Koltsov
 */
@SuppressWarnings("unchecked")
public abstract class ExtendedIntEnumSet<T extends IntEnum> {
    public abstract boolean contains(T t);

    public Function<T, Boolean> containsP() {
        return new Function<T, Boolean>() {
            public Boolean apply(T t) {
                return contains(t);
            }
        };
    }

    public abstract ExtendedIntEnumSet<T> invert();

    // XXX: API sucks

    public boolean isAny() { return false; }
    public boolean isNone() { return false; }

    public static <T extends IntEnum> ExtendedIntEnumSet<T> any() {
        return (ExtendedIntEnumSet<T>) Any.ANY;
    }

    public static <T extends IntEnum> ExtendedIntEnumSet<T> none() {
        return (ExtendedIntEnumSet<T>) None.NONE;
    }

    public static <T extends IntEnum> ExtendedIntEnumSet<T> anyOf(Set<T> set, @Nullable String defaultColumnName) {
        if (set.isEmpty()) return none();
        else return new AnyOf<T>(set, defaultColumnName);
    }

    public static <T extends IntEnum> ExtendedIntEnumSet<T> anyOf(Set<T> set) {
        if (set.isEmpty()) return none();
        else return new AnyOf<T>(set, null);
    }

    public static <T extends IntEnum> ExtendedIntEnumSet<T> noneOf(Set<T> set, @Nullable String defaultColumnName) {
        if (set.isEmpty()) return any();
        else return new NoneOf<T>(set, defaultColumnName);
    }

    public static <T extends IntEnum> ExtendedIntEnumSet<T> noneOf(Set<T> set) {
        if (set.isEmpty()) return any();
        return new NoneOf<T>(set, null);
    }

    static class Any<T extends IntEnum> extends ExtendedIntEnumSet<T> {
        private static final Any ANY = new Any();


        public boolean contains(T t) {
            return true;
        }

        public Function<T, Boolean> containsP() {
            return Functions.always(true);
        }

        public boolean isAny() {
            return true;
        }

        public ExtendedIntEnumSet<T> invert() {
            return none();
        }
    }

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

        public boolean contains(T t) {
            return false;
        }

        public Function<T, Boolean> containsP() {
            return Functions.always(false);
        }

        public ExtendedIntEnumSet<T> invert() {
            return any();
        }

        public boolean isNone() {
            return true;
        }
    }

    static abstract class SetSupport<T extends IntEnum> extends ExtendedIntEnumSet<T> {
        protected final Set<T> set;
        @Nullable
        private final String defaultColumnName;

        public SetSupport(Set<T> set, String defaultColumnName) {
            this.set = set;
            this.defaultColumnName = defaultColumnName;
        }

        protected abstract String operator();
    }

    static class AnyOf<T extends IntEnum> extends SetSupport<T> {
        public AnyOf(Set<T> set, String defaultColumnName) {
            super(set, defaultColumnName);
        }

        @Override
        protected String operator() {
            return "IN";
        }

        @Override
        public boolean contains(T t) {
            return set.contains(t);
        }

        @Override
        public ExtendedIntEnumSet<T> invert() {
            return noneOf(set);
        }
    }

    static class NoneOf<T extends IntEnum> extends SetSupport<T> {

        @Override
        protected String operator() {
            return "NOT IN";
        }

        public NoneOf(Set<T> set, String defaultColumnName) {
            super(set, defaultColumnName);
        }

        public boolean contains(T t) {
            return !set.contains(t);
        }

        public ExtendedIntEnumSet<T> invert() {
            return anyOf(set);
        }
    }

}