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

package com.hypixel.hytale.codec.lookup;

import java.util.Set;
import java.util.stream.Collector;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.Comparator;
import java.util.LinkedHashMap;
import com.hypixel.hytale.codec.schema.config.ObjectSchema;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.SchemaContext;
import com.hypixel.hytale.codec.exception.CodecException;
import java.util.Iterator;
import org.bson.BsonDocument;
import java.io.IOException;
import com.hypixel.hytale.codec.util.RawJsonReader;
import com.hypixel.hytale.codec.ExtraInfo;
import org.bson.BsonValue;
import javax.annotation.Nonnull;
import java.util.function.Function;
import com.hypixel.hytale.codec.validation.ValidatableCodec;
import com.hypixel.hytale.codec.Codec;
import java.util.Map;

public abstract class AMapProvidedMapCodec<K, V, P, M extends Map<K, V>> implements Codec<M>, ValidatableCodec<M>
{
    protected final Map<K, P> codecProvider;
    protected final Function<P, Codec<V>> mapper;
    protected final boolean unmodifiable;
    
    public AMapProvidedMapCodec(final Map<K, P> codecProvider, final Function<P, Codec<V>> mapper) {
        this(codecProvider, mapper, true);
    }
    
    public AMapProvidedMapCodec(final Map<K, P> codecProvider, final Function<P, Codec<V>> mapper, final boolean unmodifiable) {
        this.codecProvider = codecProvider;
        this.mapper = mapper;
        this.unmodifiable = unmodifiable;
    }
    
    public abstract M createMap();
    
    public void handleUnknown(final M map, @Nonnull final String key, final BsonValue value, @Nonnull final ExtraInfo extraInfo) {
        extraInfo.addUnknownKey(key);
    }
    
    public void handleUnknown(final M map, @Nonnull final String key, @Nonnull final RawJsonReader reader, @Nonnull final ExtraInfo extraInfo) throws IOException {
        extraInfo.addUnknownKey(key);
        reader.skipValue();
    }
    
    @Override
    public M decode(@Nonnull final BsonValue bsonValue, @Nonnull final ExtraInfo extraInfo) {
        final BsonDocument bsonDocument = bsonValue.asDocument();
        M map = this.createMap();
        for (final Map.Entry<String, BsonValue> entry : bsonDocument.entrySet()) {
            extraInfo.pushKey(entry.getKey());
            try {
                final K key = this.getKeyForId(entry.getKey());
                if (key == null) {
                    this.handleUnknown(map, entry.getKey(), entry.getValue(), extraInfo);
                }
                else {
                    final Codec<V> codecFor = this.getCodecFor(key);
                    map.put(key, codecFor.decode(entry.getValue(), extraInfo));
                }
            }
            finally {
                extraInfo.popKey();
            }
        }
        if (this.unmodifiable) {
            map = this.unmodifiableMap(map);
        }
        return map;
    }
    
    @Nonnull
    @Override
    public BsonValue encode(@Nonnull final M map, final ExtraInfo extraInfo) {
        final BsonDocument document = new BsonDocument();
        for (final Map.Entry<K, V> entry : map.entrySet()) {
            final Codec<V> codecFor = this.getCodecFor(entry.getKey());
            document.put(this.getIdForKey(entry.getKey()), codecFor.encode(entry.getValue(), extraInfo));
        }
        this.encodeExtra(document, map, extraInfo);
        return document;
    }
    
    protected void encodeExtra(final BsonDocument document, final M map, final ExtraInfo extraInfo) {
    }
    
    @Override
    public M decodeJson(@Nonnull final RawJsonReader reader, @Nonnull final ExtraInfo extraInfo) throws IOException {
        reader.expect('{');
        reader.consumeWhiteSpace();
        if (reader.tryConsume('}')) {
            return this.unmodifiable ? this.emptyMap() : this.createMap();
        }
        M map = this.createMap();
        while (true) {
            final String id = reader.readString();
            reader.consumeWhiteSpace();
            reader.expect(':');
            reader.consumeWhiteSpace();
            extraInfo.pushKey(id, reader);
            try {
                final K key = this.getKeyForId(id);
                if (key == null) {
                    this.handleUnknown(map, id, reader, extraInfo);
                }
                else {
                    final Codec<V> codec = this.getCodecFor(key);
                    map.put(key, 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 = this.unmodifiableMap(map);
        }
        return map;
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        final ObjectSchema obj = new ObjectSchema();
        obj.setAdditionalProperties(false);
        final LinkedHashMap<String, Schema> props = this.codecProvider.keySet().stream().map(key -> {
            final Codec<V> codec = this.getCodecFor(key);
            return Map.entry(this.getIdForKey(key), codec.toSchema(context));
        }).sorted((Comparator<? super Object>)Map.Entry.comparingByKey()).collect((Collector<? super Object, ?, LinkedHashMap<String, Schema>>)Collectors.toMap((Function<? super Object, ?>)Map.Entry::getKey, (Function<? super Object, ?>)Map.Entry::getValue, (a, b) -> a, (Supplier<R>)LinkedHashMap::new));
        obj.setProperties(props);
        return obj;
    }
    
    @Override
    public void validate(@Nonnull final M map, final ExtraInfo extraInfo) {
        for (final Map.Entry<K, V> entry : map.entrySet()) {
            final Codec<V> codec = this.getCodecFor(entry.getKey());
            if (codec instanceof final ValidatableCodec validatableCodec) {
                validatableCodec.validate(entry.getValue(), extraInfo);
            }
        }
    }
    
    @Override
    public void validateDefaults(final ExtraInfo extraInfo, @Nonnull final Set<Codec<?>> tested) {
        if (!tested.add(this)) {
            return;
        }
        for (final P value : this.codecProvider.values()) {
            final Codec<V> codec = this.mapper.apply(value);
            ValidatableCodec.validateDefaults(codec, extraInfo, tested);
        }
    }
    
    private Codec<V> getCodecFor(final K key) {
        return this.mapper.apply(this.codecProvider.get(key));
    }
    
    protected abstract String getIdForKey(final K p0);
    
    protected abstract K getKeyForId(final String p0);
    
    protected abstract M emptyMap();
    
    protected abstract M unmodifiableMap(final M p0);
}
