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

package org.bson;

import java.util.Collection;
import java.util.Set;
import org.bson.codecs.EncoderContext;
import java.io.Writer;
import org.bson.json.JsonWriter;
import java.io.StringWriter;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriterSettings;
import java.util.Date;
import org.bson.types.ObjectId;
import java.util.Iterator;
import java.util.List;
import org.bson.codecs.Encoder;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.DecoderContext;
import org.bson.json.JsonReader;
import org.bson.assertions.Assertions;
import org.bson.codecs.Decoder;
import org.bson.codecs.DocumentCodec;
import java.util.LinkedHashMap;
import org.bson.conversions.Bson;
import java.io.Serializable;
import java.util.Map;

public class Document implements Map<String, Object>, Serializable, Bson
{
    private static final long serialVersionUID = 6297731997167536582L;
    private final LinkedHashMap<String, Object> documentAsMap;
    
    public Document() {
        this.documentAsMap = new LinkedHashMap<String, Object>();
    }
    
    public Document(final String key, final Object value) {
        (this.documentAsMap = new LinkedHashMap<String, Object>()).put(key, value);
    }
    
    public Document(final Map<String, Object> map) {
        this.documentAsMap = new LinkedHashMap<String, Object>(map);
    }
    
    public static Document parse(final String json) {
        return parse(json, new DocumentCodec());
    }
    
    public static Document parse(final String json, final Decoder<Document> decoder) {
        Assertions.notNull("codec", decoder);
        final JsonReader bsonReader = new JsonReader(json);
        return decoder.decode(bsonReader, DecoderContext.builder().build());
    }
    
    @Override
    public <C> BsonDocument toBsonDocument(final Class<C> documentClass, final CodecRegistry codecRegistry) {
        return new BsonDocumentWrapper<Object>(this, codecRegistry.get(Document.class));
    }
    
    public Document append(final String key, final Object value) {
        this.documentAsMap.put(key, value);
        return this;
    }
    
    public <T> T get(final Object key, final Class<T> clazz) {
        Assertions.notNull("clazz", clazz);
        return clazz.cast(this.documentAsMap.get(key));
    }
    
    public <T> T get(final Object key, final T defaultValue) {
        Assertions.notNull("defaultValue", defaultValue);
        final Object value = this.documentAsMap.get(key);
        return (T)((value == null) ? defaultValue : value);
    }
    
    public <T> T getEmbedded(final List<?> keys, final Class<T> clazz) {
        Assertions.notNull("keys", keys);
        Assertions.isTrue("keys", !keys.isEmpty());
        Assertions.notNull("clazz", clazz);
        return this.getEmbeddedValue(keys, clazz, (T)null);
    }
    
    public <T> T getEmbedded(final List<?> keys, final T defaultValue) {
        Assertions.notNull("keys", keys);
        Assertions.isTrue("keys", !keys.isEmpty());
        Assertions.notNull("defaultValue", defaultValue);
        return this.getEmbeddedValue(keys, null, defaultValue);
    }
    
    private <T> T getEmbeddedValue(final List<?> keys, final Class<T> clazz, final T defaultValue) {
        Object value = this;
        final Iterator<?> keyIterator = keys.iterator();
        while (keyIterator.hasNext()) {
            final Object key = keyIterator.next();
            value = ((Document)value).get(key);
            if (!(value instanceof Document)) {
                if (value == null) {
                    return defaultValue;
                }
                if (keyIterator.hasNext()) {
                    throw new ClassCastException(String.format("At key %s, the value is not a Document (%s)", key, value.getClass().getName()));
                }
                continue;
            }
        }
        return (T)((clazz != null) ? clazz.cast(value) : value);
    }
    
    public Integer getInteger(final Object key) {
        return (Integer)this.get(key);
    }
    
    public int getInteger(final Object key, final int defaultValue) {
        return this.get(key, defaultValue);
    }
    
    public Long getLong(final Object key) {
        return (Long)this.get(key);
    }
    
    public Double getDouble(final Object key) {
        return (Double)this.get(key);
    }
    
    public String getString(final Object key) {
        return (String)this.get(key);
    }
    
    public Boolean getBoolean(final Object key) {
        return (Boolean)this.get(key);
    }
    
    public boolean getBoolean(final Object key, final boolean defaultValue) {
        return this.get(key, defaultValue);
    }
    
    public ObjectId getObjectId(final Object key) {
        return (ObjectId)this.get(key);
    }
    
    public Date getDate(final Object key) {
        return (Date)this.get(key);
    }
    
    public <T> List<T> getList(final Object key, final Class<T> clazz) {
        Assertions.notNull("clazz", clazz);
        return this.constructValuesList(key, clazz, null);
    }
    
    public <T> List<T> getList(final Object key, final Class<T> clazz, final List<T> defaultValue) {
        Assertions.notNull("defaultValue", defaultValue);
        Assertions.notNull("clazz", clazz);
        return (List<T>)this.constructValuesList(key, (Class<Object>)clazz, (List<Object>)defaultValue);
    }
    
    private <T> List<T> constructValuesList(final Object key, final Class<T> clazz, final List<T> defaultValue) {
        final List<?> value = this.get(key, List.class);
        if (value == null) {
            return defaultValue;
        }
        for (final Object item : value) {
            if (!clazz.isAssignableFrom(item.getClass())) {
                throw new ClassCastException(String.format("List element cannot be cast to %s", clazz.getName()));
            }
        }
        return (List<T>)value;
    }
    
    public String toJson() {
        return this.toJson(JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build());
    }
    
    public String toJson(final JsonWriterSettings writerSettings) {
        return this.toJson(writerSettings, new DocumentCodec());
    }
    
    public String toJson(final Encoder<Document> encoder) {
        return this.toJson(JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build(), encoder);
    }
    
    public String toJson(final JsonWriterSettings writerSettings, final Encoder<Document> encoder) {
        final JsonWriter writer = new JsonWriter(new StringWriter(), writerSettings);
        encoder.encode(writer, this, EncoderContext.builder().build());
        return writer.getWriter().toString();
    }
    
    @Override
    public int size() {
        return this.documentAsMap.size();
    }
    
    @Override
    public boolean isEmpty() {
        return this.documentAsMap.isEmpty();
    }
    
    @Override
    public boolean containsValue(final Object value) {
        return this.documentAsMap.containsValue(value);
    }
    
    @Override
    public boolean containsKey(final Object key) {
        return this.documentAsMap.containsKey(key);
    }
    
    @Override
    public Object get(final Object key) {
        return this.documentAsMap.get(key);
    }
    
    @Override
    public Object put(final String key, final Object value) {
        return this.documentAsMap.put(key, value);
    }
    
    @Override
    public Object remove(final Object key) {
        return this.documentAsMap.remove(key);
    }
    
    @Override
    public void putAll(final Map<? extends String, ?> map) {
        this.documentAsMap.putAll((Map<?, ?>)map);
    }
    
    @Override
    public void clear() {
        this.documentAsMap.clear();
    }
    
    @Override
    public Set<String> keySet() {
        return this.documentAsMap.keySet();
    }
    
    @Override
    public Collection<Object> values() {
        return this.documentAsMap.values();
    }
    
    @Override
    public Set<Entry<String, Object>> entrySet() {
        return this.documentAsMap.entrySet();
    }
    
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        final Document document = (Document)o;
        return this.documentAsMap.equals(document.documentAsMap);
    }
    
    @Override
    public int hashCode() {
        return this.documentAsMap.hashCode();
    }
    
    @Override
    public String toString() {
        return "Document{" + this.documentAsMap + '}';
    }
}
