/*
 * @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.AnnotatedElement;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Consumer;
import net.ranides.assira.collection.iterators.ForwardIterator;
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 class RWClass<T> extends AClass<T> {
    
    private static final Set<IAttribute> WATTRS = IAttribute.constant(IAttribute.PUBLIC);
    
    private static final IClass<?>[] EMPTY = new IClass<?>[0];

    private final Type type;
    private final IClass<?>[] upper;
    private final IClass<?>[] lower;
    private final AnnotatedElement atype;
    
    RWClass(IContext context, WildcardType type) {
        super(context);
        this.type = type;
        this.upper = map(type.getUpperBounds());
        this.lower = map(type.getLowerBounds());
        this.atype = Object.class;
    }
    
    RWClass(IContext context, TypeVariable<?> type) {
        super(context);
        this.type = type;
        this.upper = map(type.getBounds());
        this.lower = EMPTY;
        this.atype = type;
    }
    
    @Override
    public IAnnotations annotations() {
        AHints attr = FElements.newHints();
        return FElements.newAnnotations(attr, getAnnotations(attr));
    }
    
    private IClass<?>[] map(Type[] types) {
//        IContext c = context();
        return Arrays.stream(types).map(IClass::typeinfo).toArray(IClass[]::new);
    }
    
    @SuppressWarnings("PMD.MethodReturnsInternalArray")
    IClass<?>[] upper() {
        return upper;
    }
    
    @SuppressWarnings("PMD.MethodReturnsInternalArray")
    IClass<?>[] lower() {
        return lower;
    }
    
    @Override
    public IClass<?> parent() {
        return FElements.newClasses(upper).discard(IAttribute.INTERFACE).first().get();
    }
    
    @Override
    public IClasses parents() {
        AHints attr = FElements.newHints();
        return FElements.newClasses(attr, CQueryBuilder.fromIterable(() -> new RWParents(attr)));
    }

    @Override
    public IClasses interfaces() {
        AHints attr = FElements.newHints();
        return FElements.newClasses(attr, getInterfaces(attr));
    }
    
    @Override
    public Type reflective() {
        return type;
    }

    @Override
    public Class<?> raw() {
        return Object.class;
    }

    @Override
    public boolean isInstance(Object value) {
        return RCompareUtils.isInstance(this, value);
    }

    @Override
    public String name() {
        return type.getTypeName();
    }
    
    @Override
    public Set<IAttribute> attributes() {
        return WATTRS;
    }

    private CQuery<Annotation> getAnnotations(AHints attr) {
        return CQueryBuilder.fromArray(() -> {
            boolean is = attr.require().contains(IAttribute.DECLARED);
            return is ? atype.getDeclaredAnnotations() : atype.getAnnotations();
        });
    }
    
    private CQuery<IClass<?>> getInterfaces(AHints hints) {
        if(hints.hintDeclared()) {
            return CQueryBuilder.fromArray(upper)
                .filter(m -> m.attributes().contains(IAttribute.INTERFACE));
        } else {
            return parents()
                .stream().unfold(c->c.interfaces().stream());
        }
    }

    private class RWParents extends ForwardIterator<IClass<?>> {

        private final AHints hints;
        private Spliterator<IClass<?>> parents;

        public RWParents(AHints hints) {
            this.hints = hints;
        }

        @Override
        public boolean next(Consumer<? super IClass<?>> action) {
            if(parents == null) {
                IClass<?> ip = parent();
                parents = ip.parents().hint(hints).stream().spliterator();
                action.accept(ip);
                return true;
            } else {
                return parents.tryAdvance(action);
            }
        }

    }
    
    
    
}
