package com.github.kongchen.swagger.docgen.reader;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.kongchen.swagger.docgen.GenerateException;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.converter.ModelConverter;
import io.swagger.converter.ModelConverterContext;
import io.swagger.jackson.ModelResolver;
import io.swagger.models.Model;
import io.swagger.models.properties.Property;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author chekong on 15/5/19.
 */
public class ModelModifier extends ModelResolver {
    private Map<Type, Type> modelSubtitutes = new HashMap<Type, Type>();
    private List<String> apiModelPropertyAccessExclusions = new ArrayList<String>();

    public ModelModifier(ObjectMapper mapper) {
        super(mapper);
    }

    public void addModelSubstitute(String fromClass, String toClass) throws GenerateException {
        try {
            Type type = _mapper.constructType(Class.forName(fromClass));
            Type toType = _mapper.constructType(Class.forName(toClass));

            modelSubtitutes.put(type, toType);

        } catch (ClassNotFoundException e) {
            throw new GenerateException(e);
        }
    }

    public List<String> getApiModelPropertyAccessExclusions() {
        return apiModelPropertyAccessExclusions;
    }

    public void setApiModelPropertyAccessExclusions(List<String> apiModelPropertyAccessExclusions) {
        this.apiModelPropertyAccessExclusions = apiModelPropertyAccessExclusions;
    }

    @Override
    public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations, Iterator<ModelConverter> chain) {
        if (modelSubtitutes.containsKey(type)) {
            return super.resolveProperty(modelSubtitutes.get(type), context, annotations, chain);
        } else if (chain.hasNext()) {
            return chain.next().resolveProperty(type, context, annotations, chain);
        } else {
            return super.resolveProperty(type, context, annotations, chain);
        }

    }

    @Override
    public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) {
        if (modelSubtitutes.containsKey(type)) {
            return super.resolve(modelSubtitutes.get(type), context, chain);
        } else {
            return super.resolve(type, context, chain);
        }
    }

    @Override
    public Model resolve(JavaType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
        Model model = super.resolve(type, context, chain);

        // If there are no @ApiModelPropety exclusions configured, return the untouched model
        if (apiModelPropertyAccessExclusions == null || apiModelPropertyAccessExclusions.isEmpty()) {
            return model;
        }

        Class<?> cls = type.getRawClass();

        for (Method method : cls.getDeclaredMethods()) {
            ApiModelProperty apiModelPropertyAnnotation = AnnotationUtils.findAnnotation(method, ApiModelProperty.class);

            if (apiModelPropertyAnnotation == null) {
                continue;
            }

            String apiModelPropertyAccess = apiModelPropertyAnnotation.access();
            String apiModelPropertyName = apiModelPropertyAnnotation.name();

            // If the @ApiModelProperty is not populated with both #name and #access, skip it
            if (apiModelPropertyAccess.isEmpty() || apiModelPropertyName.isEmpty()) {
                continue;
            }

            // Check to see if the value of @ApiModelProperty#access is one to exclude.
            // If so, remove it from the previously-calculated model.
            if (apiModelPropertyAccessExclusions.contains(apiModelPropertyAccess)) {
                model.getProperties().remove(apiModelPropertyName);
            }
        }

        return model;
    }
}

@@ -862,30 +862,38 @@
 private Map%3C
+Java
 Type, 
+Java
 Type%3E modelS
@@ -916,22 +916,30 @@
 HashMap%3C
+Java
 Type, 
+Java
 Type%3E();
@@ -1217,28 +1217,29 @@
 y %7B%0A        
-    
+%09Java
 Type type = 
@@ -1299,16 +1299,20 @@
         
+Java
 Type toT
@@ -1973,32 +1973,148 @@
 erter%3E chain) %7B%0A
+    %09// for method parameter types we get here Type but we need JavaType%0A    %09JavaType javaType = toJavaType(type);%0A
         if (mode
@@ -2129,33 +2129,37 @@
 tes.containsKey(
-t
+javaT
 ype)) %7B%0A        
@@ -2203,33 +2203,37 @@
 lSubtitutes.get(
-t
+javaT
 ype), context, a
@@ -2593,32 +2593,148 @@
 erter%3E chain) %7B%0A
+    %09// for method parameter types we get here Type but we need JavaType%0A    %09JavaType javaType = toJavaType(type);%0A
         if (mode
@@ -2757,17 +2757,21 @@
 ainsKey(
-t
+javaT
 ype)) %7B%0A
@@ -2823,17 +2823,21 @@
 tes.get(
-t
+javaT
 ype), co
@@ -4410,16 +4410,410 @@
  model;%0A
-
     %7D%0A
+    %0A    /**%0A     * Converts %7B@link Type%7D to %7B@link JavaType%7D.%0A     * @param type object to convert%0A     * @return object converted to %7B@link JavaType%7D%0A     */%0A    private JavaType toJavaType(Type type) %7B%0A%09%09JavaType typeToFind;%0A    %09if (type instanceof JavaType) %7B%0A    %09%09typeToFind = (JavaType) type;%0A    %09%7D else %7B%0A    %09%09typeToFind = _mapper.constructType(type);%0A    %09%7D%0A%09%09return typeToFind;%0A%09%7D%0A
 %7D%0A

Add support for Type to ModelModifier.