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

package com.hypixel.hytale.assetstore.codec;

import java.util.Set;
import com.hypixel.hytale.codec.schema.SchemaConvertable;
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 javax.annotation.Nullable;
import java.nio.file.Path;
import com.hypixel.hytale.assetstore.AssetStore;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.assetstore.RawAsset;
import com.hypixel.hytale.codec.ExtraInfo;
import org.bson.BsonValue;
import com.hypixel.hytale.assetstore.AssetRegistry;
import com.hypixel.hytale.assetstore.AssetExtraInfo;
import java.util.function.Function;
import javax.annotation.Nonnull;
import com.hypixel.hytale.codec.validation.ValidatableCodec;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.assetstore.AssetMap;
import com.hypixel.hytale.assetstore.map.JsonAssetWithMap;

public class ContainedAssetCodec<K, T extends JsonAssetWithMap<K, M>, M extends AssetMap<K, T>> implements Codec<K>, ValidatableCodec<K>
{
    private static final boolean DISABLE_DIRECT_LOADING = true;
    private final Class<T> assetClass;
    private final AssetCodec<K, T> codec;
    @Nonnull
    private final Mode mode;
    private final Function<AssetExtraInfo<K>, K> keyGenerator;
    
    public ContainedAssetCodec(final Class<T> assetClass, final AssetCodec<K, T> codec) {
        this(assetClass, codec, Mode.GENERATE_ID);
    }
    
    public ContainedAssetCodec(final Class<T> assetClass, final AssetCodec<K, T> codec, @Nonnull final Mode mode) {
        this(assetClass, (AssetCodec<Object, T>)codec, mode, assetExtraInfo -> AssetRegistry.getAssetStore((Class<JsonAssetWithMap>)assetClass).transformKey(assetExtraInfo.generateKey()));
    }
    
    public ContainedAssetCodec(final Class<T> assetClass, final AssetCodec<K, T> codec, @Nonnull final Mode mode, final Function<AssetExtraInfo<K>, K> keyGenerator) {
        if (mode == Mode.NONE) {
            throw new UnsupportedOperationException("Contained asset mode can't be NONE!");
        }
        this.assetClass = assetClass;
        this.codec = codec;
        this.mode = mode;
        this.keyGenerator = keyGenerator;
    }
    
    public Class<T> getAssetClass() {
        return this.assetClass;
    }
    
    @Nullable
    @Override
    public K decode(@Nonnull final BsonValue bsonValue, final ExtraInfo extraInfo) {
        if (!(extraInfo instanceof AssetExtraInfo)) {
            throw new UnsupportedOperationException("Unable to decode asset from codec used outside of an AssetStore");
        }
        final AssetExtraInfo<K> assetExtraInfo = (AssetExtraInfo<K>)extraInfo;
        if (bsonValue.isString()) {
            return this.codec.getKeyCodec().getChildCodec().decode(bsonValue, extraInfo);
        }
        final KeyedCodec<K> parentCodec = this.codec.getParentCodec();
        K parentId = (parentCodec != null) ? parentCodec.getOrNull(bsonValue.asDocument(), extraInfo) : null;
        final AssetStore<K, T, M> assetStore = AssetRegistry.getAssetStore(this.assetClass);
        K id = null;
        switch (this.mode.ordinal()) {
            case 1: {
                id = this.keyGenerator.apply(assetExtraInfo);
                final boolean inheritContainerTags = false;
                break;
            }
            case 2: {
                id = assetStore.transformKey(assetExtraInfo.getKey());
                final boolean inheritContainerTags = true;
                break;
            }
            case 3: {
                id = assetStore.transformKey(assetExtraInfo.getKey());
                if (parentId == null) {
                    final Object thisAssetParentId = assetExtraInfo.getData().getParentKey();
                    if (thisAssetParentId != null) {
                        parentId = assetStore.transformKey(thisAssetParentId);
                    }
                }
                final boolean inheritContainerTags = true;
                break;
            }
            case 4: {
                id = this.keyGenerator.apply(assetExtraInfo);
                if (parentId == null && !assetExtraInfo.getKey().equals(id)) {
                    parentId = assetExtraInfo.getKey();
                }
                final boolean inheritContainerTags = true;
                break;
            }
            default: {
                throw new UnsupportedOperationException("Contained asset mode can't be NONE!");
            }
        }
        final T parent = (T)((parentId != null) ? ((T)assetStore.getAssetMap().getAsset(parentId)) : null);
        if (parentId == null || parent != null) {}
        final char[] clone = bsonValue.asDocument().toJson().toCharArray();
        final Path path = assetExtraInfo.getAssetPath();
        assetExtraInfo.getData().addContainedAsset(this.assetClass, new RawAsset<K>(path, id, parentId, 0, clone, assetExtraInfo.getData(), this.mode));
        return id;
    }
    
