// 
// Decompiled by Procyon v0.6.0
// 

package org.bson.codecs.pojo;

import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.bson.assertions.Assertions;
import java.util.Map;
import java.util.List;

final class TypeData<T> implements TypeWithTypeParameters<T>
{
    private final Class<T> type;
    private final List<TypeData<?>> typeParameters;
    private static final Map<Class<?>, Class<?>> PRIMITIVE_CLASS_MAP;
    
    public static <T> Builder<T> builder(final Class<T> type) {
        return new Builder<T>((Class)Assertions.notNull("type", type));
    }
    
    public static TypeData<?> newInstance(final Method method) {
        if (PropertyReflectionUtils.isGetter(method)) {
            return newInstance(method.getGenericReturnType(), method.getReturnType());
        }
        return newInstance(method.getGenericParameterTypes()[0], method.getParameterTypes()[0]);
    }
    
    public static TypeData<?> newInstance(final Field field) {
        return newInstance(field.getGenericType(), field.getType());
    }
    
    public static <T> TypeData<T> newInstance(final Type genericType, final Class<T> clazz) {
        final Builder<T> builder = builder(clazz);
        if (genericType instanceof ParameterizedType) {
            final ParameterizedType pType = (ParameterizedType)genericType;
            for (final Type argType : pType.getActualTypeArguments()) {
                getNestedTypeData(builder, argType);
            }
        }
        return builder.build();
    }
    
    private static <T> void getNestedTypeData(final Builder<T> builder, final Type type) {
        if (type instanceof ParameterizedType) {
            final ParameterizedType pType = (ParameterizedType)type;
            final Builder paramBuilder = builder((Class<Object>)pType.getRawType());
            for (final Type argType : pType.getActualTypeArguments()) {
                getNestedTypeData((Builder<Object>)paramBuilder, argType);
            }
            builder.addTypeParameter(paramBuilder.build());
        }
        else if (type instanceof WildcardType) {
            builder.addTypeParameter((TypeData<Object>)builder((Class<S>)((WildcardType)type).getUpperBounds()[0]).build());
        }
        else if (type instanceof TypeVariable) {
            builder.addTypeParameter((TypeData<Object>)builder((Class<S>)Object.class).build());
        }
        else if (type instanceof Class) {
            builder.addTypeParameter((TypeData<Object>)builder((Class<S>)type).build());
        }
    }
    
    @Override
    public Class<T> getType() {
        return this.type;
    }
    
    @Override
    public List<TypeData<?>> getTypeParameters() {
        return this.typeParameters;
    }
    
    @Override
    public String toString() {
        final String typeParams = this.typeParameters.isEmpty() ? "" : (", typeParameters=[" + nestedTypeParameters(this.typeParameters) + "]");
        return "TypeData{type=" + this.type.getSimpleName() + typeParams + "}";
    }
    
    private static String nestedTypeParameters(final List<TypeData<?>> typeParameters) {
        final StringBuilder builder = new StringBuilder();
        int count = 0;
        final int last = typeParameters.size();
        for (final TypeData<?> typeParameter : typeParameters) {
            ++count;
            builder.append(typeParameter.getType().getSimpleName());
            if (!typeParameter.getTypeParameters().isEmpty()) {
                builder.append(String.format("<%s>", nestedTypeParameters(typeParameter.getTypeParameters())));
            }
            if (count < last) {
                builder.append(", ");
            }
        }
        return builder.toString();
    }
    
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof TypeData)) {
            return false;
        }
        final TypeData<?> that = (TypeData<?>)o;
        return this.getType().equals(that.getType()) && this.getTypeParameters().equals(that.getTypeParameters());
    }
    
    @Override
    public int hashCode() {
        int result = this.getType().hashCode();
        result = 31 * result + this.getTypeParameters().hashCode();
        return result;
    }
    
    private TypeData(final Class<T> type, final List<TypeData<?>> typeParameters) {
        this.type = this.boxType(type);
        this.typeParameters = typeParameters;
    }
    
    boolean isAssignableFrom(final Class<?> cls) {
        return this.type.isAssignableFrom(this.boxType(cls));
    }
    
    private <S> Class<S> boxType(final Class<S> clazz) {
        if (clazz.isPrimitive()) {
            return (Class)TypeData.PRIMITIVE_CLASS_MAP.get(clazz);
        }
        return clazz;
    }
    
    static {
        final Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();
        map.put(Boolean.TYPE, Boolean.class);
        map.put(Byte.TYPE, Byte.class);
        map.put(Character.TYPE, Character.class);
        map.put(Double.TYPE, Double.class);
        map.put(Float.TYPE, Float.class);
        map.put(Integer.TYPE, Integer.class);
        map.put(Long.TYPE, Long.class);
        map.put(Short.TYPE, Short.class);
        PRIMITIVE_CLASS_MAP = map;
    }
    
    public static final class Builder<T>
    {
        private final Class<T> type;
        private final List<TypeData<?>> typeParameters;
        
        private Builder(final Class<T> type) {
            this.typeParameters = new ArrayList<TypeData<?>>();
            this.type = type;
        }
        
        public <S> Builder<T> addTypeParameter(final TypeData<S> typeParameter) {
            this.typeParameters.add(Assertions.notNull("typeParameter", typeParameter));
            return this;
        }
        
        public Builder<T> addTypeParameters(final List<TypeData<?>> typeParameters) {
            Assertions.notNull("typeParameters", typeParameters);
            for (final TypeData<?> typeParameter : typeParameters) {
                this.addTypeParameter(typeParameter);
            }
            return this;
        }
        
        public TypeData<T> build() {
            return new TypeData<T>(this.type, Collections.unmodifiableList((List<?>)this.typeParameters), null);
        }
    }
}
