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

package org.bson.codecs.pojo;

import org.bson.diagnostics.Loggers;
import org.bson.BsonReaderMark;
import java.util.Map;
import java.util.Collection;
import java.util.function.Consumer;
import org.bson.BsonInvalidOperationException;
import org.bson.codecs.Decoder;
import org.bson.BsonType;
import org.bson.codecs.Encoder;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.BsonReader;
import java.util.Iterator;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.EncoderContext;
import org.bson.BsonWriter;
import java.util.List;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.diagnostics.Logger;

final class PojoCodecImpl<T> extends PojoCodec<T>
{
    private static final Logger LOGGER;
    private final ClassModel<T> classModel;
    private final CodecRegistry registry;
    private final PropertyCodecRegistry propertyCodecRegistry;
    private final DiscriminatorLookup discriminatorLookup;
    private final boolean specialized;
    
    PojoCodecImpl(final ClassModel<T> classModel, final CodecRegistry codecRegistry, final List<PropertyCodecProvider> propertyCodecProviders, final DiscriminatorLookup discriminatorLookup) {
        this.classModel = classModel;
        this.registry = codecRegistry;
        this.discriminatorLookup = discriminatorLookup;
        this.propertyCodecRegistry = new PropertyCodecRegistryImpl(this, this.registry, propertyCodecProviders);
        this.specialized = shouldSpecialize(classModel);
        this.specialize();
    }
    
    PojoCodecImpl(final ClassModel<T> classModel, final CodecRegistry codecRegistry, final PropertyCodecRegistry propertyCodecRegistry, final DiscriminatorLookup discriminatorLookup, final boolean specialized) {
        this.classModel = classModel;
        this.registry = codecRegistry;
        this.discriminatorLookup = discriminatorLookup;
        this.propertyCodecRegistry = propertyCodecRegistry;
        this.specialized = specialized;
        this.specialize();
    }
    
    @Override
    public void encode(final BsonWriter writer, final T value, final EncoderContext encoderContext) {
        if (!this.specialized) {
            throw new CodecConfigurationException(String.format("%s contains generic types that have not been specialised.%nTop level classes with generic types are not supported by the PojoCodec.", this.classModel.getName()));
        }
        if (this.areEquivalentTypes(value.getClass(), this.classModel.getType())) {
            writer.writeStartDocument();
            this.encodeIdProperty(writer, value, encoderContext, this.classModel.getIdPropertyModelHolder());
            if (this.classModel.useDiscriminator()) {
                writer.writeString(this.classModel.getDiscriminatorKey(), this.classModel.getDiscriminator());
            }
            for (final PropertyModel<?> propertyModel : this.classModel.getPropertyModels()) {
                if (propertyModel.equals(this.classModel.getIdPropertyModel())) {
                    continue;
                }
                this.encodeProperty(writer, value, encoderContext, propertyModel);
            }
            writer.writeEndDocument();
        }
        else {
            this.registry.get(value.getClass()).encode(writer, value, encoderContext);
        }
    }
    
    @Override
    public T decode(final BsonReader reader, final DecoderContext decoderContext) {
        if (!decoderContext.hasCheckedDiscriminator()) {
            return this.getCodecFromDocument(reader, this.classModel.useDiscriminator(), this.classModel.getDiscriminatorKey(), this.registry, this.discriminatorLookup, this).decode(reader, DecoderContext.builder().checkedDiscriminator(true).build());
        }
        if (!this.specialized) {
            throw new CodecConfigurationException(String.format("%s contains generic types that have not been specialised.%nTop level classes with generic types are not supported by the PojoCodec.", this.classModel.getName()));
        }
        final InstanceCreator<T> instanceCreator = this.classModel.getInstanceCreator();
        this.decodeProperties(reader, decoderContext, instanceCreator);
        return instanceCreator.getInstance();
    }
    
    @Override
    public Class<T> getEncoderClass() {
        return this.classModel.getType();
    }
    
    @Override
    public String toString() {
        return String.format("PojoCodec<%s>", this.classModel);
    }
    
    @Override
    ClassModel<T> getClassModel() {
        return this.classModel;
    }
    
    private <S> void encodeIdProperty(final BsonWriter writer, final T instance, final EncoderContext encoderContext, final IdPropertyModelHolder<S> propertyModelHolder) {
        if (propertyModelHolder.getPropertyModel() != null) {
            if (propertyModelHolder.getIdGenerator() == null) {
                this.encodeProperty(writer, instance, encoderContext, propertyModelHolder.getPropertyModel());
            }
            else {
                S id = propertyModelHolder.getPropertyModel().getPropertyAccessor().get(instance);
                if (id == null && encoderContext.isEncodingCollectibleDocument()) {
                    id = propertyModelHolder.getIdGenerator().generate();
                    try {
                        propertyModelHolder.getPropertyModel().getPropertyAccessor().set(instance, id);
                    }
                    catch (final Exception ex) {}
                }
                this.encodeValue(writer, encoderContext, propertyModelHolder.getPropertyModel(), id);
            }
        }
    }
    
    private <S> void encodeProperty(final BsonWriter writer, final T instance, final EncoderContext encoderContext, final PropertyModel<S> propertyModel) {
        if (propertyModel != null && propertyModel.isReadable()) {
            final S propertyValue = propertyModel.getPropertyAccessor().get(instance);
            this.encodeValue(writer, encoderContext, propertyModel, propertyValue);
        }
    }
    
