package com.agentdid127.resourcepack.forwards.impl.other;

import com.agentdid127.resourcepack.library.Converter;
import com.agentdid127.resourcepack.library.PackConverter;
import com.agentdid127.resourcepack.library.pack.Pack;
import com.agentdid127.resourcepack.library.utilities.FileUtil;
import com.agentdid127.resourcepack.library.utilities.Logger;
import com.agentdid127.resourcepack.library.utilities.Mapping;
import com.agentdid127.resourcepack.library.utilities.Util;
import com.google.gson.Gson;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

public class NameConverter extends Converter {
    private final int from;
    private final int to;

    private final Mapping entityMapping;

    private final Mapping blockMapping_1_13;
    private final Mapping itemMapping_1_13;

    private final Mapping blockMapping_1_14;
    private final Mapping itemMapping_1_14;

    private final Mapping blockMapping_1_17;
    private final Mapping itemMapping_1_17;

    private final Mapping blockMapping_1_19;

    private final Mapping blockMapping_1_20_3;
    private final Mapping itemMapping1_20_3;

    public NameConverter(PackConverter packConverter, int from, int to) {
        super(packConverter);

        this.from = from;
        this.to = to;

        Gson gson = packConverter.getGson();
        entityMapping = new Mapping(gson, "entities", "*", false);

//      TODO/(Not used):  langMapping_1_13 = new Mapping(gson, "lang", "1_13", false);
        blockMapping_1_13 = new Mapping(gson, "blocks", "1_13", false);
        itemMapping_1_13 = new Mapping(gson, "items", "1_13", false);

//      TODO/(Not used):  langMapping_1_14 = new Mapping(gson, "lang", "1_14", false);
        blockMapping_1_14 = new Mapping(gson, "blocks", "1_14", false);
        itemMapping_1_14 = new Mapping(gson, "items", "1_14", false);

        blockMapping_1_17 = new Mapping(gson, "blocks", "1_17", false);
        itemMapping_1_17 = new Mapping(gson, "items", "1_17", false);

        blockMapping_1_19 = new Mapping(gson, "blocks", "1_19", false);

        blockMapping_1_20_3 = new Mapping(gson, "blocks", "1_20_3", false);
        itemMapping1_20_3 = new Mapping(gson, "items", "1_20_3", false);
    }

    @Override
    public boolean shouldConvert(Gson gson, int from, int to) {
        return true;
    }

