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

package org.bson.codecs.pojo;

import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.Constructor;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.pojo.annotations.BsonCreator;
import java.lang.reflect.Modifier;
import org.bson.BsonType;
import org.bson.codecs.pojo.annotations.BsonRepresentation;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.bson.codecs.pojo.annotations.BsonId;
import org.bson.codecs.pojo.annotations.BsonProperty;
import org.bson.codecs.pojo.annotations.BsonDiscriminator;
import java.util.Iterator;
import java.lang.annotation.Annotation;

final class ConventionAnnotationImpl implements Convention
{
    @Override
    public void apply(final ClassModelBuilder<?> classModelBuilder) {
        for (final Annotation annotation : classModelBuilder.getAnnotations()) {
            this.processClassAnnotation(classModelBuilder, annotation);
        }
        for (final PropertyModelBuilder<?> propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) {
            this.processPropertyAnnotations(classModelBuilder, propertyModelBuilder);
        }
        this.processCreatorAnnotation(classModelBuilder);
        this.cleanPropertyBuilders(classModelBuilder);
    }
    
    private void processClassAnnotation(final ClassModelBuilder<?> classModelBuilder, final Annotation annotation) {
        if (annotation instanceof BsonDiscriminator) {
            final BsonDiscriminator discriminator = (BsonDiscriminator)annotation;
            final String key = discriminator.key();
            if (!key.equals("")) {
                classModelBuilder.discriminatorKey(key);
            }
            final String name = discriminator.value();
            if (!name.equals("")) {
                classModelBuilder.discriminator(name);
            }
            classModelBuilder.enableDiscriminator(true);
        }
    }
    
    private void processPropertyAnnotations(final ClassModelBuilder<?> classModelBuilder, final PropertyModelBuilder<?> propertyModelBuilder) {
        for (final Annotation annotation : propertyModelBuilder.getReadAnnotations()) {
            if (annotation instanceof BsonProperty) {
                final BsonProperty bsonProperty = (BsonProperty)annotation;
                if (!"".equals(bsonProperty.value())) {
                    propertyModelBuilder.readName(bsonProperty.value());
                }
                propertyModelBuilder.discriminatorEnabled(bsonProperty.useDiscriminator());
                if (!propertyModelBuilder.getName().equals(classModelBuilder.getIdPropertyName())) {
                    continue;
                }
                classModelBuilder.idPropertyName(null);
            }
            else if (annotation instanceof BsonId) {
                classModelBuilder.idPropertyName(propertyModelBuilder.getName());
            }
            else if (annotation instanceof BsonIgnore) {
                propertyModelBuilder.readName(null);
            }
            else {
                if (!(annotation instanceof BsonRepresentation)) {
                    continue;
                }
                final BsonRepresentation bsonRepresentation = (BsonRepresentation)annotation;
                final BsonType bsonRep = bsonRepresentation.value();
                propertyModelBuilder.bsonRepresentation(bsonRep);
            }
        }
        for (final Annotation annotation : propertyModelBuilder.getWriteAnnotations()) {
            if (annotation instanceof BsonProperty) {
                final BsonProperty bsonProperty = (BsonProperty)annotation;
                if ("".equals(bsonProperty.value())) {
                    continue;
                }
                propertyModelBuilder.writeName(bsonProperty.value());
            }
            else {
                if (!(annotation instanceof BsonIgnore)) {
                    continue;
                }
                propertyModelBuilder.writeName(null);
            }
        }
    }
    
