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

package com.hypixel.hytale.codec.codecs.map;

import javax.annotation.Nullable;
import com.hypixel.hytale.codec.schema.SchemaConvertable;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import com.hypixel.hytale.codec.schema.config.StringSchema;
import com.hypixel.hytale.codec.schema.config.ObjectSchema;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.SchemaContext;
import java.io.IOException;
import com.hypixel.hytale.codec.util.RawJsonReader;
import java.util.Iterator;
import org.bson.BsonDocument;
import java.util.Collections;
import com.hypixel.hytale.codec.exception.CodecException;
import com.hypixel.hytale.codec.ExtraInfo;
import org.bson.BsonValue;
import java.util.EnumMap;
import java.util.function.Supplier;
import com.hypixel.hytale.codec.codecs.EnumCodec;
import javax.annotation.Nonnull;
import com.hypixel.hytale.codec.WrappedCodec;
import java.util.Map;
import com.hypixel.hytale.codec.Codec;

public class EnumMapCodec<K extends Enum<K>, V> implements Codec<Map<K, V>>, WrappedCodec<V>
{
    @Nonnull
    private final Class<K> clazz;
    private final K[] enumConstants;
    @Nonnull
    private final String[] enumKeys;
    private final EnumCodec.EnumStyle enumStyle;
    private final Codec<V> codec;
    private final Supplier<Map<K, V>> supplier;
    private final boolean unmodifiable;
    @Nonnull
    private final EnumMap<K, String> keyDocumentation;
    
    public EnumMapCodec(@Nonnull final Class<K> clazz, final Codec<V> codec) {
        this(clazz, codec, true);
    }
    
    public EnumMapCodec(@Nonnull final Class<K> clazz, final Codec<V> codec, final boolean unmodifiable) {
        this((Class)clazz, EnumCodec.EnumStyle.CAMEL_CASE, (Codec)codec, () -> new EnumMap(clazz), unmodifiable);
    }
    
    public EnumMapCodec(@Nonnull final Class<K> clazz, final Codec<V> codec, final Supplier<Map<K, V>> supplier) {
        this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, codec, supplier, true);
    }
    
    public EnumMapCodec(@Nonnull final Class<K> clazz, final Codec<V> codec, final Supplier<Map<K, V>> supplier, final boolean unmodifiable) {
        this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, codec, supplier, unmodifiable);
    }
    
    public EnumMapCodec(@Nonnull final Class<K> clazz, final EnumCodec.EnumStyle enumStyle, final Codec<V> codec, final Supplier<Map<K, V>> supplier, final boolean unmodifiable) {
        this.clazz = clazz;
        this.enumConstants = clazz.getEnumConstants();
        this.enumStyle = enumStyle;
        this.codec = codec;
        this.supplier = supplier;
        this.unmodifiable = unmodifiable;
        this.keyDocumentation = new EnumMap<K, String>(clazz);
        final EnumCodec.EnumStyle currentStyle = EnumCodec.EnumStyle.detect(this.enumConstants);
        this.enumKeys = new String[this.enumConstants.length];
        for (int i = 0; i < this.enumConstants.length; ++i) {
            final K e = this.enumConstants[i];
            this.enumKeys[i] = currentStyle.formatCamelCase(e.name());
        }
    }
    
    @Nonnull
    public EnumMapCodec<K, V> documentKey(final K key, final String doc) {
        this.keyDocumentation.put(key, doc);
        return this;
    }
    
    @Override
    public Codec<V> getChildCodec() {
        return this.codec;
    }
    
    @Override
    public Map<K, V> decode(@Nonnull final BsonValue bsonValue, @Nonnull final ExtraInfo extraInfo) {
        final BsonDocument bsonDocument = bsonValue.asDocument();
        Map<K, V> map = this.supplier.get();
        for (final Map.Entry<String, BsonValue> entry : bsonDocument.entrySet()) {
            final String key = entry.getKey();
            final BsonValue value = entry.getValue();
            final K enumKey = this.getEnum(key);
            extraInfo.pushKey(key);
            try {
                map.put(enumKey, this.codec.decode(value, extraInfo));
            }
            catch (final Exception e) {
                throw new CodecException("Failed to decode", value, extraInfo, e);
            }
            finally {
                extraInfo.popKey();
            }
        }
        if (this.unmodifiable) {
            map = Collections.unmodifiableMap((Map<? extends K, ? extends V>)map);
        }
        return map;
    }
    
    @Nonnull
    @Override
    public BsonValue encode(@Nonnull final Map<K, V> map, final ExtraInfo extraInfo) {
        final BsonDocument bsonDocument = new BsonDocument();
        for (final Map.Entry<K, V> entry : map.entrySet()) {
            final BsonValue value = this.codec.encode(entry.getValue(), extraInfo);
            if (value != null && !value.isNull() && (!value.isDocument() || !value.asDocument().isEmpty()) && (!value.isArray() || !value.asArray().isEmpty())) {
                final String key = switch (this.enumStyle) {
                    default -> throw new MatchException(null, null);
                    case CAMEL_CASE -> this.enumKeys[entry.getKey().ordinal()];
                    case LEGACY -> entry.getKey().name();
                };
                bsonDocument.put(key, value);
            }
        }
        return bsonDocument;
    }
    
    @Override
    public Map<K, V> decodeJson(@Nonnull final RawJsonReader reader, @Nonnull final ExtraInfo extraInfo) throws IOException {
        reader.expect('{');
        reader.consumeWhiteSpace();
        if (reader.tryConsume('}')) {
            return this.unmodifiable ? Collections.emptyMap() : this.supplier.get();
        }
        Map<K, V> map = this.supplier.get();
        while (true) {
            final String key = reader.readString();
            final K enumKey = this.getEnum(key);
            reader.consumeWhiteSpace();
            reader.expect(':');
            reader.consumeWhiteSpace();
            extraInfo.pushKey(key, reader);
            try {
                map.put(enumKey, this.codec.decodeJson(reader, extraInfo));
            }
            catch (final Exception e) {
                throw new CodecException("Failed to decode", reader, extraInfo, e);
            }
            finally {
                extraInfo.popKey();
            }
            reader.consumeWhiteSpace();
            if (reader.tryConsumeOrExpect('}', ',')) {
                break;
            }
            reader.consumeWhiteSpace();
        }
        if (this.unmodifiable) {
            map = Collections.unmodifiableMap((Map<? extends K, ? extends V>)map);
        }
        return map;
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        final ObjectSchema schema = new ObjectSchema();
        schema.getHytale().setType("EnumMap");
        schema.setTitle("Map of " + this.clazz.getSimpleName());
        final StringSchema values = new StringSchema();
        schema.setPropertyNames(values);
        final Map<String, Schema> properties = new Object2ObjectLinkedOpenHashMap<String, Schema>();
        schema.setProperties(properties);
        final Schema childSchema = context.refDefinition(this.codec);
        schema.setAdditionalProperties(childSchema);
        for (int i = 0; i < this.enumConstants.length; ++i) {
            final Schema subSchema = context.refDefinition(this.codec);
            subSchema.setMarkdownDescription(this.keyDocumentation.get(this.enumConstants[i]));
            properties.put(this.enumKeys[i], subSchema);
        }
        values.setEnum(this.enumKeys);
        return schema;
    }
    
    @Nullable
    protected K getEnum(final String value) {
        return this.enumStyle.match(this.enumConstants, this.enumKeys, value);
    }
}
