package net.ranides.assira.reflection.util;

import net.ranides.assira.text.Wildcard;

import java.util.function.Predicate;

/**
 * Extension of Predicate, for Class.
 * Defines few helper methods for matching class by name, type, inner class etc
 */
public interface ClassPredicate extends Predicate<Class<?>> {

    /**
     * Predicate accepting everything
     */
    ClassPredicate ANY = c -> true;

    /**
     * Predicate accepting nothing
     */
    ClassPredicate NONE = c -> false;

    @Override
    default ClassPredicate negate() {
        return c -> !test(c);
    }

    @Override
    default ClassPredicate and(Predicate<? super Class<?>> other) {
        return c -> test(c) && other.test(c);
    }

    @Override
    default ClassPredicate or(Predicate<? super Class<?>> other) {
        return c -> test(c) || other.test(c);
    }

    /**
     * Predicate "OR" composition:
     * returns new predicate which matches additional classes by name
     *
     * @param wildcard wildcard
     * @return ClassPredicate
     */
    default ClassPredicate accept(String wildcard) {
        Wildcard w = Wildcard.compile(wildcard);
        return or(c -> w.test(c.getName()));
    }

    /**
     * Predicate "OR" composition:
     * returns new predicate which matches additional classes by name
     *
     * @param predicate predicate
     * @return ClassPredicate
     */
    default ClassPredicate accept(Predicate<String> predicate) {
        return or(c -> predicate.test(c.getName()));
    }

    /**
     * Predicate "OR" composition:
     * returns new predicate which matches additionally provided class
     *
     * @param type type
     * @return ClassPredicate
     */
    default ClassPredicate accept(Class<?> type) {
        return or(c -> type.equals(c));
    }

    /**
     * Predicate "OR" composition:
     * returns new predicate which matches inner classes declared inside provided class
     *
     * @param type type
     * @return ClassPredicate
     */
    default ClassPredicate acceptInner(Class<?> type) {
        String inner = type.getName() + "$";
        return or(c -> c.getName().startsWith(inner));
    }

    /**
     * Predicate "AND" composition:
     * returns new predicate which rejects classes by name
     *
     * @param wildcard wildcard
     * @return ClassPredicate
     */
    default ClassPredicate reject(String wildcard) {
        Wildcard w = Wildcard.compile(wildcard);
        return and(c -> w.test(c.getName()));
    }

    /**
     * Predicate "AND" composition:
     * returns new predicate which rejects additionally provided class
     *
     * @param type type
     * @return ClassPredicate
     */
    default ClassPredicate reject(Class<?> type) {
        return and(c -> !type.equals(c));
    }

    /**
     * Predicate "AND" composition:
     * returns new predicate which rejects inner classes declared inside provided class
     *
     * @param type type
     * @return ClassPredicate
     */
    default ClassPredicate rejectInner(Class<?> type) {
        String inner = type.getName() + "$";
        return and(c -> !c.getName().startsWith(inner));
    }
}
