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

package org.bson.codecs;

import org.bson.codecs.configuration.CodecRegistries;
import java.util.Arrays;
import org.bson.codecs.configuration.CodecProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Iterator;
import org.bson.BsonType;
import org.bson.BsonReader;
import java.util.Map;
import org.bson.BsonWriter;
import org.bson.BsonDocumentWriter;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.assertions.Assertions;
import org.bson.UuidRepresentation;
import org.bson.Transformer;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.Document;

public class DocumentCodec implements CollectibleCodec<Document>, OverridableUuidRepresentationCodec<Document>
{
    private static final String ID_FIELD_NAME = "_id";
    private static final CodecRegistry DEFAULT_REGISTRY;
    private static final BsonTypeCodecMap DEFAULT_BSON_TYPE_CODEC_MAP;
    private static final IdGenerator DEFAULT_ID_GENERATOR;
    private final BsonTypeCodecMap bsonTypeCodecMap;
    private final CodecRegistry registry;
    private final IdGenerator idGenerator;
    private final Transformer valueTransformer;
    private final UuidRepresentation uuidRepresentation;
    
    public DocumentCodec() {
        this(DocumentCodec.DEFAULT_REGISTRY, DocumentCodec.DEFAULT_BSON_TYPE_CODEC_MAP, null);
    }
    
    public DocumentCodec(final CodecRegistry registry) {
        this(registry, BsonTypeClassMap.DEFAULT_BSON_TYPE_CLASS_MAP);
    }
    
