/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.CQueryBuilder;
import net.ranides.assira.reflection.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class AClasses extends AElements<IClass<?>> implements IClasses {
    
    static final AClasses EMPTY = FElements.newClasses(AHints.EMPTY, CQueryBuilder.empty());

    AClasses(IHints hints, CQuery<IClass<?>> stream) {
        super(hints, stream);
    }

    @Override
    public List<String> names() {
        return list(IClass::name);
    }

    @Override
    public IClasses require(Predicate<? super IClass<?>> predicate) {
        filter(predicate);
		return this;
    }

    @Override
    public IClasses require(IAnnotations req) {
        return require(c -> {
			Set<Annotation> set = c.annotations().stream().collect(Collectors.toSet());
			return req.stream().matchAll(set::contains);
		});
    }

    @Override
    public IClasses require(Class<? extends Annotation> annotation) {
        return require(c -> !c.annotations().require(annotation).isEmpty());
    }

    @Override
    public IClasses require(IAttribute attribute) {
        hints().require(attribute);
        return attribute.equals(IAttribute.ANY) ? this : require(c -> c.attributes().contains(attribute));
    }
    
    @Override
    public IClasses require(Set<IAttribute> attributes) {
        hints().require(attributes);
        return attributes.contains(IAttribute.ANY) ? this : require(c -> c.attributes().containsAll(attributes));
    }

    @Override
    public IClasses require(IMethod method) {
        return require(c -> c.methods().stream().matchAny(method::equals));
    }
    
    @Override
    public IClasses discard(Predicate<? super IClass<?>> predicate) {
        return require(predicate.negate());
    }

    @Override
    public IClasses discard(IAnnotations annotations) {
        Set<Annotation> req = annotations.stream().collect(Collectors.toSet());
		return require(c -> !c.annotations().stream().matchAny(req::contains));
    }

    @Override
    public IClasses discard(Class<? extends Annotation> annotation) {
        return require(c -> c.annotations().require(annotation).isEmpty());
    }

    @Override
    public IClasses discard(IAttribute attribute) {
        hints().discard(attribute);
        return require(c -> !c.attributes().contains(attribute));
    }
    
    @Override
    public IClasses discard(Set<IAttribute> attributes) {
        hints().discard(attributes);
        return require(c -> !c.attributes().containsAll(attributes));
    }

    @Override
    public IClasses discard(IMethod method) {
        return require(c -> !c.methods().stream().matchAny(method::equals));
    }

    @Override
    public IFields fields() {
        IHints attr = FElements.newHints();
        return FElements.newFields(attr, stream().unfold(c -> c.fields().hint(attr).stream()));
    }

    @Override
    public IMethods constructors() {
        IHints attr = FElements.newHints();
        return FElements.newMethods(attr, stream().unfold(c -> c.constructors().hint(attr).stream()));
    }

    @Override
    public IMethods methods() {
        IHints attr = FElements.newHints();
        return FElements.newMethods(attr, stream().unfold(c -> c.methods().hint(attr).stream()));
    }

    @Override
    public IClasses parent() {
        return FElements.newClasses(AHints.EMPTY, stream(IClass::parent));
    }

    @Override
    public IClasses parents() {
        IHints attr = FElements.newHints();
        return FElements.newClasses(attr, stream().unfold(c -> c.parents().hint(attr).stream()));
    }

    @Override
    public IClasses interfaces() {
        IHints attr = FElements.newHints();
        return FElements.newClasses(attr, stream().unfold(c -> c.interfaces().hint(attr).stream()));
    }

    @Override
    public IClasses outer() {
        return FElements.newClasses(AHints.EMPTY, stream(IClass::outer));
    }

    @Override
    public IClasses inner() {
        IHints attr = FElements.newHints();
        return FElements.newClasses(attr, stream().unfold(c -> c.inner().hint(attr).stream()));
    }

    @Override
    public CQuery<Class<?>> raw() {
        return stream().map(IClass::raw);
    }
    
    @Override
    public CQuery<Type> reflective() {
        return stream().map(IClass::reflective);
    }

    @Override
    public boolean isSuper(IClasses types) {
        return ListUtils.equals(list(), types.list(), IClass::isSuper);
    }

    @Override
    public boolean isSubclass(IClasses types) {
        return ListUtils.equals(list(), types.list(), IClass::isSubclass);
    }
    
    @Override
    public boolean isInstance(Object... values) {
        if(this.count() != values.length) {
            return false;
        }
        int i=0;
        for(IClass<?> type : this) {
            if( !type.isInstance(values[i++]) ) {
                return false;
            }
        }
        return true;
    }
    
}
