package org.allaymc.server.registry.loader;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.SneakyThrows;
import org.allaymc.api.item.ItemStack;
import org.allaymc.api.item.recipe.*;
import org.allaymc.api.item.recipe.descriptor.ItemDescriptor;
import org.allaymc.api.registry.RegistryLoader;
import org.allaymc.api.utils.Utils;
import org.allaymc.api.utils.identifier.Identifier;
import org.allaymc.server.item.recipe.ComplexRecipe;
import org.allaymc.server.item.recipe.DecoratedPotRecipe;
import org.allaymc.server.item.recipe.FireworkRecipe;

import java.io.InputStreamReader;
import java.util.*;

/**
 * @author daoge_cmd
 */
public class RecipeRegistryLoader implements RegistryLoader<Void, Map<Identifier, Recipe>> {
    @SneakyThrows
    @Override
    public Map<Identifier, Recipe> load(Void $) {
        var recipes = new HashMap<Identifier, Recipe>();
        JsonObject obj;
        try (var reader = new InputStreamReader(Objects.requireNonNull(Utils.getResource("recipes.json")))) {
            obj = JsonParser.parseReader(reader).getAsJsonObject();
        }

        // Shaped
        var shapedRecipes = obj.getAsJsonArray("shaped");
        for (var shapedRecipe : shapedRecipes) {
            if (isDeprecated(shapedRecipe.getAsJsonObject())) {
                continue;
            }

            var recipe = parseShaped(shapedRecipe.getAsJsonObject());
            recipes.put(recipe.getIdentifier(), recipe);
        }

        // Shapeless
        var shapelessRecipes = obj.getAsJsonArray("shapeless");
        for (var shapelessRecipe : shapelessRecipes) {
            if (isDeprecated(shapelessRecipe.getAsJsonObject())) {
                continue;
            }

            var recipe = isFurnaceLike(shapelessRecipe.getAsJsonObject()) ?
                    parseFurnaceLikeShapeless(shapelessRecipe.getAsJsonObject()) :
                    parseShapeless(shapelessRecipe.getAsJsonObject());
            recipes.put(recipe.getIdentifier(), recipe);
        }

        // Smithing Transform
        var smithingTransformRecipes = obj.getAsJsonArray("smithingTransform");
        for (var smithingTransformRecipe : smithingTransformRecipes) {
            var recipe = parseSmithingTransform(smithingTransformRecipe.getAsJsonObject());
            recipes.put(recipe.getIdentifier(), recipe);
        }

        // Smithing Trim
        var smithingTrimRecipes = obj.getAsJsonArray("smithingTrim");
        for (var smithingTrimRecipe : smithingTrimRecipes) {
            var recipe = parseSmithingTrim(smithingTrimRecipe.getAsJsonObject());
            recipes.put(recipe.getIdentifier(), recipe);
        }

        // Furnace
        var furnaceRecipeElement = obj.get("furnaceAux");
        if (furnaceRecipeElement != null && !furnaceRecipeElement.isJsonNull()) {
            var furnaceRecipes = furnaceRecipeElement.getAsJsonArray();
            for (var furnaceRecipe : furnaceRecipes) {
                if (isDeprecated(furnaceRecipe.getAsJsonObject())) {
                    continue;
                }

                var recipe = parseFurnace(furnaceRecipe.getAsJsonObject());
                recipes.put(recipe.getIdentifier(), recipe);
            }
        }

        // Potion
        var potionRecipes = obj.getAsJsonArray("potionMixes");
        for (var potionRecipe : potionRecipes) {
            var recipe = parsePotion(potionRecipe.getAsJsonObject());
            recipes.put(recipe.getIdentifier(), recipe);
        }

        // Complex
        var complexRecipes = new HashSet<ComplexRecipe>();
        complexRecipes.add(FireworkRecipe.INSTANCE);
        complexRecipes.add(DecoratedPotRecipe.INSTANCE);
        complexRecipes.forEach(complexRecipe -> recipes.put(complexRecipe.getIdentifier(), complexRecipe));

        return recipes;
    }

    protected static boolean isDeprecated(JsonObject obj) {
        return obj.has("tag") && obj.get("tag").getAsString().equals("deprecated");
    }

    protected static boolean isFurnaceLike(JsonObject obj) {
        if (!obj.has("tag")) {
            return false;
        }

        return switch (obj.get("tag").getAsString()) {
            case "furnace", "blast_furnace", "smoker", "campfire", "soul_campfire" -> true;
            default -> false;
        };
    }

