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

package org.bson.codecs.pojo;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Arrays;
import org.bson.assertions.Assertions;
import java.util.Iterator;
import java.util.Collections;
import java.util.Collection;
import java.util.ArrayList;
import org.bson.diagnostics.Loggers;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecRegistry;
import java.util.List;
import java.util.Set;
import java.util.Map;
import org.bson.diagnostics.Logger;
import org.bson.codecs.configuration.CodecProvider;

public final class PojoCodecProvider implements CodecProvider
{
    static final Logger LOGGER;
    private final boolean automatic;
    private final Map<Class<?>, ClassModel<?>> classModels;
    private final Set<String> packages;
    private final List<Convention> conventions;
    private final DiscriminatorLookup discriminatorLookup;
    private final List<PropertyCodecProvider> propertyCodecProviders;
    
    private PojoCodecProvider(final boolean automatic, final Map<Class<?>, ClassModel<?>> classModels, final Set<String> packages, final List<Convention> conventions, final List<PropertyCodecProvider> propertyCodecProviders) {
        this.automatic = automatic;
        this.classModels = classModels;
        this.packages = packages;
        this.conventions = conventions;
        this.discriminatorLookup = new DiscriminatorLookup(classModels, packages);
        this.propertyCodecProviders = propertyCodecProviders;
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    @Override
    public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry registry) {
        return this.getPojoCodec(clazz, registry);
    }
    
    private <T> PojoCodec<T> getPojoCodec(final Class<T> clazz, final CodecRegistry registry) {
        ClassModel<T> classModel = (ClassModel<T>)this.classModels.get(clazz);
        if (classModel != null) {
            return new PojoCodecImpl<T>(classModel, registry, this.propertyCodecProviders, this.discriminatorLookup);
        }
        if (!this.automatic) {
            if (clazz.getPackage() == null || !this.packages.contains(clazz.getPackage().getName())) {
                return null;
            }
        }
        try {
            classModel = createClassModel(clazz, this.conventions);
            if (clazz.isInterface() || !classModel.getPropertyModels().isEmpty()) {
                this.discriminatorLookup.addClassModel(classModel);
                return new AutomaticPojoCodec<T>(new PojoCodecImpl<T>(classModel, registry, this.propertyCodecProviders, this.discriminatorLookup));
            }
        }
        catch (final Exception e) {
            PojoCodecProvider.LOGGER.warn(String.format("Cannot use '%s' with the PojoCodec.", clazz.getSimpleName()), e);
            return null;
        }
        return null;
    }
    
    private static <T> ClassModel<T> createClassModel(final Class<T> clazz, final List<Convention> conventions) {
        final ClassModelBuilder<T> builder = ClassModel.builder(clazz);
        if (conventions != null) {
            builder.conventions(conventions);
        }
        return builder.build();
    }
    
    static {
        LOGGER = Loggers.getLogger("codecs.pojo");
    }
    
    public static final class Builder
    {
        private final Set<String> packages;
        private final Map<Class<?>, ClassModel<?>> classModels;
        private final List<Class<?>> clazzes;
        private List<Convention> conventions;
        private final List<PropertyCodecProvider> propertyCodecProviders;
        private boolean automatic;
        
        public PojoCodecProvider build() {
            final List<Convention> immutableConventions = (this.conventions != null) ? Collections.unmodifiableList((List<? extends Convention>)new ArrayList<Convention>(this.conventions)) : null;
            for (final Class<?> clazz : this.clazzes) {
                if (!this.classModels.containsKey(clazz)) {
                    this.register(createClassModel(clazz, immutableConventions));
                }
            }
            return new PojoCodecProvider(this.automatic, this.classModels, this.packages, immutableConventions, this.propertyCodecProviders, null);
        }
        
        public Builder automatic(final boolean automatic) {
            this.automatic = automatic;
            return this;
        }
        
        public Builder conventions(final List<Convention> conventions) {
            this.conventions = Assertions.notNull("conventions", conventions);
            return this;
        }
        
        public Builder register(final Class<?>... classes) {
            this.clazzes.addAll(Arrays.asList(classes));
            return this;
        }
        
        public Builder register(final ClassModel<?>... classModels) {
            Assertions.notNull("classModels", classModels);
            for (final ClassModel<?> classModel : classModels) {
                this.classModels.put(classModel.getType(), classModel);
            }
            return this;
        }
        
        public Builder register(final String... packageNames) {
            this.packages.addAll((Collection<? extends String>)Arrays.asList((Object[])Assertions.notNull("packageNames", (T[])packageNames)));
            return this;
        }
        
        public Builder register(final PropertyCodecProvider... providers) {
            this.propertyCodecProviders.addAll((Collection<? extends PropertyCodecProvider>)Arrays.asList((Object[])Assertions.notNull("providers", (T[])providers)));
            return this;
        }
        
        private Builder() {
            this.packages = new HashSet<String>();
            this.classModels = new HashMap<Class<?>, ClassModel<?>>();
            this.clazzes = new ArrayList<Class<?>>();
            this.conventions = null;
            this.propertyCodecProviders = new ArrayList<PropertyCodecProvider>();
        }
    }
}