    /**
     * Fixes folder names and file names
     *
     * @param pack Resource Pack
     * @throws IOException Error handler
     */
    @Override
    public void convert(Pack pack) throws IOException {
        Path minecraftPath = pack.getWorkingPath().resolve("assets/minecraft".replace("/", File.separator));
        Logger.addTab();
        // Less than 1.12
        if (from <= Util.getVersionProtocol(packConverter.getGson(), "1.12.2")
                && to > Util.getVersionProtocol(packConverter.getGson(), "1.13")) {
            Logger.debug("Finding files that are less than 1.12");
            findFiles(minecraftPath);
        }

        // Version is greater than 1.13
        if (to >= Util.getVersionProtocol(packConverter.getGson(), "1.13")) {
            // OptiFine conversion
            if (minecraftPath.resolve("mcpatcher").toFile().exists()) {
                Logger.debug("MCPatcher exists, switching to optifine");
                if (minecraftPath.resolve("optifine").toFile().exists()) {
                    Logger.debug("OptiFine exists, merging directories");
                    FileUtil.mergeDirectories(minecraftPath.resolve("optifine").toFile(), minecraftPath.resolve("mcpatcher").toFile());
                } else {
                    Files.move(minecraftPath.resolve("mcpatcher"), minecraftPath.resolve("optifine"));
                }

                if (minecraftPath.resolve("mcpatcher").toFile().exists()) {
                    FileUtil.deleteDirectoryAndContents(minecraftPath.resolve("mcpatcher"));
                }
            }

            // 1.13 Models
            Path modelsPath = pack.getWorkingPath().resolve("assets/minecraft/models".replace("/", File.separator));
            if (modelsPath.toFile().exists()) {
                // 1.13 block/item name change
                if (modelsPath.resolve("blocks").toFile().exists()) {
                    if (modelsPath.resolve("block").toFile().exists()) {
                        FileUtil.deleteDirectoryAndContents(modelsPath.resolve("block"));
                    }

                    Files.move(modelsPath.resolve("blocks"), modelsPath.resolve("block"));
                }

                // Update all blocks for 1.13
                renameAll(blockMapping_1_13, ".json", modelsPath.resolve("block"));
                if (modelsPath.resolve("items").toFile().exists()) {
                    if (modelsPath.resolve("item").toFile().exists()) {
                        FileUtil.deleteDirectoryAndContents(modelsPath.resolve("item"));
                    }

                    Files.move(modelsPath.resolve("items"), modelsPath.resolve("item"));
                }

                // Update all items for 1.13
                renameAll(itemMapping_1_13, ".json", modelsPath.resolve("item"));

                // Update 1.14 items
                if (to > Util.getVersionProtocol(packConverter.getGson(), "1.13")) {
                    Path itemModelPath = modelsPath.resolve("iem");
                    renameAll(itemMapping_1_14, ".json", itemModelPath);

                    if (!itemModelPath.resolve("ink_sac.json").toFile().exists()
                            && itemModelPath.resolve("black_dye.json").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("black_dye.json"), itemModelPath.resolve("ink_sac.json"));
                    }

                    if (!itemModelPath.resolve("cocoa_beans.json").toFile().exists()
                            && itemModelPath.resolve("brown_dye.json").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("brown_dye.json"), itemModelPath.resolve("cocoa_beans.json"));
                    }

                    if (!itemModelPath.resolve("bone_meal.json").toFile().exists()
                            && itemModelPath.resolve("white_dye.json").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("white_dye.json"), itemModelPath.resolve("bone_meal.json"));
                    }

                    if (!itemModelPath.resolve("lapis_lazuli.json").toFile().exists()
                            && itemModelPath.resolve("blue_dye.json").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("blue_dye.json"), itemModelPath.resolve("lapis_lazuli.json"));
                    }
                }

                if (to > Util.getVersionProtocol(packConverter.getGson(), "1.19")) {
                    renameAll(blockMapping_1_19, ".json", modelsPath.resolve("block"));
                }

                if (to > Util.getVersionProtocol(packConverter.getGson(), "1.20.3")) {
                    renameAll(itemMapping1_20_3, ".json", modelsPath.resolve("item"));
                    renameAll(blockMapping_1_20_3, ".json", modelsPath.resolve("block"));
                }
            }

            // Update BlockStates
            Path blockStates = pack.getWorkingPath().resolve("assets/minecraft/blockstates".replace("/", File.separator));
            if (blockStates.toFile().exists()) {
                renameAll(blockMapping_1_13, ".json", blockStates);
            }

            // Update textures
            Path texturesPath = pack.getWorkingPath().resolve("assets/minecraft/textures".replace("/", File.separator));
            if (texturesPath.toFile().exists()) {
                Path blockPath = texturesPath.resolve("block");
                if (texturesPath.resolve("blocks").toFile().exists()) {
                    Files.move(texturesPath.resolve("blocks"), blockPath);
                }

                if (from < Util.getVersionProtocol(packConverter.getGson(), "1.13")) {
                    renameAll(blockMapping_1_13, ".png", blockPath);
                    renameAll(blockMapping_1_13, ".png.mcmeta", blockPath);
                }

                if (to > Util.getVersionProtocol(packConverter.getGson(), "1.13")) {
                    renameAll(blockMapping_1_14, ".png", blockPath);
                    renameAll(blockMapping_1_14, ".png.mcmeta", blockPath);
                }

                Path itemPath = texturesPath.resolve("item");
                if (from < Util.getVersionProtocol(packConverter.getGson(), "1.13")) {
                    renameAll(itemMapping_1_13, ".png", itemPath);
                    renameAll(itemMapping_1_13, ".png.mcmeta", itemPath);
                }

                if (to > Util.getVersionProtocol(packConverter.getGson(), "1.13.2")) {
                    renameAll(itemMapping_1_14, ".png", itemPath);
                    renameAll(itemMapping_1_14, ".png.mcmeta", itemPath);

                    Path itemModelPath = modelsPath.resolve("item");
                    if (!itemModelPath.resolve("ink_sac.png").toFile().exists()
                            && itemModelPath.resolve("black_dye.png").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("black_dye.png"), itemModelPath.resolve("ink_sac.png"));
                    }

                    if (!itemModelPath.resolve("cocoa_beans.png").toFile().exists()
                            && itemModelPath.resolve("brown_dye.png").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("brown_dye.png"), itemModelPath.resolve("cocoa_beans.png"));
                    }

                    if (!itemModelPath.resolve("bone_meal.png").toFile().exists()
                            && itemModelPath.resolve("white_dye.png").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("white_dye.png"), itemModelPath.resolve("bone_meal.png"));
                    }

                    if (!itemModelPath.resolve("lapis_lazuli.png").toFile().exists()
                            && itemModelPath.resolve("blue_dye.png").toFile().exists()) {
                        Files.copy(itemModelPath.resolve("blue_dye.png"), itemModelPath.resolve("lapis_lazuli.png"));
                    }
                }

                // Entities
                Path entityPath = texturesPath.resolve("entity");

                // 1.16 Iron golems
                if (from < Util.getVersionProtocol(packConverter.getGson(), "1.15")
                        && to >= Util.getVersionProtocol(packConverter.getGson(), "1.15")) {
                    if (!entityPath.resolve("iron_golem").toFile().exists()) {
                        entityPath.resolve("iron_golem").toFile().mkdir();
                    }

                    if (entityPath.resolve("iron_golem.png").toFile().exists()) {
                        Path newIronGolemPath = entityPath.resolve("iron_golem/iron_golem.png");
                        if (newIronGolemPath.toFile().exists()) {
                            newIronGolemPath.toFile().delete();
                        }

                        Files.move(entityPath.resolve("iron_golem.png"), newIronGolemPath);
                    }
                }

                // 1.17 Squid
                if (to >= Util.getVersionProtocol(packConverter.getGson(), "1.17")
                        && from < Util.getVersionProtocol(packConverter.getGson(), "1.17")) {
                    renameAll(blockMapping_1_17, ".png", blockPath);
                    renameAll(itemMapping_1_17, ".png", itemPath);
                    renameAll(blockMapping_1_17, ".png", modelsPath.resolve("block"));
                    renameAll(itemMapping_1_17, ".png", modelsPath.resolve("item"));

                    Path squidFolderPath = entityPath.resolve("squid");
                    if (!squidFolderPath.toFile().exists()) {
                        squidFolderPath.toFile().mkdir();
                    }

                    if (entityPath.resolve("squid.png").toFile().exists()) {
                        Path newSquidPath = squidFolderPath.resolve("squid.png");
                        if (newSquidPath.toFile().exists()) {
                            newSquidPath.toFile().delete();
                        }

                        Files.move(entityPath.resolve("squid.png"), newSquidPath);
                    }
                }

                if (to >= Util.getVersionProtocol(packConverter.getGson(), "1.19")
                        && from < Util.getVersionProtocol(packConverter.getGson(), "1.19")) {
                    renameAll(blockMapping_1_19, ".png", blockPath);
                }

                // 1.13 End Crystals
                if (entityPath.resolve("endercrystal").toFile().exists()
                        && !entityPath.resolve("end_crystal").toFile().exists()) {
                    Path newEnderCrystalPath = entityPath.resolve("end_crystal");
                    if (newEnderCrystalPath.toFile().exists()) {
                        newEnderCrystalPath.toFile().delete();
                    }

                    Files.move(entityPath.resolve("endercrystal"), newEnderCrystalPath);
                }

                findEntityFiles(entityPath);
            }
        }
        Logger.subTab();
    }

    /**
     * Finds files in entity folder
     *
     * @param path The input path
     * @throws IOException The IO exception
     */
    protected void findEntityFiles(Path path) throws IOException {
        if (path.toFile().exists()) {
            File directory = path.toFile();
            for (File file : Objects.requireNonNull(directory.listFiles())) {
                if (file.isDirectory()) {
                    renameAll(entityMapping, ".png", file.toPath());
                    renameAll(entityMapping, ".png.mcmeta", file.toPath());
                    findEntityFiles(file.toPath());
                }
            }
        }
    }

    /**
     * Finds files in folders called
     * @param path The input path
     */
    protected void findFiles(Path path) {
        if (path.toFile().exists()) {
            File directory = path.toFile();
            for (File file : Objects.requireNonNull(directory.listFiles())) {
                if (file.isDirectory()) {
                    if (file.getName().equals("items")) {
                        Logger.debug("Found Items folder, renaming..");
                        FileUtil.renameFile(path.resolve(file.getName()), file.getName().replaceAll("items", "item"));
                    }

                    if (file.getName().equals("blocks")) {
                        Logger.debug("Found blocks folder, renaming..");
                        FileUtil.renameFile(path.resolve(file.getName()), file.getName().replaceAll("blocks", "block"));
                    }

                    findFiles(file.toPath());
                }

                if (file.getName().contains("(")) {
                    FileUtil.renameFile(path.resolve(file.getName()), file.getName().replaceAll("[()]", ""));
                }

                if (!file.getName().equals(file.getName().toLowerCase())) {
                    Logger.debug("Renamed: " + file.getName() + "->" + file.getName().toLowerCase());
                }

                FileUtil.renameFile(path.resolve(file.getName()), file.getName().toLowerCase());
            }
        }
    }

    /**
     * Renames folder
     *
     * @param mapping The input mapping
     * @param extension The file extension
     * @param path The input path
     * @throws IOException The IO exception
     */
    protected void renameAll(Mapping mapping, String extension, Path path) throws IOException {
        if (path.toFile().exists()) {
            // remap grass blocks in order due to the cyclical way their names have changed,
            // i.e grass -> grass_block, tall_grass -> grass, double_grass -> tall_grass
            List<String> grasses = Arrays.asList("grass", "tall_grass", "double_grass");
            if (from <= Util.getVersionProtocol(packConverter.getGson(), "1.12.2")) {
                if ((path.endsWith("blockstates") || path.endsWith("textures/block"))) {
                    grasses.forEach(name -> {
                        String newName = mapping.remap(name);
                        Boolean ret = FileUtil.renameFile(Paths.get(path + File.separator + name + extension), newName + extension);
                        if (ret == null) {
                            return;
                        }

                        if (ret) {
                            Logger.debug("Renamed: " + name + extension + "->" + newName + extension);
                        } else if (!ret) {
                            Logger.log("Failed to rename: " + name + extension + "->" + newName + extension);
                        }
                    });
                }
            }

            // remap snow jsons, but not images.
            if (from < Util.getVersionProtocol(packConverter.getGson(), "1.13")
                    && to >= Util.getVersionProtocol(packConverter.getGson(), "1.13")) {
                if (path.resolve("snow_layer.json").toFile().exists()) {
                    FileUtil.renameFile(path.resolve("snow_layer" + extension), "snow" + extension);
                }
            }

            try (Stream<Path> pathStream = Files.list(path).filter(path1 -> path1.toString().endsWith(extension))) {
                pathStream.forEach(path1 -> {
                    String baseName = path1.getFileName().toString().substring(0, path1.getFileName().toString().length() - extension.length());

                    // skip the already renamed grass blocks
                    if (grasses.contains(baseName) && (path.endsWith("blockstates") || path.endsWith("textures/block"))) {
                        return;
                    }

                    String remappedName = mapping.remap(baseName);
                    if (remappedName != null && !remappedName.equals(baseName)) {
                        Path newPath = path1.getParent().resolve(remappedName + extension);
                        if (newPath.toFile().exists()) {
                            // Same note from Slicer.java line 38
                            newPath.toFile().delete();
                        }

                        Boolean renameSuccess = FileUtil.renameFile(path1, newPath);
                        if (renameSuccess == null) {
                            return;
                        }

                        if (renameSuccess) {
                            Logger.debug("Renamed: " + path1.getFileName().toString() + "->" + newPath.getFileName());
                        } else if (!renameSuccess) {
                            Logger.log("Failed to rename: " + path1.getFileName().toString() + "->" + newPath.getFileName());
                        }
                    }
                });
            }
        }
    }

    public Mapping getBlockMapping_1_13() {
        return blockMapping_1_13;
    }

    public Mapping getItemMapping_1_13() {
        return itemMapping_1_13;
    }

    public Mapping getBlockMapping_1_14() {
        return blockMapping_1_14;
    }

    public Mapping getItemMapping_1_14() {
        return itemMapping_1_14;
    }

    public Mapping getItemMapping_1_17() {
        return itemMapping_1_17;
    }

    public Mapping getBlockMapping_1_17() {
        return blockMapping_1_17;
    }

    public Mapping getBlockMapping_1_19() {
        return blockMapping_1_19;
    }
}