    private <T> void processCreatorAnnotation(final ClassModelBuilder<T> classModelBuilder) {
        final Class<T> clazz = classModelBuilder.getType();
        CreatorExecutable<T> creatorExecutable = null;
        for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
            if (Modifier.isPublic(constructor.getModifiers()) && !constructor.isSynthetic()) {
                for (final Annotation annotation : constructor.getDeclaredAnnotations()) {
                    if (annotation.annotationType().equals(BsonCreator.class)) {
                        if (creatorExecutable != null) {
                            throw new CodecConfigurationException("Found multiple constructors annotated with @BsonCreator");
                        }
                        creatorExecutable = new CreatorExecutable<T>(clazz, (Constructor<T>)constructor);
                    }
                }
            }
        }
        Class<?> bsonCreatorClass = clazz;
        for (boolean foundStaticBsonCreatorMethod = false; bsonCreatorClass != null && !foundStaticBsonCreatorMethod; bsonCreatorClass = bsonCreatorClass.getSuperclass()) {
            for (final Method method : bsonCreatorClass.getDeclaredMethods()) {
                if (Modifier.isStatic(method.getModifiers()) && !method.isSynthetic() && !method.isBridge()) {
                    for (final Annotation annotation2 : method.getDeclaredAnnotations()) {
                        if (annotation2.annotationType().equals(BsonCreator.class)) {
                            if (creatorExecutable != null) {
                                throw new CodecConfigurationException("Found multiple constructors / methods annotated with @BsonCreator");
                            }
                            if (!bsonCreatorClass.isAssignableFrom(method.getReturnType())) {
                                throw new CodecConfigurationException(String.format("Invalid method annotated with @BsonCreator. Returns '%s', expected %s", method.getReturnType(), bsonCreatorClass));
                            }
                            creatorExecutable = new CreatorExecutable<T>(clazz, method);
                            foundStaticBsonCreatorMethod = true;
                        }
                    }
                }
            }
        }
        if (creatorExecutable != null) {
            final List<BsonProperty> properties = creatorExecutable.getProperties();
            final List<Class<?>> parameterTypes = creatorExecutable.getParameterTypes();
            final List<Type> parameterGenericTypes = creatorExecutable.getParameterGenericTypes();
            if (properties.size() != parameterTypes.size()) {
                throw creatorExecutable.getError(clazz, "All parameters in the @BsonCreator method / constructor must be annotated with a @BsonProperty.");
            }
            for (int i = 0; i < properties.size(); ++i) {
                final boolean isIdProperty = creatorExecutable.getIdPropertyIndex() != null && creatorExecutable.getIdPropertyIndex().equals(i);
                final Class<?> parameterType = parameterTypes.get(i);
                final Type genericType = parameterGenericTypes.get(i);
                PropertyModelBuilder<?> propertyModelBuilder = null;
                if (isIdProperty) {
                    propertyModelBuilder = classModelBuilder.getProperty(classModelBuilder.getIdPropertyName());
                }
                else {
                    final BsonProperty bsonProperty = properties.get(i);
                    for (final PropertyModelBuilder<?> builder : classModelBuilder.getPropertyModelBuilders()) {
                        if (bsonProperty.value().equals(builder.getWriteName())) {
                            propertyModelBuilder = builder;
                            break;
                        }
                        if (!bsonProperty.value().equals(builder.getReadName())) {
                            continue;
                        }
                        propertyModelBuilder = builder;
                    }
                    if (propertyModelBuilder == null) {
                        propertyModelBuilder = classModelBuilder.getProperty(bsonProperty.value());
                    }
                    if (propertyModelBuilder == null) {
                        propertyModelBuilder = this.addCreatorPropertyToClassModelBuilder(classModelBuilder, bsonProperty.value(), parameterType);
                    }
                    else {
                        if (!bsonProperty.value().equals(propertyModelBuilder.getName())) {
                            propertyModelBuilder.writeName(bsonProperty.value());
                        }
                        tryToExpandToGenericType(parameterType, propertyModelBuilder, genericType);
                    }
                }
                if (!propertyModelBuilder.getTypeData().isAssignableFrom(parameterType)) {
                    throw creatorExecutable.getError(clazz, String.format("Invalid Property type for '%s'. Expected %s, found %s.", propertyModelBuilder.getWriteName(), propertyModelBuilder.getTypeData().getType(), parameterType));
                }
            }
            classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl<T>(creatorExecutable));
        }
    }
    
    private static <T> void tryToExpandToGenericType(final Class<?> parameterType, final PropertyModelBuilder<T> propertyModelBuilder, final Type genericType) {
        if (parameterType.isAssignableFrom(propertyModelBuilder.getTypeData().getType())) {
            propertyModelBuilder.typeData(TypeData.newInstance(genericType, parameterType));
        }
    }
    
    private <T, S> PropertyModelBuilder<S> addCreatorPropertyToClassModelBuilder(final ClassModelBuilder<T> classModelBuilder, final String name, final Class<S> clazz) {
        final PropertyModelBuilder<S> propertyModelBuilder = PojoBuilderHelper.createPropertyModelBuilder(new PropertyMetadata<S>(name, classModelBuilder.getType().getSimpleName(), TypeData.builder(clazz).build())).readName(null).writeName(name);
        classModelBuilder.addProperty(propertyModelBuilder);
        return propertyModelBuilder;
    }
    
    private void cleanPropertyBuilders(final ClassModelBuilder<?> classModelBuilder) {
        final List<String> propertiesToRemove = new ArrayList<String>();
        for (final PropertyModelBuilder<?> propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) {
            if (!propertyModelBuilder.isReadable() && !propertyModelBuilder.isWritable()) {
                propertiesToRemove.add(propertyModelBuilder.getName());
            }
        }
        for (final String propertyName : propertiesToRemove) {
            classModelBuilder.removeProperty(propertyName);
        }
    }
}
