/*
 * @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.lists.ListUtils;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.generic.LazyCaller;
import net.ranides.assira.generic.LazyCallers;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.reflection.*;
import net.ranides.assira.reflection.util.ReflectUtils;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

/**
 * Implementation of IClass using reflective data resolution from raw class.
 *
 * It collects as much reflective data as possible, trying to bypass type erasure.
 *
 * Information about type parameters defined by outer/parent classes are stored inside context.
 *
 * @param <T> T
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class RClass<T> extends AClass<T> implements Serializable {

    private final Class<T> type;

    private final LazyCaller<IAttributes> iattributes = LazyCallers.concurrent();

    RClass(IContext context, Class<T> type) {
        super(context.derive(type));
        this.type = type;
    }
    
    @Override
    public List<IClass<?>> params() {
        IContext c = context();
        return ListUtils.map(Arrays.asList(type.getTypeParameters()), c::typeinfo);
    }

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

    @SuppressWarnings("unchecked")
    @Override
    public final IConstructors<T> constructors() {
        AHints attr = FElements.newHints();
        return FElements.newConstructors(attr, CQuery.from().query(attr, this::getRConstructors));
    }

    @Override
    public final IMethods methods() {
        AHints attr = FElements.newHints();
        return FElements.newMethods(attr, CQuery.from().query(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 IClasses.empty();
        }
        AHints attr = FElements.newHints();
        return FElements.newClasses(attr, CQuery.from().iterable(()->new RParents(attr)));
    }

    @Override
    public final IClasses interfaces() {
        AHints attr = FElements.newHints();
        return FElements.newClasses(attr, CQuery.from().query(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<T> 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 IAttributes attributes() {
        return iattributes.computeIfAbsent(this::iattributes);
    }

    protected IAttributes iattributes() {
        IAttributes attrs = IAttributes.of(type);
        if (!attrs.contains(IAttribute.RESOLVED) && params().stream().allMatch(IClass::isResolved)) {
            attrs.add(IAttribute.RESOLVED);
        }
        return attrs;
    }

    @Override
    public final IAnnotations annotations() {
        AHints attr = FElements.newHints();
        return FElements.newAnnotations(attr, CQuery.from().array(attr, this::getRAnnotations));
    }
    
    private CQuery<IMethod> getRMethods(AHints hints) {
        IContext c = context();
        if(hints.isDeclaredHint()) {
            return CQuery.from().array(type.getDeclaredMethods()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newMethod(true, c, m));
        } else if(hints.isInheritedHint()) {
            return CQuery.from()
                .iterable(() -> new RawAncestors(type)).flatArray(Class::getDeclaredMethods)
                .filter(ReflectUtils::isAccessible)
                .map(m -> FMethod.newMethod(isdecl(m), c, m));
        } else {
            return CQuery.from().array(type.getMethods()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newMethod(isdecl(m), c, m));
        }
    }
    
    private boolean isdecl(Member m) {
        return type.equals(m.getDeclaringClass());
    }

    private CQuery getRConstructors(AHints hints) {
        IContext c = context();
        if(hints.isInheritedHint() || hints.isDeclaredHint()) {
            return CQuery.from().array(type.getDeclaredConstructors()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newConstructor(c,m));
        } else {
            return CQuery.from().array(type.getConstructors()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newConstructor(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) || now.attributes().contains(IAttribute.INTERFACE)) {
                return false;
            }
            now = (RClass)now.context().typeinfo(now.type.getSuperclass());
            action.accept(now);
            if(now==OBJECT || hints.isDeclaredHint()) { // NOPMD
                now = null;
            }
            return true;
        }

    }
    
    private CQuery<IClass<?>> getRInterfaces(AHints hints) {
        CQuery<IClass<?>> declared = CQuery.from().array(type.getGenericInterfaces()).map(context()::typeinfo);

        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 CQuery<IField> getRFields(AHints hints) {
        IContext c = context();
        if(hints.isDeclaredHint()) {
            return CQuery.from()
                .array(type.getDeclaredFields())
                .filter(ReflectUtils::isAccessible)
                .map(f -> FField.newField(true, c, f));
        } else if(hints.isInheritedHint()) {
            return CQuery.from()
                .iterable(() -> new RawAncestors(type)).flatArray(Class::getDeclaredFields)
                .filter(ReflectUtils::isAccessible)
                .map(f -> FField.newField(isdecl(f), c, f));
        } else {
            return CQuery.from()
                .array(type.getFields())
                .filter(ReflectUtils::isAccessible)
                .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();
        }
    }

    protected Object writeReplace() {
        final IContext cContext = super.context();
        final Class<T> cType = this.type;
        return SerializationUtils.proxy(() -> FClass.newClass(cContext, cType));
    }
    
    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;
        }

    }
    
}
