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

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

import javax.annotation.Nullable;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import com.hypixel.hytale.codec.schema.SchemaConvertable;
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 java.util.function.BiFunction;
import java.util.function.Function;
import javax.annotation.Nonnull;
import com.hypixel.hytale.codec.WrappedCodec;
import java.util.Map;
import com.hypixel.hytale.codec.Codec;

public class MergedEnumMapCodec<K extends Enum<K>, V, M extends Enum<M>> implements Codec<Map<K, V>>, WrappedCodec<V>
{
    @Nonnull
    private final Class<K> clazz;
    private final K[] enumConstants;
    @Nonnull
    private final String[] enumKeys;
    @Nonnull
    private final Class<M> mergeClazz;
    private final M[] mergeEnumConstants;
    @Nonnull
    private final String[] mergeEnumKeys;
    private final Function<M, K[]> unmergeFunction;
    private final BiFunction<V, V, V> mergeResultFunction;
    private final EnumCodec.EnumStyle enumStyle;
    private final Codec<V> codec;
    private final Supplier<EnumMap<K, V>> supplier;
    private final boolean unmodifiable;
    
    public MergedEnumMapCodec(@Nonnull final Class<K> clazz, @Nonnull final Class<M> mergeClass, final Function<M, K[]> unmergeFunction, final BiFunction<V, V, V> mergeResultFunction, final Codec<V> codec) {
        this((Class)clazz, EnumCodec.EnumStyle.CAMEL_CASE, (Class)mergeClass, (Function)unmergeFunction, (BiFunction)mergeResultFunction, (Codec)codec, () -> new EnumMap(clazz), true);
    }
    
    public MergedEnumMapCodec(@Nonnull final Class<K> clazz, @Nonnull final Class<M> mergeClass, final Function<M, K[]> unmergeFunction, final BiFunction<V, V, V> mergeResultFunction, final Codec<V> codec, final Supplier<EnumMap<K, V>> supplier) {
        this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, mergeClass, unmergeFunction, mergeResultFunction, codec, supplier, true);
    }
    
    public MergedEnumMapCodec(@Nonnull final Class<K> clazz, @Nonnull final Class<M> mergeClass, final Function<M, K[]> unmergeFunction, final BiFunction<V, V, V> mergeResultFunction, final Codec<V> codec, final Supplier<EnumMap<K, V>> supplier, final boolean unmodifiable) {
        this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, mergeClass, unmergeFunction, mergeResultFunction, codec, supplier, unmodifiable);
    }
    
    public MergedEnumMapCodec(@Nonnull final Class<K> clazz, final EnumCodec.EnumStyle enumStyle, @Nonnull final Class<M> mergeClass, final Function<M, K[]> unmergeFunction, final BiFunction<V, V, V> mergeResultFunction, final Codec<V> codec, final Supplier<EnumMap<K, V>> supplier, final boolean unmodifiable) {
        this.clazz = clazz;
        this.enumConstants = clazz.getEnumConstants();
        this.mergeClazz = mergeClass;
        this.mergeEnumConstants = mergeClass.getEnumConstants();
        this.unmergeFunction = unmergeFunction;
        this.enumStyle = enumStyle;
        this.mergeResultFunction = mergeResultFunction;
        this.codec = codec;
        this.supplier = supplier;
        this.unmodifiable = unmodifiable;
        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());
        }
        final EnumCodec.EnumStyle currentMergeStyle = EnumCodec.EnumStyle.detect(this.mergeEnumConstants);
        this.mergeEnumKeys = new String[this.mergeEnumConstants.length];
        for (int j = 0; j < this.mergeEnumConstants.length; ++j) {
            final M e2 = this.mergeEnumConstants[j];
            this.mergeEnumKeys[j] = currentMergeStyle.formatCamelCase(e2.name());
        }
    }
    
    @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();
            extraInfo.pushKey(key);
            try {
                final V decode = this.codec.decode(value, extraInfo);
                this.put0(map, key, decode);
            }
            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;
    }
    
    private void put0(@Nonnull final Map<K, V> map, final String key, final V decode) {
        final K k = this.getEnum(key);
        if (k != null) {
            final V v = map.get(k);
            if (v == null) {
                map.put(k, decode);
            }
            else {
                map.put(k, this.mergeResultFunction.apply(v, decode));
            }
        }
        else {
            final K[] mergedEnum = this.getMergedEnum(key);
            if (mergedEnum != null) {
                for (final K merged : mergedEnum) {
                    final V v2 = map.get(merged);
                    if (v2 == null) {
                        map.put(merged, decode);
                    }
                    else {
                        map.put(merged, this.mergeResultFunction.apply(v2, decode));
                    }
                }
            }
        }
    }
    
    @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();
            reader.consumeWhiteSpace();
            reader.expect(':');
            reader.consumeWhiteSpace();
            extraInfo.pushKey(key, reader);
            try {
                final V decode = this.codec.decodeJson(reader, extraInfo);
                this.put0(map, key, decode);
            }
            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("Merged map of " + this.clazz.getSimpleName() + " and " + this.mergeClazz.getSimpleName());
        final StringSchema values = new StringSchema();
        schema.setPropertyNames(values);
        final Schema childSchema = context.refDefinition(this.codec);
        final Map<String, Schema> properties = new Object2ObjectLinkedOpenHashMap<String, Schema>();
        schema.setProperties(properties);
        schema.setAdditionalProperties(childSchema);
        final String[] enum_ = new String[this.enumKeys.length + this.mergeEnumKeys.length];
        for (int i = 0; i < this.enumKeys.length; ++i) {
            final String entry = this.enumKeys[i];
            properties.put(enum_[i] = entry, childSchema);
        }
        for (int i = 0; i < this.mergeEnumKeys.length; ++i) {
            final String entry = this.mergeEnumKeys[i];
            properties.put(enum_[this.enumConstants.length + i] = entry, childSchema);
        }
        values.setEnum(enum_);
        return schema;
    }
    
    @Nullable
    protected K getEnum(final String value) {
        return this.enumStyle.match(this.enumConstants, this.enumKeys, value, true);
    }
    
    protected K[] getMergedEnum(final String value) {
        final M m = this.enumStyle.match(this.mergeEnumConstants, this.mergeEnumKeys, value, true);
        return this.unmergeFunction.apply(m);
    }
}
