package io.leangen.graphql.generator.mapping.common;

import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeFactory;
import io.leangen.graphql.execution.ResolutionEnvironment;
import io.leangen.graphql.generator.mapping.DelegatingOutputConverter;
import io.leangen.graphql.util.ClassUtils;
import io.leangen.graphql.util.Scalars;

import java.lang.reflect.AnnotatedArrayType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Array;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;

/**
 * @author Bojan Tomic (kaqqao)
 */
@SuppressWarnings("rawtypes")
public class ArrayAdapter extends AbstractTypeSubstitutingMapper implements DelegatingOutputConverter {

    @Override
    public AnnotatedType getSubstituteType(AnnotatedType original) {
        AnnotatedType component = ((AnnotatedArrayType) original).getAnnotatedGenericComponentType();
        Class<?> raw = ClassUtils.getRawType(component.getType());
        if (raw.isPrimitive()) {
            component = GenericTypeReflector.annotate(GenericTypeReflector.box(raw), component.getAnnotations());
        }
        return TypeFactory.parameterizedAnnotatedClass(List.class, original.getAnnotations(), component);
    }

    @Override
    public Object convertOutput(Object original, AnnotatedType type, ResolutionEnvironment env) {
        return IntStream.range(0, Array.getLength(original))
                .mapToObj(i -> env.convertOutput(Array.get(original, i), env.resolver.getTypedElement(), getElementType(type)))
                .toArray();
    }

    @Override
    public List<AnnotatedType> getDerivedTypes(AnnotatedType type) {
        return Collections.singletonList(getElementType(type));
    }

    @Override
    public boolean isTransparent() {
        return true;
    }

    @Override
    public boolean supports(AnnotatedElement element, AnnotatedType type) {
        return !Scalars.isScalar(type.getType()) && type instanceof AnnotatedArrayType;
    }

    private AnnotatedType getElementType(AnnotatedType arrayType) {
        return ((AnnotatedArrayType) arrayType).getAnnotatedGenericComponentType();
    }
}