package codechicken.lib.datagen.recipe;

import com.google.gson.JsonObject;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.ICriterionInstance;
import net.minecraft.advancements.IRequirementsStrategy;
import net.minecraft.advancements.criterion.*;
import net.minecraft.data.IFinishedRecipe;
import net.minecraft.item.Item;
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.tags.Tag;
import net.minecraft.util.IItemProvider;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StringUtils;

import javax.annotation.Nullable;

import java.util.HashSet;
import java.util.Set;

import static codechicken.lib.util.SneakyUtils.unsafeCast;

/**
 * Created by covers1624 on 28/12/20.
 */
public abstract class AbstractRecipeBuilder<R, T extends AbstractRecipeBuilder<R, T>> implements RecipeBuilder {

    protected final Throwable created = new Throwable("Created at");
    protected final Advancement.Builder advancementBuilder = Advancement.Builder.builder();
    protected final IRecipeSerializer<?> serializer;
    protected final ResourceLocation id;
    protected final R result;
    private final Set<IItemProvider> criteriaItems = new HashSet<>();
    private final Set<Tag<Item>> criteriaTags = new HashSet<>();
    private int criteriaCounter = 0;
    protected boolean generateCriteria = false;
    private String group;

    protected AbstractRecipeBuilder(IRecipeSerializer<?> serializer, ResourceLocation id, R result) {
        this.serializer = serializer;
        this.id = id;
        this.result = result;
    }

    protected T getThis() {
        return unsafeCast(this);
    }

    protected abstract ResourceLocation getAdvancementId();

    public T autoCriteria() {
        generateCriteria = true;
        return getThis();
    }

    public T setGroup(String group) {
        this.group = group;
        return getThis();
    }

    public T addCriterion(String name, ICriterionInstance criterion) {
        advancementBuilder.withCriterion(name, criterion);
        return getThis();
    }

    @Override
    public final ResourceLocation getId() {
        return id;
    }

    @Override
    public final IFinishedRecipe build() {
        validate();
        advancementBuilder.withParentId(new ResourceLocation("recipes/root"))
                .withCriterion("has_the_recipe", RecipeUnlockedTrigger.create(id))
                .withRewards(AdvancementRewards.Builder.recipe(id))
                .withRequirementsStrategy(IRequirementsStrategy.OR);
        return _build();
    }

    protected void validate() {
        if (advancementBuilder.getCriteria().isEmpty()) {
            throw new IllegalStateException("No way of obtaining recipe " + id, created);
        }

    }

    protected abstract AbstractFinishedRecipe _build();

    protected void addAutoCriteria(IItemProvider item) {
        if (generateCriteria && criteriaItems.add(item)) {
            addCriterion("has_ingredient_" + criteriaCounter++, hasItem(item));
        }
    }

    protected void addAutoCriteria(Tag<Item> tag) {
        if (generateCriteria && criteriaTags.add(tag)) {
            addCriterion("has_ingredient_" + criteriaCounter++, hasItem(tag));
        }
    }

    protected InventoryChangeTrigger.Instance hasItem(IItemProvider itemIn) {
        return this.hasItem(ItemPredicate.Builder.create().item(itemIn).build());
    }

    protected InventoryChangeTrigger.Instance hasItem(Tag<Item> tagIn) {
        return this.hasItem(ItemPredicate.Builder.create().tag(tagIn).build());
    }

    protected InventoryChangeTrigger.Instance hasItem(ItemPredicate... predicates) {
        return new InventoryChangeTrigger.Instance(EntityPredicate.AndPredicate.ANY_AND, MinMaxBounds.IntBound.UNBOUNDED, MinMaxBounds.IntBound.UNBOUNDED, MinMaxBounds.IntBound.UNBOUNDED, predicates);
    }

    public abstract class AbstractFinishedRecipe implements IFinishedRecipe {

        @Override
        public void serialize(JsonObject json) {
            if (!StringUtils.isNullOrEmpty(group)) {
                json.addProperty("group", group);
            }
        }

        @Override
        public IRecipeSerializer<?> getSerializer() {
            return serializer;
        }

        @Override
        public ResourceLocation getID() {
            return id;
        }

        @Nullable
        @Override
        public JsonObject getAdvancementJson() {
            return advancementBuilder.serialize();
        }

        @Nullable
        @Override
        public ResourceLocation getAdvancementID() {
            return AbstractRecipeBuilder.this.getAdvancementId();
        }
    }
}

@@ -647,17 +647,16 @@
 llable;%0A
-%0A
 import j
@@ -1421,16 +1421,63 @@
  false;%0A
+    protected boolean enableUnlocking = false;%0A
     priv
@@ -1819,24 +1819,122 @@
 ementId();%0A%0A
+    public T enableUnlocking() %7B%0A        enableUnlocking = true;%0A        return getThis();%0A    %7D%0A%0A
     public T
@@ -1947,24 +1947,51 @@
 riteria() %7B%0A
+        enableUnlocking();%0A
         gene
@@ -2210,24 +2210,170 @@
 riterion) %7B%0A
+        if (!enableUnlocking) %7B%0A            throw new IllegalStateException(%22Recipe unlocking must be enabled with 'enableUnlocking'%22);%0A        %7D%0A
         adva
@@ -2609,24 +2609,59 @@
 validate();%0A
+        if (enableUnlocking) %7B%0A    
         adva
@@ -2738,24 +2738,28 @@
             
+   
+ 
 .withCriteri
@@ -2809,24 +2809,28 @@
 create(id))%0A
+    
             
@@ -2897,24 +2897,28 @@
             
+    
 .withRequire
@@ -2958,16 +2958,26 @@
 gy.OR);%0A
+        %7D%0A
         
@@ -3044,16 +3044,35 @@
     if (
+enableUnlocking && 
 advancem
@@ -4897,16 +4897,34 @@
   return
+ enableUnlocking ?
  advance
@@ -4946,16 +4946,23 @@
 ialize()
+ : null
 ;%0A      

Make recipe unlocking in datagen explicitly enabled only