/*
 * @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.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import net.ranides.assira.collection.iterators.ForwardIterator;
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.generic.LazyReference;
import net.ranides.assira.reflection.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class RClass<T> extends AClass<T> {

    private final Class<T> type;
    
    private final Supplier<IContext> icontext = LazyReference.concurrent(this::icontext);
    
    RClass(IContext context, Class<T> type) {
        super(context);
        this.type = type;
    }
    
    @Override
    public List<IClass<?>> params() {
        Type gtype = type.getGenericSuperclass();
        if(gtype instanceof ParameterizedType) {
            return FClass.newClass(context(), type, (ParameterizedType)gtype).params();
        }
        IContext c = context();
        return ListUtils.map(Arrays.asList(type.getTypeParameters()), c::typeinfo);
    }
    
    @Override
    public IContext context() {
        return icontext.get();
    }
    
    private IContext icontext() {
        return super.context().derive(type.getGenericSuperclass());
    }

    @Override
    public final IFields fields() {
        AHints attr = FElements.newHints();
        return FElements.newFields(attr, CQueryBuilder.fromQuery(attr, this::getRFields));
    }

    @Override
    public final IMethods constructors() {
        AHints attr = FElements.newHints();
        return FElements.newMethods(attr, CQueryBuilder.fromQuery(attr, this::getRConstructors));
    }

    @Override
    public final IMethods methods() {
        AHints attr = FElements.newHints();
        return FElements.newMethods(attr, CQueryBuilder.fromQuery(attr, this::getRMethods));
    }

    @Override
    public final IClass<?> parent() {
        return context().typeinfo(type.getSuperclass());
    }

    @Override
    public final IClasses parents() {
        if(attributes().contains(IAttribute.PRIMITIVE) || Object.class.equals(type)) {
            return AClasses.EMPTY;
        }
        AHints attr = FElements.newHints();
        return FElements.newClasses(attr, CQueryBuilder.fromIterable(()->new RParents(attr)));
    }

    @Override
    public final IClasses interfaces() {
        AHints attr = FElements.newHints();
        return FElements.newClasses(attr, CQueryBuilder.fromQuery(attr, this::getRInterfaces));
    }

    @Override
    public IClass<?> outer() {
        return context().typeinfo(type.getDeclaringClass());
    }

    @Override
    public final IClasses inner() {
        return context().typesinfo(type.getDeclaredClasses());
    }
    
    @Override
    public Type reflective() {
        return type;
    }

    @Override
    public final Class<?> raw() {
        return type;
    }

    @Override
    public final boolean isInstance(Object value) {
        return RCompareUtils.isInstance(this, value);
    }
    
    @Override
    public final String name() {
        return type.isArray() ? type.getComponentType().getName()+"[]": type.getName();
    }
    
    @Override
    public final String shortname() {
        return type.getSimpleName();
    }

    @Override
    public final Set<IAttribute> attributes() {
        return IAttribute.resolve(type);
    }

    @Override
    public final IAnnotations annotations() {
        AHints attr = FElements.newHints();
        return FElements.newAnnotations(attr, CQueryBuilder.fromArray(attr, this::getRAnnotations));
    }
    
    private CQuery<IMethod> getRMethods(AHints hints) {
        IContext c = context();
        if(hints.hintDeclared()) {
            return CQueryBuilder.fromArray(type.getDeclaredMethods()).map(m -> FMethod.newMethod(true, c, m));
        } else if(hints.hintAny()) {
            return CQueryBuilder
                .fromIterable(()->new RawAncestors(type))
                .split(Class::getDeclaredMethods)
                .map(m -> FMethod.newMethod(isdecl(m), c, m));
        } else {
            return CQueryBuilder.fromArray(type.getMethods()).map(m -> FMethod.newMethod(isdecl(m), c, m));
        }
    }
    
    private boolean isdecl(Member m) {
        return type.equals(m.getDeclaringClass());
    }
    
    private CQuery<IMethod> getRConstructors(AHints hints) {
        IContext c = context();
        if(hints.hintAny() || hints.hintDeclared()) {
            return CQueryBuilder.fromArray(type.getDeclaredConstructors()).map(m -> FMethod.newMethod(c,m));
        } else {
            return CQueryBuilder.fromArray(type.getConstructors()).map(m -> FMethod.newMethod(c,m));
        }
    }
    
    private class RParents extends ForwardIterator<IClass<?>> {

        private final AHints hints;
        private RClass<?> now;

        public RParents(AHints hints) {
            this.now = RClass.this;
            this.hints = hints;
        }

        @Override
        public boolean next(Consumer<? super IClass<?>> action) {
            if(null==now || OBJECT.equals(now)) {
                return false;
            }
            now = (RClass)now.context().typeinfo(now.type.getSuperclass());
            action.accept(now);
            if(now==OBJECT || hints.hintDeclared()) { // NOPMD
                now = null;
            }
            return true;
        }

    }
    
    private CQuery<IClass<?>> getRInterfaces(AHints hints) {
        IContext c = context();
        if(hints.hintDeclared()) {
            return CQueryBuilder
                .fromArray(type.getInterfaces())
                .map(i -> FClass.newClass(c,i));
        } else {
            return new CQueryBuilder<Class<?>>()
                .addQuery(CQueryBuilder.fromIterable(()->new RawParents(type)).filter(Class::isInterface))
                .addQuery(CQueryBuilder.fromIterable(()->new RawParents(type)).split(Class::getInterfaces))
                .result()
                .map(i -> FClass.newClass(c,i));
        }
    }
    
    private CQuery<IField> getRFields(AHints hints) {
        IContext c = context();
        if(hints.hintDeclared()) {
            return CQueryBuilder
                .fromArray(type.getDeclaredFields())
                .map(f -> FField.newField(true, c, f));
        } else if(hints.hintAny()) {
            return CQueryBuilder
                .fromIterable(() -> new RawAncestors(type))
                .split(Class::getDeclaredFields)
                .map(f -> FField.newField(isdecl(f), c, f));
        } else {
            return CQueryBuilder
                .fromArray(type.getFields())
                .map(f -> FField.newField(isdecl(f), c, f));
        }
    }
    
    private Annotation[] getRAnnotations(AHints hints) {
        if(hints.require().contains(IAttribute.DECLARED)) {
            return type.getDeclaredAnnotations();
        } else {
            return type.getAnnotations();
        }
    }
    
    
    private static class RawParents extends ForwardIterator<Class<?>> {

        private Class<?> now;

        public RawParents(Class<?> type) {
            this.now = type;
        }

        @Override
        public boolean next(Consumer<? super Class<?>> action) {
            if(now == null) {
                return false;
            }
            action.accept(now);
            now = now.getSuperclass();
            return true;
        }

    }

    private static class RawAncestors extends ForwardIterator<Class<?>> {

        Class<?> clazz;
        Class<?>[] faces;
        int position;

        public RawAncestors(Class<?> clazz) {
            this.clazz = clazz;
            this.faces = clazz.getInterfaces();
            this.position = -1;
        }

        @Override
        public boolean next(Consumer<? super Class<?>> action) {
            if(clazz == null) {
                return false;
            }
            if(position >= faces.length && clazz.getSuperclass()==null) {
                return false;
            }
            if(position == -1) {
                position = 0;
                action.accept(clazz);
                return true;
            }
            if(position >= faces.length) {
                clazz = clazz.getSuperclass();
                if(clazz != null) {
                    faces = clazz.getInterfaces();
                    position = 0;
                }
                action.accept(clazz);
                return true;
            }
            action.accept(faces[position++]);
            return true;
        }

    }
    
}
