package eu.mhsl.minecraft.pixelpics.render.registry;

import com.google.gson.Gson;
import eu.mhsl.minecraft.pixelpics.Main;
import eu.mhsl.minecraft.pixelpics.render.model.AbstractModel;
import eu.mhsl.minecraft.pixelpics.render.model.Model;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.*;

import static eu.mhsl.minecraft.pixelpics.render.registry.DefaultModelRegistry.TEXTURE_SIZE;

public class AdvancedModelRegistry implements ModelRegistry {
    private final Gson gson = new Gson();

    private final Map<Material, Map<BlockData, Model>> modelMap = new HashMap<>();
    private final Set<String> tintedBlocks = Set.of("grass", "grass_block", "leaves", "oak_leaves", "water", "vine", "sugar_cane");

    public record BlockInfo(String parent, BlockTextures textures){}
    public record BlockTextures(
        String texture,
        String bottom,
        String top,
        String all,
        String particle,
        String end,
        String side,
        String cross,
        String rail,
        String overlay
    ){}

    @Override
    public void initialize() {
        System.out.println(modelMap);

        File blockDir = new File(Main.getInstance().getDataFolder(), "models/block");
        for (File file : Objects.requireNonNull(blockDir.listFiles())) {
            addModelFromFile(file);
        }

        try {
            registerModel(Material.LAVA, AbstractModel.Builder.createSimple(getTextureArray("lava_still"))
                .transparency(0.15).reflection(0.05).occlusion().build());
            registerModel(Material.WATER, AbstractModel.Builder.createSimple(getTextureArray("water_still"))
                .transparency(0.60).reflection(0.1).occlusion().build());
        } catch (Exception ignored) { }
    }

    @Override
    public Model getModel(Block block) {
        return ModelRegistry.super.getModel(block);
    }

    @Override
    public Model getModel(Material material, BlockData blockData) {
        return getModel(material, blockData, 0.8, 0.4);
    }

    public Model getModel(Material material, BlockData blockData, double temperature, double humidity) {
        return modelMap.computeIfAbsent(material, key -> new HashMap<>()).getOrDefault(blockData,
            blockData == null ? getDefaultModel()
                : modelMap.get(material).getOrDefault(null, getDefaultModel()));
    }

    @Override
    public Model getDefaultModel() {
        return AbstractModel.Builder.createStatic(Color.PURPLE.asRGB()).build();
    }

    private void registerModel(Material material, Model blockModel) {
        modelMap.computeIfAbsent(material, key -> new HashMap<>())
            .put(null, blockModel);
    }

    private void addModelFromFile(File file) {
        String blockName = file.getName().substring(0, file.getName().lastIndexOf('.'));
        Material material = Material.getMaterial(blockName.toUpperCase());
        if(material == null) return;

        Model model = getModelFromFile(file);
        if(model == null) return;

        registerModel(material, model);
    }

    private Model getModelFromFile(File file) {
        try (Reader reader = new FileReader(file)) {
            BlockInfo blockInfo = gson.fromJson(reader, BlockInfo.class);

            if(blockInfo.textures.all != null) {
                return AbstractModel.Builder.createSimple(
                    getTextureArray(blockInfo.textures.all.substring(blockInfo.textures.all.lastIndexOf('/') + 1))
                ).build();
            }
            if(blockInfo.textures.cross != null) {
                return AbstractModel.Builder.createCross(
                    getTextureArray(blockInfo.textures.cross.substring(blockInfo.textures.cross.lastIndexOf('/') + 1))
                ).build();
            }
            if(blockInfo.textures.side != null && blockInfo.textures.bottom != null && blockInfo.textures.top != null) {
                return AbstractModel.Builder.createMulti(
                    getTextureArray(blockInfo.textures.top.substring(blockInfo.textures.top.lastIndexOf('/') + 1)),
                    getTextureArray(blockInfo.textures.side.substring(blockInfo.textures.side.lastIndexOf('/') + 1)),
                    getTextureArray(blockInfo.textures.bottom.substring(blockInfo.textures.bottom.lastIndexOf('/') + 1))
                ).build();
            }
            if(blockInfo.textures.side != null && blockInfo.textures.end != null) {
                return AbstractModel.Builder.createMulti(
                    getTextureArray(blockInfo.textures.end.substring(blockInfo.textures.end.lastIndexOf('/') + 1)),
                    getTextureArray(blockInfo.textures.side.substring(blockInfo.textures.side.lastIndexOf('/') + 1)),
                    getTextureArray(blockInfo.textures.end.substring(blockInfo.textures.end.lastIndexOf('/') + 1))
                ).build();
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return null;
    }

    private int[][] getTextureArray(String textureName) {
        int[][] texture = new int[TEXTURE_SIZE][TEXTURE_SIZE];
        BufferedImage img;
        URL url = this.getClass().getClassLoader().getResource(String.format("textures/block/%s.png", textureName));
        if (url == null) {
            throw new RuntimeException("Block Texture Resource not found.");
        }
        try (InputStream input = url.openConnection().getInputStream()) {
            img = ImageIO.read(input);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        for (int pixelY = 0; pixelY < TEXTURE_SIZE; pixelY++) {
            for (int pixelX = 0; pixelX < TEXTURE_SIZE; pixelX++) {
                texture[TEXTURE_SIZE - 1 - pixelY][TEXTURE_SIZE - 1 - pixelX] = img.getRGB(pixelX, pixelY);
            }
        }

        return texture;
    }

    private int tintPixel(int baseColor, int tintColor) {
        int a = (baseColor >> 24) & 0xFF;
        int r = ((baseColor >> 16) & 0xFF) * ((tintColor >> 16) & 0xFF) / 255;
        int g = ((baseColor >> 8) & 0xFF) * ((tintColor >> 8) & 0xFF) / 255;
        int b = (baseColor & 0xFF) * (tintColor & 0xFF) / 255;
        return (a << 24) | (r << 16) | (g << 8) | b;
    }
}