    protected static SmithingTrimRecipe parseSmithingTrim(JsonObject obj) {
        return new SmithingTrimRecipe(
                new Identifier(obj.get("id").getAsString()),
                null,
                obj.has("priority") ? obj.get("priority").getAsInt() : 0,
                RecipeJsonUtils.parseItemDescriptor(obj.getAsJsonObject("template")),
                RecipeJsonUtils.parseItemDescriptor(obj.getAsJsonObject("base")),
                RecipeJsonUtils.parseItemDescriptor(obj.getAsJsonObject("addition"))
        );
    }

    protected static SmithingTransformRecipe parseSmithingTransform(JsonObject obj) {
        return new SmithingTransformRecipe(
                new Identifier(obj.get("id").getAsString()),
                RecipeJsonUtils.parseOutputs(obj).toArray(ItemStack[]::new),
                obj.has("priority") ? obj.get("priority").getAsInt() : 0,
                RecipeJsonUtils.parseItemDescriptor(obj.getAsJsonObject("template")),
                RecipeJsonUtils.parseItemDescriptor(obj.getAsJsonObject("base")),
                RecipeJsonUtils.parseItemDescriptor(obj.getAsJsonObject("addition"))
        );
    }

    protected static ShapelessRecipe parseShapeless(JsonObject obj) {
        // Ingredients
        List<ItemDescriptor> ingredients = new ArrayList<>();
        obj.getAsJsonArray("input").forEach(item -> {
            ingredients.add(RecipeJsonUtils.parseItemDescriptor(item.getAsJsonObject()));
        });

        return new ShapelessRecipe(
                new Identifier(obj.get("id").getAsString()),
                RecipeJsonUtils.parseOutputs(obj).toArray(ItemStack[]::new),
                obj.has("priority") ? obj.get("priority").getAsInt() : 0,
                ingredients.toArray(ItemDescriptor[]::new),
                switch (obj.get("tag").getAsString()) {
                    case "crafting_table" -> ShapelessRecipe.Type.CRAFTING;
                    case "stonecutter" -> ShapelessRecipe.Type.STONECUTTER;
                    case "cartography_table" -> ShapelessRecipe.Type.CARTOGRAPHY_TABLE;
                    default -> throw new IllegalStateException("Unhandled tag for shapeless recipe: " + obj.get("tag").getAsString());
                }
        );
    }

    protected static FurnaceRecipe parseFurnaceLikeShapeless(JsonObject obj) {
        var input = obj.getAsJsonArray("input").get(0).getAsJsonObject();

        return new FurnaceRecipe(
                RecipeJsonUtils.parseItemDescriptor(input),
                RecipeJsonUtils.parseOutput(obj.getAsJsonArray("output").get(0).getAsJsonObject()),
                obj.has("priority") ? obj.get("priority").getAsInt() : 0,
                FurnaceRecipe.Type.valueOf(obj.get("tag").getAsString().toUpperCase(Locale.ROOT))
        );
    }

    protected static ShapedRecipe parseShaped(JsonObject obj) {
        // Pattern
        List<String> patternList = new ArrayList<>();
        obj.getAsJsonArray("pattern").forEach(line -> {
            patternList.add(line.getAsString());
        });

        // Keys
        Map<Character, ItemDescriptor> keys = new HashMap<>();
        obj.getAsJsonObject("input").entrySet().forEach(k -> {
            // the length of k.getKey() must be 1 so we can safely convert it to a char
            var key = k.getKey().charAt(0);
            var itemDescriptor = RecipeJsonUtils.parseItemDescriptor(k.getValue().getAsJsonObject());
            keys.put(key, itemDescriptor);
        });

        return new ShapedRecipe(
                new Identifier(obj.get("id").getAsString()),
                RecipeJsonUtils.parseOutputs(obj).toArray(ItemStack[]::new),
                obj.has("priority") ? obj.get("priority").getAsInt() : 0,
                ShapedRecipe.PatternHelper.build(patternList),
                keys
        );
    }

    protected static FurnaceRecipe parseFurnace(JsonObject obj) {
        return new FurnaceRecipe(
                RecipeJsonUtils.parseItemDescriptor(obj.getAsJsonObject("input")),
                RecipeJsonUtils.parseOutput(obj.getAsJsonObject("output")),
                obj.has("priority") ? obj.get("priority").getAsInt() : 0,
                FurnaceRecipe.Type.valueOf(obj.get("tag").getAsString().toUpperCase(Locale.ROOT))
        );
    }

    protected static PotionRecipe parsePotion(JsonObject obj) {
        return new PotionRecipe(
                RecipeJsonUtils.parseItemStack(obj.getAsJsonObject("input")),
                RecipeJsonUtils.parseItemStack(obj.getAsJsonObject("output")),
                RecipeJsonUtils.parseItemStack(obj.getAsJsonObject("reagent"))
        );
    }
}
