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

package com.hypixel.hytale.server.npc.asset.builder;

import java.util.Iterator;
import java.util.LinkedHashMap;
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 com.google.gson.JsonObject;
import java.util.Collection;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import javax.annotation.Nullable;
import com.google.gson.JsonElement;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import com.hypixel.hytale.codec.schema.NamedSchema;
import com.hypixel.hytale.codec.schema.SchemaConvertable;

public class BuilderFactory<T> implements SchemaConvertable<Void>, NamedSchema
{
    public static final String DEFAULT_TYPE = "Type";
    public static final String COMPONENT_TYPE = "Component";
    private final String typeTag;
    private final Supplier<Builder<T>> defaultBuilder;
    private final Class<T> category;
    private final Map<String, Supplier<Builder<T>>> buildersSuppliers;
    
    public BuilderFactory(final Class<T> category, final String typeTag) {
        this(category, typeTag, null);
    }
    
    public BuilderFactory(final Class<T> category, final String typeTag, final Supplier<Builder<T>> defaultBuilder) {
        this.buildersSuppliers = new HashMap<String, Supplier<Builder<T>>>();
        this.category = category;
        this.typeTag = typeTag;
        this.defaultBuilder = defaultBuilder;
        this.add("Component", () -> new BuilderComponent(category));
    }
    
    @Nonnull
    public BuilderFactory<T> add(final String name, final Supplier<Builder<T>> builder) {
        if (this.buildersSuppliers.containsKey(name)) {
            throw new IllegalArgumentException(String.format("Builder with name %s already exists", name));
        }
        if (this.typeTag.isEmpty()) {
            throw new IllegalArgumentException("Can't add named builder to array builder factory");
        }
        this.buildersSuppliers.put(name, builder);
        return this;
    }
    
    public Class<T> getCategory() {
        return this.category;
    }
    
    public Builder<T> createBuilder(@Nonnull final JsonElement config) {
        if (config.isJsonObject()) {
            return this.createBuilder(config.getAsJsonObject(), this.typeTag);
        }
        if (this.defaultBuilder == null) {
            throw new IllegalArgumentException(String.format("Array builder must have default builder defined: %s", config));
        }
        return this.defaultBuilder.get();
    }
    
    public String getKeyName(@Nonnull final JsonElement config) {
        if (!config.isJsonObject()) {
            return "-";
        }
        final JsonElement element = config.getAsJsonObject().get(this.typeTag);
        return (element != null) ? element.getAsString() : "???";
    }
    
    @Nonnull
    public Builder<T> createBuilder(final String name) {
        if (!this.buildersSuppliers.containsKey(name)) {
            throw new IllegalArgumentException(String.format("Builder %s does not exist", name));
        }
        final Builder<T> builder = this.buildersSuppliers.get(name).get();
        if (builder.category() != this.getCategory()) {
            throw new IllegalArgumentException(String.format("Builder %s has category %s which does not match %s", name, builder.category().getName(), this.getCategory().getName()));
        }
        builder.setTypeName(name);
        return builder;
    }
    
    @Nullable
    public Builder<T> tryCreateDefaultBuilder() {
        return (this.defaultBuilder != null) ? this.defaultBuilder.get() : null;
    }
    
    @Nonnull
    public List<String> getBuilderNames() {
        return new ObjectArrayList<String>(this.buildersSuppliers.keySet());
    }
    
    private Builder<T> createBuilder(@Nonnull final JsonObject config, @Nonnull final String tag) {
        if (config == null) {
            throw new IllegalArgumentException("JSON config cannot be null when creating builder");
        }
        if (tag == null || tag.trim().isEmpty()) {
            throw new IllegalArgumentException(String.format("Tag cannot be null or empty when creating builder with content %s", config));
        }
        final JsonElement element = config.get(tag);
        if (element == null && this.defaultBuilder != null) {
            return this.defaultBuilder.get();
        }
        if (element == null) {
            throw new IllegalArgumentException(String.format("Builder tag of type %s must be supplied if no default is defined in %s", tag, config));
        }
        return this.createBuilder(element.getAsString());
    }
    
    @Nonnull
    @Override
    public String getSchemaName() {
        return "NPCType:" + this.getCategory().getSimpleName();
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        return this.toSchema(context, false);
    }
    
    @Nonnull
    public Schema toSchema(@Nonnull final SchemaContext context, final boolean isRoot) {
        int index = 0;
        final Schema[] schemas = new Schema[this.getBuilderNames().size()];
        final ObjectSchema check = new ObjectSchema();
        check.setRequired(this.typeTag);
        final StringSchema keys = new StringSchema();
        keys.setEnum(this.getBuilderNames().toArray(String[]::new));
        check.setProperties((Map<String, Schema>)Map.of(this.typeTag, keys));
        final Schema root = new Schema();
        if (this.defaultBuilder != null || !this.getBuilderNames().isEmpty()) {
            root.setIf(check);
            root.setThen(Schema.anyOf(schemas));
        }
        else {
            root.setAnyOf(schemas);
        }
        for (final String builderName : this.getBuilderNames()) {
            final Builder<T> builder = this.createBuilder(builderName);
            final Schema schemaRef = context.refDefinition(builder);
            final ObjectSchema schema = (ObjectSchema)context.getRawDefinition(builder);
            final LinkedHashMap<String, Schema> newProps = new LinkedHashMap<String, Schema>();
            final Schema type = StringSchema.constant(builderName);
            if (builder instanceof final BuilderBase builderBase) {
                type.setDescription(builderBase.getLongDescription());
            }
            newProps.put(this.typeTag, type);
            if (isRoot) {
                newProps.put("TestType", new StringSchema());
                newProps.put("FailReason", new StringSchema());
                newProps.put("Parameters", BuilderParameters.toSchema(context));
            }
            newProps.putAll((Map<?, ?>)schema.getProperties());
            schema.setProperties(newProps);
            final Schema cond = new Schema();
            final ObjectSchema checkType = new ObjectSchema();
            checkType.setProperties(Map.of(this.typeTag, StringSchema.constant(builderName)));
            checkType.setRequired(this.typeTag);
            cond.setIf(checkType);
            cond.setThen(schemaRef);
            cond.setElse(false);
            schemas[index++] = cond;
        }
        if (this.defaultBuilder != null) {
            final Builder<T> builder2 = this.defaultBuilder.get();
            final Schema schemaRef2 = context.refDefinition(builder2);
            root.setElse(schemaRef2);
        }
        else {
            root.setElse(false);
        }
        root.setHytaleSchemaTypeField(new Schema.SchemaTypeField(this.typeTag, null, (String[])this.getBuilderNames().toArray(String[]::new)));
        root.setTitle(this.getCategory().getSimpleName());
        return root;
    }
}
