package com.gregtechceu.gtceu.api.recipe;

import com.gregtechceu.gtceu.core.mixins.ShapedRecipeAccessor;

import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.level.Level;

import com.google.gson.JsonObject;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;

import java.util.Map;

import javax.annotation.ParametersAreNonnullByDefault;

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public class StrictShapedRecipe extends ShapedRecipe {

    public static final RecipeSerializer<StrictShapedRecipe> SERIALIZER = new Serializer();

    @Getter
    private final boolean matchSize;

    public StrictShapedRecipe(ResourceLocation id, String group, CraftingBookCategory category, int width, int height,
                              NonNullList<Ingredient> recipeItems, ItemStack result, boolean matchSize) {
        super(id, group, category, width, height, recipeItems, result);
        this.matchSize = matchSize;
    }

    @Override
    public boolean matches(CraftingContainer inv, Level level) {
        if (matchSize && (inv.getWidth() != this.getWidth() || inv.getHeight() != this.getHeight())) return false;
        for (int i = 0; i <= inv.getWidth() - this.getWidth(); ++i) {
            for (int j = 0; j <= inv.getHeight() - this.getHeight(); ++j) {
                if (this.matches(inv, i, j)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Checks if the region of a crafting inventory is match for the recipe.
     */
    private boolean matches(CraftingContainer craftingInventory, int width, int height) {
        for (int i = 0; i < craftingInventory.getWidth(); ++i) {
            for (int j = 0; j < craftingInventory.getHeight(); ++j) {
                int k = i - width;
                int l = j - height;
                Ingredient ingredient = Ingredient.EMPTY;
                if (k >= 0 && l >= 0 && k < this.getWidth() && l < this.getHeight()) {
                    ingredient = this.getIngredients().get(k + l * this.getWidth());
                }
                if (ingredient.test(craftingInventory.getItem(i + j * craftingInventory.getWidth()))) continue;
                return false;
            }
        }
        return true;
    }

    @Override
    public @NotNull RecipeSerializer<?> getSerializer() {
        return SERIALIZER;
    }

    public static class Serializer implements RecipeSerializer<StrictShapedRecipe> {

        @Override
        public StrictShapedRecipe fromJson(ResourceLocation recipeId, JsonObject json) {
            String string = GsonHelper.getAsString(json, "group", "");
            CraftingBookCategory craftingBookCategory = CraftingBookCategory.CODEC
                    .byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC);
            Map<String, Ingredient> map = ShapedRecipeAccessor.callKeyFromJson(GsonHelper.getAsJsonObject(json, "key"));
            String[] strings = ShapedRecipeAccessor.callPatternFromJson(GsonHelper.getAsJsonArray(json, "pattern"));
            int i = strings[0].length();
            int j = strings.length;
            NonNullList<Ingredient> nonNullList = ShapedRecipeAccessor.callDissolvePattern(strings, map, i, j);
            ItemStack itemStack = StrictShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
            boolean matchSize = json.get("matchSize").getAsBoolean();
            return new StrictShapedRecipe(recipeId, string, craftingBookCategory, i, j, nonNullList, itemStack,
                    matchSize);
        }

        @Override
        public StrictShapedRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) {
            int i = buffer.readVarInt();
            int j = buffer.readVarInt();
            String string = buffer.readUtf();
            CraftingBookCategory craftingBookCategory = buffer.readEnum(CraftingBookCategory.class);
            NonNullList<Ingredient> nonNullList = NonNullList.withSize(i * j, Ingredient.EMPTY);
            nonNullList.replaceAll(ignored -> Ingredient.fromNetwork(buffer));
            ItemStack itemStack = buffer.readItem();
            boolean matchSize = buffer.readBoolean();
            return new StrictShapedRecipe(recipeId, string, craftingBookCategory, i, j, nonNullList, itemStack,
                    matchSize);
        }

        @Override
        public void toNetwork(FriendlyByteBuf buffer, StrictShapedRecipe recipe) {
            buffer.writeVarInt(recipe.getWidth());
            buffer.writeVarInt(recipe.getHeight());
            buffer.writeUtf(recipe.getGroup());
            buffer.writeEnum(recipe.category());
            for (Ingredient ingredient : recipe.getIngredients()) {
                ingredient.toNetwork(buffer);
            }
            buffer.writeItem(((ShapedRecipeAccessor) recipe).getResult());
            buffer.writeBoolean(recipe.matchSize);
        }
    }
}