    private <S> void encodeValue(final BsonWriter writer, final EncoderContext encoderContext, final PropertyModel<S> propertyModel, final S propertyValue) {
        if (propertyModel.shouldSerialize(propertyValue)) {
            writer.writeName(propertyModel.getReadName());
            if (propertyValue == null) {
                writer.writeNull();
            }
            else {
                try {
                    encoderContext.encodeWithChildContext(propertyModel.getCachedCodec(), writer, propertyValue);
                }
                catch (final CodecConfigurationException e) {
                    throw new CodecConfigurationException(String.format("Failed to encode '%s'. Encoding '%s' errored with: %s", this.classModel.getName(), propertyModel.getReadName(), e.getMessage()), e);
                }
            }
        }
    }
    
    private void decodeProperties(final BsonReader reader, final DecoderContext decoderContext, final InstanceCreator<T> instanceCreator) {
        reader.readStartDocument();
        while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
            final String name = reader.readName();
            if (this.classModel.useDiscriminator() && this.classModel.getDiscriminatorKey().equals(name)) {
                reader.readString();
            }
            else {
                this.decodePropertyModel(reader, decoderContext, instanceCreator, name, this.getPropertyModelByWriteName(this.classModel, name));
            }
        }
        reader.readEndDocument();
    }
    
    private <S> void decodePropertyModel(final BsonReader reader, final DecoderContext decoderContext, final InstanceCreator<T> instanceCreator, final String name, final PropertyModel<S> propertyModel) {
        if (propertyModel != null) {
            try {
                S value = null;
                if (reader.getCurrentBsonType() == BsonType.NULL) {
                    reader.readNull();
                }
                else {
                    final Codec<S> codec = propertyModel.getCachedCodec();
                    if (codec == null) {
                        throw new CodecConfigurationException(String.format("Missing codec in '%s' for '%s'", this.classModel.getName(), propertyModel.getName()));
                    }
                    value = decoderContext.decodeWithChildContext(codec, reader);
                }
                if (propertyModel.isWritable()) {
                    instanceCreator.set(value, propertyModel);
                }
                return;
            }
            catch (final BsonInvalidOperationException | CodecConfigurationException e) {
                throw new CodecConfigurationException(String.format("Failed to decode '%s'. Decoding '%s' errored with: %s", this.classModel.getName(), name, e.getMessage()), e);
            }
        }
        if (PojoCodecImpl.LOGGER.isTraceEnabled()) {
            PojoCodecImpl.LOGGER.trace(String.format("Found property not present in the ClassModel: %s", name));
        }
        reader.skipValue();
    }
    
    private void specialize() {
        if (this.specialized) {
            this.classModel.getPropertyModels().forEach(this::cachePropertyModelCodec);
        }
    }
    
    private <S> void cachePropertyModelCodec(final PropertyModel<S> propertyModel) {
        if (propertyModel.getCachedCodec() == null) {
            final Codec<S> codec = (propertyModel.getCodec() != null) ? propertyModel.getCodec() : new LazyPropertyModelCodec<S>(propertyModel, this.registry, this.propertyCodecRegistry, this.discriminatorLookup);
            propertyModel.cachedCodec(codec);
        }
    }
    
    private <S, V> boolean areEquivalentTypes(final Class<S> t1, final Class<V> t2) {
        return t1.equals(t2) || (Collection.class.isAssignableFrom(t1) && Collection.class.isAssignableFrom(t2)) || (Map.class.isAssignableFrom(t1) && Map.class.isAssignableFrom(t2));
    }
    
    private Codec<T> getCodecFromDocument(final BsonReader reader, final boolean useDiscriminator, final String discriminatorKey, final CodecRegistry registry, final DiscriminatorLookup discriminatorLookup, final Codec<T> defaultCodec) {
        Codec<T> codec = defaultCodec;
        if (useDiscriminator) {
            final BsonReaderMark mark = reader.getMark();
            reader.readStartDocument();
            boolean discriminatorKeyFound = false;
            while (!discriminatorKeyFound && reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
                final String name = reader.readName();
                if (discriminatorKey.equals(name)) {
                    discriminatorKeyFound = true;
                    try {
                        final Class<?> discriminatorClass = discriminatorLookup.lookup(reader.readString());
                        if (codec.getEncoderClass().equals(discriminatorClass)) {
                            continue;
                        }
                        codec = registry.get(discriminatorClass);
                        continue;
                    }
                    catch (final Exception e) {
                        throw new CodecConfigurationException(String.format("Failed to decode '%s'. Decoding errored with: %s", this.classModel.getName(), e.getMessage()), e);
                    }
                }
                reader.skipValue();
            }
            mark.reset();
        }
        return codec;
    }
    
    private PropertyModel<?> getPropertyModelByWriteName(final ClassModel<T> classModel, final String readName) {
        for (final PropertyModel<?> propertyModel : classModel.getPropertyModels()) {
            if (propertyModel.isWritable() && propertyModel.getWriteName().equals(readName)) {
                return propertyModel;
            }
        }
        return null;
    }
    
    private static <T> boolean shouldSpecialize(final ClassModel<T> classModel) {
        if (!classModel.hasTypeParameters()) {
            return true;
        }
        for (final Map.Entry<String, TypeParameterMap> entry : classModel.getPropertyNameToTypeParameterMap().entrySet()) {
            final TypeParameterMap typeParameterMap = entry.getValue();
            final PropertyModel<?> propertyModel = classModel.getPropertyModel(entry.getKey());
            if (typeParameterMap.hasTypeParameters() && (propertyModel == null || propertyModel.getCodec() == null)) {
                return false;
            }
        }
        return true;
    }
    
    @Override
    DiscriminatorLookup getDiscriminatorLookup() {
        return this.discriminatorLookup;
    }
    
    static {
        LOGGER = Loggers.getLogger("PojoCodec");
    }
}
