package cz.jzitnik.game.generation;

import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;

import java.util.List;
import java.util.Random;

public class PopulateWorld {
    private static final int WORLD_WIDTH = 512;
    private static final int WORLD_HEIGHT = 256;

    public static int[] generateTerrain() {
        Random random = new Random();
        int baseHeight = 120;
        int[] terrainHeight = new int[WORLD_WIDTH];

        terrainHeight[0] = baseHeight;
        for (int i = 1; i < WORLD_WIDTH; i++) {
            int heightChange = random.nextInt(3) - 1;
            terrainHeight[i] = Math.max(100, Math.min(140, terrainHeight[i - 1] + heightChange));
        }

        for (int i = 2; i < WORLD_WIDTH - 2; i++) {
            terrainHeight[i] = (terrainHeight[i - 1] + terrainHeight[i] + terrainHeight[i + 1]) / 3;
        }

        return terrainHeight;
    }

    public static void populateWorld(List<Block>[][] world, int[] terrainHeight) {
        boolean[][] isCave = generateCavesWithCellularAutomata(terrainHeight);
        Random random = new Random();

        for (int x = 0; x < WORLD_WIDTH; x++) {
            int hillHeight = terrainHeight[x];

            // Surface block
            world[hillHeight][x].add(ItemBlockSupplier.getBlock("grass"));

            // Grass bush
            if (random.nextDouble() < 0.1 && !Trees.isTreeNearby(world, x, hillHeight)) {
                world[hillHeight - 1][x].add(ItemBlockSupplier.getBlock("grass_bush"));
            }

            // Dirt layer
            for (int y = hillHeight + 1; y <= hillHeight + 4; y++) {
                if (!isCave[y][x]) {
                    world[y][x].add(ItemBlockSupplier.getBlock("dirt"));
                }
            }

            // Stone layer
            for (int y = hillHeight + 5; y < WORLD_HEIGHT - 1; y++) {
                if (!isCave[y][x]) {
                    world[y][x].add(ItemBlockSupplier.getBlock("stone"));
                }
            }

            // Bedrock
            world[WORLD_HEIGHT - 1][x].add(new Block("bedrock", SpriteLoader.SPRITES.BEDROCK));
        }

        // Generate ores
        generateOres(world, isCave, terrainHeight);

        // Fill air blocks
        for (List<Block>[] column : world) {
            for (List<Block> list : column) {
                list.addFirst(new Block("air", SpriteLoader.SPRITES.AIR, true, false));
            }
        }
    }

    private static void generateOres(List<Block>[][] world, boolean[][] isCave, int[] terrainHeight) {
        Random random = new Random();

        // oreName, minVeinSize, maxVeinSize, frequency, extraDepthOffset
        String[][] ores = {
                {"coal_ore", "5", "8", "100", "0"},
                {"iron_ore", "4", "6", "80", "10"},
                {"gold_ore", "3", "5", "50", "20"},
                {"diamond_ore", "2", "4", "30", "40"}, // diamonds spawn 40+ blocks deeper
        };

        for (String[] ore : ores) {
            String oreName = ore[0];
            int minVeinSize = Integer.parseInt(ore[1]);
            int maxVeinSize = Integer.parseInt(ore[2]);
            int frequency = Integer.parseInt(ore[3]);
            int depthOffset = Integer.parseInt(ore[4]);

            for (int i = 0; i < frequency; i++) {
                int x = random.nextInt(WORLD_WIDTH);

                // Minimum Y now includes extra depth offset
                int minY = terrainHeight[x] + 10 + depthOffset;
                int maxY = Math.min(WORLD_HEIGHT - 2, minY + 60); // reduce max spread depth too

                if (minY >= WORLD_HEIGHT - 2) continue; // skip if too deep

                int y = minY + random.nextInt(Math.max(1, maxY - minY));
                int veinSize = minVeinSize + random.nextInt(maxVeinSize - minVeinSize + 1);

                generateOreVein(world, isCave, x, y, oreName, veinSize);
            }
        }
    }



