package com.nfl.glitr.registry.type;

import graphql.schema.GraphQLInputType;
import graphql.schema.GraphQLOutputType;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Top level factory, responsible for holding the delegate factories
 */
public class GraphQLTypeFactory {

    private final Map<JavaType, DelegateTypeFactory> delegateFactories = new HashMap<>();
    private final Map<JavaType, DelegateTypeFactory> delegateInputFactories = new HashMap<>();


    /**
     * Add a {@link DelegateTypeFactory} (mapped by {@link JavaType} as the key) to delegateFactories
     *
     * @param factory {@link DelegateTypeFactory} that can create output types
     * @param types {@link JavaType}(s) to be resolved by given factory
     * @return {@link GraphQLTypeFactory}
     */
    public GraphQLTypeFactory withOutputTypeFactory(final DelegateTypeFactory factory, JavaType... types) {
        Map<JavaType, DelegateTypeFactory> factories = Arrays
                .stream(types)
                .collect(Collectors.toMap(type -> type, type -> factory));
        delegateFactories.putAll(factories);
        return this;
    }

    /**
     * Add a {@link DelegateTypeFactory} (mapped by {@link JavaType} as the key) to delegateInputFactories
     *
     * @param factory {@link DelegateTypeFactory} that can create input types
     * @param types {@link JavaType}(s) to be resolved by given factory
     * @return {@link GraphQLTypeFactory}
     */
    public GraphQLTypeFactory withInputTypeFactory(final DelegateTypeFactory factory, JavaType... types) {
        Map<JavaType, DelegateTypeFactory> factories = Arrays
                .stream(types)
                .collect(Collectors.toMap(type -> type, type -> factory));
        delegateInputFactories.putAll(factories);
        return this;
    }

    /**
     * Creates a {@link GraphQLOutputType} by inspecting the given a class
     *
     * @param clazz to be evaluated
     * @return {@link GraphQLOutputType}
     */
    public GraphQLOutputType createGraphQLOutputType(Class clazz) {
        JavaType javaType = getJavaTypeFromClass(clazz);
        return (GraphQLOutputType) delegateFactories.get(javaType).create(clazz);
    }

    /**
     * Creates a {@link GraphQLInputType} by inspecting the given a class
     *
     * @param clazz to be evaluated
     * @return {@link GraphQLInputType}}
     */
    public GraphQLInputType createGraphQLInputType(Class clazz) {
        JavaType javaType = getJavaTypeFromClass(clazz);
        if (javaType == JavaType.ENUM) {
            return (GraphQLInputType) delegateFactories.get(JavaType.ENUM).create(clazz);
        }
        return (GraphQLInputType) delegateInputFactories.get(javaType).create(clazz);
    }

    public static JavaType getJavaTypeFromClass(Class clazz) {
        JavaType javaType;
        if (clazz.isEnum()) {
            javaType = JavaType.ENUM;
        } else if (clazz.isInterface()) {
            javaType = JavaType.INTERFACE;
        } else if (Modifier.isAbstract(clazz.getModifiers())) {
            javaType = JavaType.ABSTRACT_CLASS;
        } else {
            javaType = JavaType.CLASS;
        }
        return javaType;
    }
}