package com.github.therapi.core;

import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.therapi.core.annotation.Default;
import com.github.therapi.core.internal.ParameterDefinition;

import static com.github.therapi.core.internal.JacksonHelper.getTypeReference;

public class StandardParameterIntrospector implements ParameterIntrospector {
    private final ObjectMapper objectMapper;

    public StandardParameterIntrospector(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public List<ParameterDefinition> findParameters(Method method, Object owner) {
        List<ParameterDefinition> params = new ArrayList<>();
        for (Parameter p : method.getParameters()) {
            TypeReference typeReference = getTypeReference(p, owner.getClass());
            params.add(new ParameterDefinition(getName(p), isNullable(p, method), getDefaultValueSupplier(p, typeReference), typeReference));
        }
        return params;
    }

    protected
    @Nullable
    Supplier<?> getDefaultValueSupplier(Parameter p, TypeReference typeReference) {
        Default defaultAnnotation = p.getAnnotation(Default.class);
        if (defaultAnnotation == null) {
            return null;
        }

        if (Default.NULL.equals(defaultAnnotation.value())) {
            return getDefaultValueSupplier(p.getType());
        }

        String defaultValueStr = defaultAnnotation.value();

        if (p.getType().equals(String.class)) {
            return () -> defaultValueStr;
        }

        return () -> objectMapper.convertValue(defaultValueStr, typeReference);
    }

    private Supplier<?> getDefaultValueSupplier(Class<?> type) {
        if (type == Boolean.TYPE) {
            return () -> false;
        }
        if (type == Integer.TYPE) {
            return () -> 0;
        }
        if (type == Long.TYPE) {
            return () -> 0L;
        }
        if (type == Character.TYPE) {
            return () -> '\0';
        }
        if (type == Short.TYPE) {
            return () -> (short) 0;
        }
        if (type == Byte.TYPE) {
            return () -> (byte) 0;
        }
        if (type == Double.TYPE) {
            return () -> (double) 0;
        }
        if (type == Float.TYPE) {
            return () -> (float) 0;
        }
        return () -> null;
    }

    protected String getName(Parameter p) {
        return p.getName();
    }

    protected boolean isNullable(Parameter p, Method method) {
        if (p.getAnnotation(Nullable.class) != null) {
            if (p.getType().isPrimitive()) {
                throw new InvalidAnnotationException("Annotation " + Nullable.class.getName() + " may not be applied to primitive " + p.getType() + " parameter '" + p.getName() + "' of method: " + method);
            }

            return true;
        }

        Default defaultAnnontation = p.getAnnotation(Default.class);
        return defaultAnnontation != null && Default.NULL.equals(defaultAnnontation.value());
    }
}

@@ -1877,21 +1877,30 @@
 %0A%0A    pr
-ivate
+otected static
  Supplie
@@ -1969,20 +1969,21 @@
  == 
-B
+b
 oolean.
-TYPE
+class
 ) %7B%0A
@@ -2048,20 +2048,17 @@
  == 
-Integer.TYPE
+int.class
 ) %7B%0A
@@ -2119,17 +2119,18 @@
  == 
-L
+l
 ong.
-TYPE
+class
 ) %7B%0A
@@ -2192,22 +2192,18 @@
  == 
-Character.TYPE
+char.class
 ) %7B%0A
@@ -2267,18 +2267,19 @@
  == 
-S
+s
 hort.
-TYPE
+class
 ) %7B%0A
@@ -2348,17 +2348,18 @@
  == 
-B
+b
 yte.
-TYPE
+class
 ) %7B%0A
@@ -2427,19 +2427,20 @@
  == 
-D
+d
 ouble.
-TYPE
+class
 ) %7B%0A
@@ -2468,18 +2468,10 @@
  -%3E 
-(double) 
 0
+d
 ;%0A  
@@ -2502,18 +2502,19 @@
  == 
-F
+f
 loat.
-TYPE
+class
 ) %7B%0A
@@ -2542,17 +2542,10 @@
  -%3E 
-(float) 
 0
+f
 ;%0A  

use <primitive>.class notation