    @Override
    public BsonValue encode(@Nonnull final K key, final ExtraInfo extraInfo) {
        if (key.toString().startsWith("*")) {
            final T asset = AssetRegistry.getAssetStore(this.assetClass).getAssetMap().getAsset(key);
            if (asset != null) {
                return this.codec.encode(asset, extraInfo);
            }
        }
        return this.codec.getKeyCodec().getChildCodec().encode(key, extraInfo);
    }
    
    @Nullable
    @Override
    public K decodeJson(@Nonnull final RawJsonReader reader, final ExtraInfo extraInfo) throws IOException {
        if (!(extraInfo instanceof AssetExtraInfo)) {
            throw new UnsupportedOperationException("Unable to decode asset from codec used outside of an AssetStore");
        }
        final AssetExtraInfo<K> assetExtraInfo = (AssetExtraInfo<K>)extraInfo;
        final int lineStart = reader.getLine() - 1;
        if (reader.peekFor('\"')) {
            return this.codec.getKeyCodec().getChildCodec().decodeJson(reader, extraInfo);
        }
        reader.mark();
        K parentId = null;
        boolean needsSkip = false;
        final KeyedCodec<K> parentCodec = this.codec.getParentCodec();
        if (parentCodec != null && RawJsonReader.seekToKey(reader, parentCodec.getKey())) {
            parentId = parentCodec.getChildCodec().decodeJson(reader, extraInfo);
            needsSkip = true;
        }
        final AssetStore<K, T, M> assetStore = AssetRegistry.getAssetStore(this.assetClass);
        K id = null;
        switch (this.mode.ordinal()) {
            case 1: {
                id = this.keyGenerator.apply(assetExtraInfo);
                final boolean inheritContainerTags = false;
                break;
            }
            case 2: {
                id = assetStore.transformKey(assetExtraInfo.getKey());
                final boolean inheritContainerTags = true;
                break;
            }
            case 3: {
                id = assetStore.transformKey(assetExtraInfo.getKey());
                if (parentId == null) {
                    final Object thisAssetParentId = assetExtraInfo.getData().getParentKey();
                    if (thisAssetParentId != null) {
                        parentId = assetStore.transformKey(thisAssetParentId);
                    }
                }
                final boolean inheritContainerTags = true;
                break;
            }
            case 4: {
                id = this.keyGenerator.apply(assetExtraInfo);
                if (parentId == null && !assetExtraInfo.getKey().equals(id)) {
                    parentId = assetExtraInfo.getKey();
                }
                final boolean inheritContainerTags = true;
                break;
            }
            default: {
                throw new UnsupportedOperationException("Contained asset mode can't be NONE!");
            }
        }
        final T parent = (T)((parentId != null) ? ((T)assetStore.getAssetMap().getAsset(parentId)) : null);
        if (parentId == null || parent != null) {}
        if (needsSkip) {
            reader.skipObjectContinued();
        }
        final char[] clone = reader.cloneMark();
        reader.unmark();
        final Path path = assetExtraInfo.getAssetPath();
        assetExtraInfo.getData().addContainedAsset(this.assetClass, new RawAsset<K>(path, id, parentId, lineStart, clone, assetExtraInfo.getData(), this.mode));
        return id;
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        final Schema keySchema = context.refDefinition(this.codec.getKeyCodec().getChildCodec());
        keySchema.setTitle("Reference to " + this.assetClass.getSimpleName());
        final Schema nestedSchema = context.refDefinition(this.codec);
        final Schema s = Schema.anyOf(keySchema, nestedSchema);
        s.setHytaleAssetRef(this.assetClass.getSimpleName());
        return s;
    }
    
    @Override
    public void validate(final K k, @Nonnull final ExtraInfo extraInfo) {
        AssetRegistry.getAssetStore(this.assetClass).validate(k, extraInfo.getValidationResults(), extraInfo);
    }
    
    @Override
    public void validateDefaults(final ExtraInfo extraInfo, @Nonnull final Set<Codec<?>> tested) {
        if (!tested.add(this)) {
            return;
        }
    }
    
    public enum Mode
    {
        NONE, 
        GENERATE_ID, 
        INHERIT_ID, 
        INHERIT_ID_AND_PARENT, 
        INJECT_PARENT;
    }
}
