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

package com.hypixel.hytale.assetstore.codec;

import com.hypixel.hytale.codec.codecs.map.MapCodec;
import java.util.HashMap;
import com.hypixel.hytale.codec.Codec;
import java.util.function.Supplier;
import java.util.LinkedHashMap;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.config.ObjectSchema;
import com.hypixel.hytale.codec.schema.SchemaContext;
import com.hypixel.hytale.codec.ExtraInfo;
import javax.annotation.Nullable;
import java.io.IOException;
import com.hypixel.hytale.codec.util.RawJsonReader;
import java.util.function.Function;
import com.hypixel.hytale.assetstore.AssetExtraInfo;
import java.util.function.BiConsumer;
import javax.annotation.Nonnull;
import java.util.Map;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.assetstore.JsonAsset;

public class AssetBuilderCodec<K, T extends JsonAsset<K>> extends BuilderCodec<T> implements AssetCodec<K, T>
{
    public static final KeyedCodec<Map<String, String[]>> TAGS_CODEC;
    private static final String TAG_DOCUMENTATION = """
                                                    Tags are a general way to describe an asset that can be interpreted by other systems in a way they see fit.
                                                    
                                                    For example you could tag something with a **Material** tag with the values **Solid** and **Stone**, And another single tag **Ore**.
                                                    
                                                    Tags will be expanded into a single list of tags automatically. Using the above example with **Material** and **Ore** the end result would be the following list of tags: **Ore**, **Material**, **Solid**, **Stone**, **Material=Solid** and **Material=Stone**.""";
    @Nonnull
    protected final KeyedCodec<K> idCodec;
    @Nonnull
    protected final KeyedCodec<K> parentCodec;
    protected final BiConsumer<T, K> idSetter;
    protected final BiConsumer<T, AssetExtraInfo.Data> dataSetter;
    @Nonnull
    protected final Function<T, AssetExtraInfo.Data> dataGetter;
    
    protected AssetBuilderCodec(@Nonnull final Builder<K, T> builder) {
        super((BuilderBase<T, ?>)builder);
        this.idCodec = builder.idCodec;
        this.parentCodec = new KeyedCodec<K>("Parent", this.idCodec.getChildCodec());
        this.idSetter = builder.idSetter;
        this.dataSetter = builder.dataSetter;
        this.dataGetter = builder.dataGetter;
    }
    
    @Nonnull
    @Override
    public KeyedCodec<K> getKeyCodec() {
        return this.idCodec;
    }
    
    @Nonnull
    @Override
    public KeyedCodec<K> getParentCodec() {
        return this.parentCodec;
    }
    
    @Override
    public AssetExtraInfo.Data getData(final T t) {
        return this.dataGetter.apply(t);
    }
    
    @Override
    public T decodeJsonAsset(@Nonnull final RawJsonReader reader, @Nonnull final AssetExtraInfo<K> extraInfo) throws IOException {
        return this.decodeAndInheritJsonAsset(reader, null, extraInfo);
    }
    
    @Override
    public T decodeAndInheritJsonAsset(@Nonnull final RawJsonReader reader, @Nullable final T parent, @Nonnull final AssetExtraInfo<K> extraInfo) throws IOException {
        final T t = (T)this.supplier.get();
        this.dataSetter.accept(t, extraInfo.getData());
        if (parent != null) {
            this.inherit(t, parent, extraInfo);
        }
        this.decodeAndInheritJson0(reader, t, parent, extraInfo);
        this.idSetter.accept(t, extraInfo.getKey());
        this.afterDecodeAndValidate(t, extraInfo);
        return t;
    }
    
    @Nonnull
    @Override
    public ObjectSchema toSchema(@Nonnull final SchemaContext context) {
        return this.toSchema(context, (T)this.supplier.get());
    }
    
    @Nonnull
    @Override
    public ObjectSchema toSchema(@Nonnull final SchemaContext context, @Nullable final T def) {
        final ObjectSchema schema = super.toSchema(context, def);
        final KeyedCodec<K> parent = this.getParentCodec();
        final Schema parentSchema = parent.getChildCodec().toSchema(context);
        parentSchema.setMarkdownDescription("""
                                            When set this asset will inherit properties from the named asset.
                                            
                                            When inheriting from another **""" + this.tClass.getSimpleName() + "** most properties will simply be copied from the parent asset to this asset. In the case where both child and parent provide a field the child field will simply replace the value provided by the parent, in the case of nested structures this will apply to the fields within the structure. In some cases the field may decide to act differently, for example: by merging the parent and child fields together.");
        Class<? super T> rootClass = (Class<? super T>)this.tClass;
        for (BuilderCodec<? super T> rootCodec = this; rootCodec.getParent() != null; rootCodec = rootCodec.getParent(), rootClass = rootCodec.getInnerClass()) {}
        parentSchema.setHytaleParent(new Schema.InheritSettings(rootClass.getSimpleName()));
        final LinkedHashMap<String, Schema> props = new LinkedHashMap<String, Schema>();
        props.put(parent.getKey(), parentSchema);
        props.putAll((Map<?, ?>)schema.getProperties());
        schema.setProperties(props);
        return schema;
    }
    
    @Nonnull
    public static <K, T extends JsonAsset<K>> Builder<K, T> builder(final Class<T> tClass, final Supplier<T> supplier, final Codec<K> idCodec, final BiConsumer<T, K> idSetter, final Function<T, K> idGetter, final BiConsumer<T, AssetExtraInfo.Data> dataSetter, @Nonnull final Function<T, AssetExtraInfo.Data> dataGetter) {
        return new Builder<K, T>(tClass, supplier, idCodec, idSetter, idGetter, dataSetter, dataGetter);
    }
    