    private static void generateOreVein(List<Block>[][] world, boolean[][] isCave, int startX, int startY, String oreName, int veinSize) {
        Random random = new Random();
        int x = startX;
        int y = startY;

        int placed = 0;
        int attempts = 0;
        int maxAttempts = veinSize * 10; // failsafe to prevent infinite loop

        while (placed < veinSize && attempts < maxAttempts) {
            attempts++;

            if (x >= 0 && x < WORLD_WIDTH && y >= 0 && y < WORLD_HEIGHT - 1) {
                List<Block> blockList = world[y][x];
                boolean placedHere = false;
                for (int j = 0; j < blockList.size(); j++) {
                    Block b = blockList.get(j);
                    if (b.getBlockId().equals("stone") && !isCave[y][x]) {
                        blockList.set(j, ItemBlockSupplier.getBlock(oreName));
                        placed++;
                        placedHere = true;
                        break;
                    }
                }

                // Only continue walk if we successfully placed, otherwise try different direction
                if (placedHere) {
                    int direction = random.nextInt(4);
                    switch (direction) {
                        case 0: x += 1; break;
                        case 1: x -= 1; break;
                        case 2: y += 1; break;
                        case 3: y -= 1; break;
                    }

                    // Optional: small branching
                    if (random.nextDouble() < 0.1 && placed < veinSize - 3) {
                        generateOreVein(world, isCave, x, y, oreName, 2); // small branch
                        placed += 2;
                    }
                } else {
                    // Jump randomly to find another stone block to continue vein
                    x = startX + random.nextInt(5) - 2;
                    y = startY + random.nextInt(5) - 2;
                }
            } else {
                // Jump back near the original vein start if out of bounds
                x = startX + random.nextInt(5) - 2;
                y = startY + random.nextInt(5) - 2;
            }
        }
    }


    private static boolean[][] generateCavesWithCellularAutomata(int[] terrainHeight) {
        Random random = new Random();
        boolean[][] caveMap = new boolean[WORLD_HEIGHT][WORLD_WIDTH];

        // Dynamically define caveStartY per column to not overlap dirt layer
        for (int x = 0; x < WORLD_WIDTH; x++) {
            int dirtEndY = terrainHeight[x] + 4;
            int caveStartY = dirtEndY + 1;
            int caveEndY = Math.min(caveStartY + 40, WORLD_HEIGHT - 2); // limit depth to ~40 blocks

            // Initial noise map for this column
            for (int y = caveStartY; y < caveEndY; y++) {
                caveMap[y][x] = random.nextDouble() < 0.45;
            }
        }

        // Smooth cave map with CA rules
        int iterations = 5;
        for (int iter = 0; iter < iterations; iter++) {
            boolean[][] newMap = new boolean[WORLD_HEIGHT][WORLD_WIDTH];

            for (int x = 0; x < WORLD_WIDTH; x++) {
                int dirtEndY = terrainHeight[x] + 4;
                int caveStartY = dirtEndY + 1;
                int caveEndY = Math.min(caveStartY + 40, WORLD_HEIGHT - 2);

                for (int y = caveStartY; y < caveEndY; y++) {
                    int walls = countSurroundingWalls(caveMap, x, y);

                    if (walls > 4) newMap[y][x] = true;      // wall
                    else if (walls < 4) newMap[y][x] = false; // cave
                    else newMap[y][x] = caveMap[y][x];        // keep
                }
            }

            caveMap = newMap;
        }

        return caveMap;
    }


    private static int countSurroundingWalls(boolean[][] map, int x, int y) {
        int count = 0;

        for (int dy = -1; dy <= 1; dy++) {
            for (int dx = -1; dx <= 1; dx++) {
                if (dx == 0 && dy == 0) continue;

                int nx = x + dx;
                int ny = y + dy;

                if (nx < 0 || nx >= WORLD_WIDTH || ny < 0 || ny >= WORLD_HEIGHT) {
                    count++; // out-of-bounds counts as wall
                } else if (map[ny][nx]) {
                    count++;
                }
            }
        }

        return count;
    }
}