    public DocumentCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap) {
        this(registry, bsonTypeClassMap, null);
    }
    
    public DocumentCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap, final Transformer valueTransformer) {
        this(registry, new BsonTypeCodecMap(Assertions.notNull("bsonTypeClassMap", bsonTypeClassMap), registry), valueTransformer);
    }
    
    private DocumentCodec(final CodecRegistry registry, final BsonTypeCodecMap bsonTypeCodecMap, final Transformer valueTransformer) {
        this(registry, bsonTypeCodecMap, DocumentCodec.DEFAULT_ID_GENERATOR, valueTransformer, UuidRepresentation.UNSPECIFIED);
    }
    
    private DocumentCodec(final CodecRegistry registry, final BsonTypeCodecMap bsonTypeCodecMap, final IdGenerator idGenerator, final Transformer valueTransformer, final UuidRepresentation uuidRepresentation) {
        this.registry = Assertions.notNull("registry", registry);
        this.bsonTypeCodecMap = bsonTypeCodecMap;
        this.idGenerator = idGenerator;
        this.valueTransformer = ((valueTransformer != null) ? valueTransformer : new Transformer() {
            @Override
            public Object transform(final Object value) {
                return value;
            }
        });
        this.uuidRepresentation = uuidRepresentation;
    }
    
    @Override
    public Codec<Document> withUuidRepresentation(final UuidRepresentation uuidRepresentation) {
        return new DocumentCodec(this.registry, this.bsonTypeCodecMap, this.idGenerator, this.valueTransformer, uuidRepresentation);
    }
    
    @Override
    public boolean documentHasId(final Document document) {
        return document.containsKey("_id");
    }
    
    @Override
    public BsonValue getDocumentId(final Document document) {
        if (!this.documentHasId(document)) {
            throw new IllegalStateException("The document does not contain an _id");
        }
        final Object id = document.get("_id");
        if (id instanceof BsonValue) {
            return (BsonValue)id;
        }
        final BsonDocument idHoldingDocument = new BsonDocument();
        final BsonWriter writer = new BsonDocumentWriter(idHoldingDocument);
        writer.writeStartDocument();
        writer.writeName("_id");
        this.writeValue(writer, EncoderContext.builder().build(), id);
        writer.writeEndDocument();
        return idHoldingDocument.get("_id");
    }
    
    @Override
    public Document generateIdIfAbsentFromDocument(final Document document) {
        if (!this.documentHasId(document)) {
            document.put("_id", this.idGenerator.generate());
        }
        return document;
    }
    
    @Override
    public void encode(final BsonWriter writer, final Document document, final EncoderContext encoderContext) {
        this.writeMap(writer, document, encoderContext);
    }
    
    @Override
    public Document decode(final BsonReader reader, final DecoderContext decoderContext) {
        final Document document = new Document();
        reader.readStartDocument();
        while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
            final String fieldName = reader.readName();
            document.put(fieldName, this.readValue(reader, decoderContext));
        }
        reader.readEndDocument();
        return document;
    }
    
    @Override
    public Class<Document> getEncoderClass() {
        return Document.class;
    }
    
    private void beforeFields(final BsonWriter bsonWriter, final EncoderContext encoderContext, final Map<String, Object> document) {
        if (encoderContext.isEncodingCollectibleDocument() && document.containsKey("_id")) {
            bsonWriter.writeName("_id");
            this.writeValue(bsonWriter, encoderContext, document.get("_id"));
        }
    }
    
    private boolean skipField(final EncoderContext encoderContext, final String key) {
        return encoderContext.isEncodingCollectibleDocument() && key.equals("_id");
    }
    
    private void writeValue(final BsonWriter writer, final EncoderContext encoderContext, final Object value) {
        if (value == null) {
            writer.writeNull();
        }
        else if (value instanceof Iterable) {
            this.writeIterable(writer, (Iterable<Object>)value, encoderContext.getChildContext());
        }
        else if (value instanceof Map) {
            this.writeMap(writer, (Map<String, Object>)value, encoderContext.getChildContext());
        }
        else {
            final Codec codec = this.registry.get(value.getClass());
            encoderContext.encodeWithChildContext(codec, writer, value);
        }
    }
    
    private void writeMap(final BsonWriter writer, final Map<String, Object> map, final EncoderContext encoderContext) {
        writer.writeStartDocument();
        this.beforeFields(writer, encoderContext, map);
        for (final Map.Entry<String, Object> entry : map.entrySet()) {
            if (this.skipField(encoderContext, entry.getKey())) {
                continue;
            }
            writer.writeName(entry.getKey());
            this.writeValue(writer, encoderContext, entry.getValue());
        }
        writer.writeEndDocument();
    }
    
    private void writeIterable(final BsonWriter writer, final Iterable<Object> list, final EncoderContext encoderContext) {
        writer.writeStartArray();
        for (final Object value : list) {
            this.writeValue(writer, encoderContext, value);
        }
        writer.writeEndArray();
    }
    
    private Object readValue(final BsonReader reader, final DecoderContext decoderContext) {
        final BsonType bsonType = reader.getCurrentBsonType();
        if (bsonType == BsonType.NULL) {
            reader.readNull();
            return null;
        }
        if (bsonType == BsonType.ARRAY) {
            return this.readList(reader, decoderContext);
        }
        Codec<?> codec = this.bsonTypeCodecMap.get(bsonType);
        if (bsonType == BsonType.BINARY && reader.peekBinarySize() == 16) {
            switch (reader.peekBinarySubType()) {
                case 3: {
                    if (this.uuidRepresentation == UuidRepresentation.JAVA_LEGACY || this.uuidRepresentation == UuidRepresentation.C_SHARP_LEGACY || this.uuidRepresentation == UuidRepresentation.PYTHON_LEGACY) {
                        codec = this.registry.get((Class<?>)UUID.class);
                        break;
                    }
                    break;
                }
                case 4: {
                    if (this.uuidRepresentation == UuidRepresentation.STANDARD) {
                        codec = this.registry.get((Class<?>)UUID.class);
                        break;
                    }
                    break;
                }
            }
        }
        return this.valueTransformer.transform(codec.decode(reader, decoderContext));
    }
    
    private List<Object> readList(final BsonReader reader, final DecoderContext decoderContext) {
        reader.readStartArray();
        final List<Object> list = new ArrayList<Object>();
        while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
            list.add(this.readValue(reader, decoderContext));
        }
        reader.readEndArray();
        return list;
    }
    
    static {
        DEFAULT_REGISTRY = CodecRegistries.fromProviders(Arrays.asList(new ValueCodecProvider(), new BsonValueCodecProvider(), new DocumentCodecProvider()));
        DEFAULT_BSON_TYPE_CODEC_MAP = new BsonTypeCodecMap(BsonTypeClassMap.DEFAULT_BSON_TYPE_CLASS_MAP, DocumentCodec.DEFAULT_REGISTRY);
        DEFAULT_ID_GENERATOR = new ObjectIdGenerator();
    }
}
