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

import net.ranides.assira.collection.iterators.ForwardIterator;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.reflection.*;

import java.io.Serializable;
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.Spliterator;
import java.util.function.Consumer;

/**
 * It is IClass implementation used to represent wildcard types:
 * "? extends T" or "? super T".
 *
 * It tries to resolve upper and lower type bounds and use them in instanceof tests.
 *
 * @param <T> T
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class RWClass<T> extends AClass<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final IAttributes WATTRS = IAttributes.of(IAttribute.PUBLIC).immutable();
    
    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;
    }

    public RWClass(IContext context, Type type, IClass<?>[] upper, IClass<?>[] lower, AnnotatedElement atype) {
        super(context);
        this.type = type;
        this.upper = upper;
        this.lower = lower;
        this.atype = atype;
    }

    @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<?> outer() {
        return NULL;
    }

    @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, CQuery.from().iterable(() -> new RWParents(attr)));
    }

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

    @SuppressWarnings("unchecked")
    @Override
    public Class<T> raw() {
        return (Class)Object.class;
    }

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

    @Override
    public String name() {
        return type.getTypeName();
    }
    
    @Override
    public IAttributes attributes() {
        return WATTRS;
    }

    public IClass<?> swap() {
        return new RWClass<>(context(), type, lower, upper, atype);
    }

    private CQuery<Annotation> getAnnotations(AHints attr) {
        return CQuery.from().array(() -> {
            boolean is = attr.require().contains(IAttribute.DECLARED);
            return is ? atype.getDeclaredAnnotations() : atype.getAnnotations();
        });
    }
    
    private CQuery<IClass<?>> getInterfaces(AHints hints) {
        CQuery<IClass<?>> declared = CQuery.from().array(upper).filter(IClass::isInterface);

        if(hints.isDeclaredHint()) {
            return declared;
        } else {
            CQuery<IClass<?>> inherited = parents().stream()
                .flat(c -> c.interfaces().require(IAttribute.DECLARED).stream());
            return CQuery.builder()
                .withQuery(declared)
                .withQuery(inherited)
                .build()
                .treeFlatIf(c -> true, c -> c.interfaces().require(IAttribute.DECLARED).stream())
                .distinct();
        }
    }

    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);
            }
        }

    }
    
    
    
}
