/*
 * @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 lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type;
import java.util.List;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.reflection.*;

/**
 * IClass implementation used to describe type of field.
 *
 * Information about type parameters defined by outer/parent classes are stored inside context.
 *
 * It uses additional information assigned to field (for example declared generic type, annotations)
 *
 * @param <T>
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class RFClass<T> extends AClass<T> implements Serializable {

    private final IClass<T> itype;
    private final AnnotatedType atype;

    RFClass(IContext context, IClass<T> itype, AnnotatedType atype) {
        super(context);
        this.itype = itype;
        this.atype = atype;
    }

    @Override
    public List<IClass<?>> params() {
        return itype.params();
    }

    @Override
    public IFields fields() {
        return itype.fields();
    }

    @Override
    public IConstructors<T> constructors() {
        return itype.constructors();
    }

    @Override
    public IMethods methods() {
        return itype.methods();
    }

    @Override
    public IClass<?> parent() {
        return itype.parent();
    }

    @Override
    public IClasses parents() {
        return itype.parents();
    }

    @Override
    public IClasses interfaces() {
        return itype.interfaces();
    }

    @Override
    public IClass<?> outer() {
        return itype.outer();
    }

    @Override
    public IClasses inner() {
        return itype.inner();
    }
    
    @Override
    public Type reflective() {
        return itype.reflective();
    }

    @Override
    public Class<T> raw() {
        return itype.raw();
    }

    @Override
    public boolean isInstance(Object value) {
        return itype.isInstance(value);
    }

    @Override
    public String name() {
        return itype.name();
    }
    
    @Override
    public final String shortName() {
        return itype.shortName();
    }

    @Override
    public IAttributes attributes() {
        return itype.attributes();
    }

    @Override
    public IAnnotations annotations() {
        AHints attr = FElements.newHints();
        CQuery<Annotation> query = CQuery.builder()
            .withQuery(itype.annotations().hint(attr).stream())
            .withArray(attr, this::getRFAnnotations)
            .build();
        return FElements.newAnnotations(attr, query);
    }
    
    protected Annotation[] getRFAnnotations(AHints hints) {
        if(hints.require().contains(IAttribute.DECLARED)) {
            return atype.getDeclaredAnnotations();
        } else {
            return atype.getAnnotations();
        }
    }

    protected IClass<?> $itype() {
        return itype;
    }

    protected Object writeReplace() {
        final IContext cContext = super.context();
        final IClass<?> ciType = this.itype;
        final AContainer cAnnotations = new AContainer(atype);

        return SerializationUtils.proxy(() -> FClass.newRFClass(cContext, ciType, cAnnotations));
    }

    @RequiredArgsConstructor
    private static class AContainer implements AnnotatedType, Serializable {

        private static final long serialVersionUID = 1L;

        private final Annotation[] annotations;
        private final Annotation[] declared;

        public AContainer(AnnotatedType atype) {
            this.annotations = atype.getAnnotations();
            this.declared = atype.getDeclaredAnnotations();
        }

        @Override
        public Type getType() {
            return null;
        }

        @Override
        public <T extends Annotation> T getAnnotation(@NonNull Class<T> annotation) {
            return getDeclaredAnnotation(annotation);
        }

        @Override
        public Annotation[] getAnnotations() {
            return annotations;
        }

        @Override
        public Annotation[] getDeclaredAnnotations() {
            return declared;
        }
    }
}
