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

package org.bson.codecs.pojo;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import java.util.Set;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Collections;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.TreeSet;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import org.bson.assertions.Assertions;

final class PojoBuilderHelper
{
    static <T> void configureClassModelBuilder(final ClassModelBuilder<T> classModelBuilder, final Class<T> clazz) {
        classModelBuilder.type(Assertions.notNull("clazz", clazz));
        final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
        final Set<String> propertyNames = new TreeSet<String>();
        final Map<String, TypeParameterMap> propertyTypeParameterMap = new HashMap<String, TypeParameterMap>();
        Class<? super T> currentClass = clazz;
        final String declaringClassName = clazz.getSimpleName();
        TypeData<?> parentClassTypeData = null;
        final Map<String, PropertyMetadata<?>> propertyNameMap = new HashMap<String, PropertyMetadata<?>>();
        while (!currentClass.isEnum() && currentClass.getSuperclass() != null) {
            annotations.addAll(Arrays.asList(currentClass.getDeclaredAnnotations()));
            final List<String> genericTypeNames = new ArrayList<String>();
            for (final TypeVariable<? extends Class<? super T>> classTypeVariable : currentClass.getTypeParameters()) {
                genericTypeNames.add(classTypeVariable.getName());
            }
            final PropertyReflectionUtils.PropertyMethods propertyMethods = PropertyReflectionUtils.getPropertyMethods(currentClass);
            for (final Method method : propertyMethods.getSetterMethods()) {
                final String propertyName = PropertyReflectionUtils.toPropertyName(method);
                propertyNames.add(propertyName);
                final PropertyMetadata<?> propertyMetadata = getOrCreateMethodPropertyMetadata(propertyName, declaringClassName, propertyNameMap, TypeData.newInstance(method), propertyTypeParameterMap, parentClassTypeData, genericTypeNames, getGenericType(method));
                if (propertyMetadata.getSetter() == null) {
                    propertyMetadata.setSetter(method);
                    for (final Annotation annotation : method.getDeclaredAnnotations()) {
                        propertyMetadata.addWriteAnnotation(annotation);
                    }
                }
            }
            for (final Method method : propertyMethods.getGetterMethods()) {
                final String propertyName = PropertyReflectionUtils.toPropertyName(method);
                propertyNames.add(propertyName);
                PropertyMetadata<?> propertyMetadata = propertyNameMap.get(propertyName);
                if (propertyMetadata != null && propertyMetadata.getGetter() != null) {
                    continue;
                }
                propertyMetadata = getOrCreateMethodPropertyMetadata(propertyName, declaringClassName, propertyNameMap, TypeData.newInstance(method), propertyTypeParameterMap, parentClassTypeData, genericTypeNames, getGenericType(method));
                if (propertyMetadata.getGetter() != null) {
                    continue;
                }
                propertyMetadata.setGetter(method);
                for (final Annotation annotation : method.getDeclaredAnnotations()) {
                    propertyMetadata.addReadAnnotation(annotation);
                }
            }
            for (final Field field : currentClass.getDeclaredFields()) {
                propertyNames.add(field.getName());
                final PropertyMetadata<?> propertyMetadata2 = getOrCreateFieldPropertyMetadata(field.getName(), declaringClassName, propertyNameMap, TypeData.newInstance(field), propertyTypeParameterMap, parentClassTypeData, genericTypeNames, field.getGenericType());
                if (propertyMetadata2 != null && propertyMetadata2.getField() == null) {
                    propertyMetadata2.field(field);
                    for (final Annotation annotation2 : field.getDeclaredAnnotations()) {
                        propertyMetadata2.addReadAnnotation(annotation2);
                        propertyMetadata2.addWriteAnnotation(annotation2);
                    }
                }
            }
            parentClassTypeData = TypeData.newInstance(currentClass.getGenericSuperclass(), (Class<?>)currentClass);
            currentClass = currentClass.getSuperclass();
        }
        if (currentClass.isInterface()) {
            annotations.addAll(Arrays.asList(currentClass.getDeclaredAnnotations()));
        }
        for (final String propertyName2 : propertyNames) {
            final PropertyMetadata<?> propertyMetadata3 = propertyNameMap.get(propertyName2);
            if (propertyMetadata3.isSerializable() || propertyMetadata3.isDeserializable()) {
                classModelBuilder.addProperty(createPropertyModelBuilder(propertyMetadata3));
            }
        }
        Collections.reverse(annotations);
        classModelBuilder.annotations(annotations);
        classModelBuilder.propertyNameToTypeParameterMap(propertyTypeParameterMap);
        Constructor<T> noArgsConstructor = null;
        for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
            if (constructor.getParameterTypes().length == 0 && (Modifier.isPublic(constructor.getModifiers()) || Modifier.isProtected(constructor.getModifiers()))) {
                noArgsConstructor = (Constructor<T>)constructor;
                noArgsConstructor.setAccessible(true);
            }
        }
        classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl<T>(new CreatorExecutable<T>(clazz, noArgsConstructor)));
    }
    
    private static <T, S> PropertyMetadata<T> getOrCreateMethodPropertyMetadata(final String propertyName, final String declaringClassName, final Map<String, PropertyMetadata<?>> propertyNameMap, final TypeData<T> typeData, final Map<String, TypeParameterMap> propertyTypeParameterMap, final TypeData<S> parentClassTypeData, final List<String> genericTypeNames, final Type genericType) {
        final PropertyMetadata<T> propertyMetadata = getOrCreatePropertyMetadata(propertyName, declaringClassName, propertyNameMap, typeData);
        if (!isAssignableClass(propertyMetadata.getTypeData().getType(), typeData.getType())) {
            propertyMetadata.setError(String.format("Property '%s' in %s, has differing data types: %s and %s.", propertyName, declaringClassName, propertyMetadata.getTypeData(), typeData));
        }
        cachePropertyTypeData(propertyMetadata, propertyTypeParameterMap, parentClassTypeData, genericTypeNames, genericType);
        return propertyMetadata;
    }
    
    private static boolean isAssignableClass(final Class<?> propertyTypeClass, final Class<?> typeDataClass) {
        Assertions.notNull("propertyTypeClass", propertyTypeClass);
        Assertions.notNull("typeDataClass", typeDataClass);
        return propertyTypeClass.isAssignableFrom(typeDataClass) || typeDataClass.isAssignableFrom(propertyTypeClass);
    }
    
    private static <T, S> PropertyMetadata<T> getOrCreateFieldPropertyMetadata(final String propertyName, final String declaringClassName, final Map<String, PropertyMetadata<?>> propertyNameMap, final TypeData<T> typeData, final Map<String, TypeParameterMap> propertyTypeParameterMap, final TypeData<S> parentClassTypeData, final List<String> genericTypeNames, final Type genericType) {
        final PropertyMetadata<T> propertyMetadata = getOrCreatePropertyMetadata(propertyName, declaringClassName, propertyNameMap, typeData);
        if (!propertyMetadata.getTypeData().getType().isAssignableFrom(typeData.getType())) {
            return null;
        }
        cachePropertyTypeData(propertyMetadata, propertyTypeParameterMap, parentClassTypeData, genericTypeNames, genericType);
        return propertyMetadata;
    }
    
    private static <T> PropertyMetadata<T> getOrCreatePropertyMetadata(final String propertyName, final String declaringClassName, final Map<String, PropertyMetadata<?>> propertyNameMap, final TypeData<T> typeData) {
        PropertyMetadata<T> propertyMetadata = (PropertyMetadata<T>)propertyNameMap.get(propertyName);
        if (propertyMetadata == null) {
            propertyMetadata = new PropertyMetadata<T>(propertyName, declaringClassName, typeData);
            propertyNameMap.put(propertyName, propertyMetadata);
        }
        return propertyMetadata;
    }
    
    private static <T, S> void cachePropertyTypeData(final PropertyMetadata<T> propertyMetadata, final Map<String, TypeParameterMap> propertyTypeParameterMap, final TypeData<S> parentClassTypeData, final List<String> genericTypeNames, final Type genericType) {
        final TypeParameterMap typeParameterMap = getTypeParameterMap(genericTypeNames, genericType);
        propertyTypeParameterMap.put(propertyMetadata.getName(), typeParameterMap);
        propertyMetadata.typeParameterInfo(typeParameterMap, parentClassTypeData);
    }
    
    private static Type getGenericType(final Method method) {
        return PropertyReflectionUtils.isGetter(method) ? method.getGenericReturnType() : method.getGenericParameterTypes()[0];
    }
    
    static <T> PropertyModelBuilder<T> createPropertyModelBuilder(final PropertyMetadata<T> propertyMetadata) {
        final PropertyModelBuilder<T> propertyModelBuilder = PropertyModel.builder().propertyName(propertyMetadata.getName()).readName(propertyMetadata.getName()).writeName(propertyMetadata.getName()).typeData(propertyMetadata.getTypeData()).readAnnotations(propertyMetadata.getReadAnnotations()).writeAnnotations(propertyMetadata.getWriteAnnotations()).propertySerialization(new PropertyModelSerializationImpl<T>()).propertyAccessor(new PropertyAccessorImpl<T>(propertyMetadata)).setError(propertyMetadata.getError());
        if (propertyMetadata.getTypeParameters() != null) {
            propertyModelBuilder.typeData(PojoSpecializationHelper.specializeTypeData(propertyModelBuilder.getTypeData(), propertyMetadata.getTypeParameters(), propertyMetadata.getTypeParameterMap()));
        }
        return propertyModelBuilder;
    }
    
    private static TypeParameterMap getTypeParameterMap(final List<String> genericTypeNames, final Type propertyType) {
        int classParamIndex = genericTypeNames.indexOf(propertyType.toString());
        final TypeParameterMap.Builder builder = TypeParameterMap.builder();
        if (classParamIndex != -1) {
            builder.addIndex(classParamIndex);
        }
        else if (propertyType instanceof ParameterizedType) {
            final ParameterizedType pt = (ParameterizedType)propertyType;
            for (int i = 0; i < pt.getActualTypeArguments().length; ++i) {
                classParamIndex = genericTypeNames.indexOf(pt.getActualTypeArguments()[i].toString());
                if (classParamIndex != -1) {
                    builder.addIndex(i, classParamIndex);
                }
                else {
                    builder.addIndex(i, getTypeParameterMap(genericTypeNames, pt.getActualTypeArguments()[i]));
                }
            }
        }
        return builder.build();
    }
    
    static <V> V stateNotNull(final String property, final V value) {
        if (value == null) {
            throw new IllegalStateException(String.format("%s cannot be null", property));
        }
        return value;
    }
    
    private PojoBuilderHelper() {
    }
}