    @Nonnull
    public static <K, T extends JsonAsset<K>> Builder<K, T> builder(final Class<T> tClass, final Supplier<T> supplier, final BuilderCodec<? super T> parentCodec, final Codec<K> idCodec, final BiConsumer<T, K> idSetter, final Function<T, K> idGetter, final BiConsumer<T, AssetExtraInfo.Data> dataSetter, @Nonnull final Function<T, AssetExtraInfo.Data> dataGetter) {
        return new Builder<K, T>(tClass, supplier, parentCodec, idCodec, idSetter, idGetter, dataSetter, dataGetter);
    }
    
    @Nonnull
    public static <K, T extends JsonAsset<K>> AssetBuilderCodec<K, T> wrap(@Nonnull final BuilderCodec<T> codec, final Codec<K> idCodec, final BiConsumer<T, K> idSetter, final Function<T, K> idGetter, final BiConsumer<T, AssetExtraInfo.Data> dataSetter, @Nonnull final Function<T, AssetExtraInfo.Data> dataGetter) {
        return builder(codec.getInnerClass(), codec.getSupplier(), codec, idCodec, idSetter, idGetter, dataSetter, dataGetter).documentation(codec.getDocumentation()).build();
    }
    
    static {
        TAGS_CODEC = new KeyedCodec<Map<String, String[]>>("Tags", new MapCodec<String[], Object>(Codec.STRING_ARRAY, HashMap::new));
    }
    
    public static class Builder<K, T extends JsonAsset<K>> extends BuilderBase<T, Builder<K, T>>
    {
        @Nonnull
        protected final KeyedCodec<K> idCodec;
        protected final BiConsumer<T, K> idSetter;
        protected final BiConsumer<T, AssetExtraInfo.Data> dataSetter;
        @Nonnull
        protected final Function<T, AssetExtraInfo.Data> dataGetter;
        
        public Builder(final Class<T> tClass, final Supplier<T> supplier, final Codec<K> idCodec, final BiConsumer<T, K> idSetter, final Function<T, K> idGetter, final BiConsumer<T, AssetExtraInfo.Data> dataSetter, @Nonnull final Function<T, AssetExtraInfo.Data> dataGetter) {
            super(tClass, supplier);
            this.idCodec = new KeyedCodec<K>("Id", idCodec);
            this.idSetter = idSetter;
            this.dataSetter = dataSetter;
            this.dataGetter = dataGetter;
            this.appendInherited(AssetBuilderCodec.TAGS_CODEC, (t, tags) -> dataGetter.apply(t).putTags(tags), t -> {
                final AssetExtraInfo.Data data = dataGetter.apply(t);
                return (data != null) ? data.getRawTags() : null;
            }, (t, parent) -> {
                final AssetExtraInfo.Data data2 = dataGetter.apply(t);
                final AssetExtraInfo.Data parentData = dataGetter.apply(parent);
                if (data2 != null && parentData != null) {
                    data2.putTags(parentData.getRawTags());
                }
            }).documentation("""
                             Tags are a general way to describe an asset that can be interpreted by other systems in a way they see fit.
                             
                             For example you could tag something with a **Material** tag with the values **Solid** and **Stone**, And another single tag **Ore**.
                             
                             Tags will be expanded into a single list of tags automatically. Using the above example with **Material** and **Ore** the end result would be the following list of tags: **Ore**, **Material**, **Solid**, **Stone**, **Material=Solid** and **Material=Stone**.""").add();
        }
        
        public Builder(final Class<T> tClass, final Supplier<T> supplier, final BuilderCodec<? super T> parentCodec, final Codec<K> idCodec, final BiConsumer<T, K> idSetter, final Function<T, K> idGetter, final BiConsumer<T, AssetExtraInfo.Data> dataSetter, @Nonnull final Function<T, AssetExtraInfo.Data> dataGetter) {
            super(tClass, supplier, parentCodec);
            this.idCodec = new KeyedCodec<K>("Id", idCodec);
            this.idSetter = idSetter;
            this.dataSetter = dataSetter;
            this.dataGetter = dataGetter;
            this.appendInherited(AssetBuilderCodec.TAGS_CODEC, (t, tags) -> dataGetter.apply(t).putTags(tags), t -> {
                final AssetExtraInfo.Data data = dataGetter.apply(t);
                return (data != null) ? data.getRawTags() : null;
            }, (t, parent) -> {
                final AssetExtraInfo.Data data2 = dataGetter.apply(t);
                final AssetExtraInfo.Data parentData = dataGetter.apply(parent);
                if (data2 != null && parentData != null) {
                    data2.putTags(parentData.getRawTags());
                }
            }).documentation("""
                             Tags are a general way to describe an asset that can be interpreted by other systems in a way they see fit.
                             
                             For example you could tag something with a **Material** tag with the values **Solid** and **Stone**, And another single tag **Ore**.
                             
                             Tags will be expanded into a single list of tags automatically. Using the above example with **Material** and **Ore** the end result would be the following list of tags: **Ore**, **Material**, **Solid**, **Stone**, **Material=Solid** and **Material=Stone**.""").add();
        }
        
        @Nonnull
        @Override
        public AssetBuilderCodec<K, T> build() {
            return new AssetBuilderCodec<K, T>(this);
        }
    }
}
