package ca.recrown.islandsurvivalcraft.world.generation.shaders;

import java.util.Random;

import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.util.noise.SimplexOctaveGenerator;

import ca.recrown.islandsurvivalcraft.utilities.caching.Cache;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Vector3;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;

@SuppressWarnings("unused")
public class WorldLayerShader {
    private final Cache<Vector3, Double> noiseCache = new Cache<>(256);
    private final SimplexOctaveGenerator noiseGenerator;
    private final IslandWorldMap islandMap;
    private final int seaLevel, maxHeight;


    private final Material[] badlandTerrocota = new Material[] {
        Material.BLACK_TERRACOTTA, 
        Material.BROWN_TERRACOTTA, 
        Material.GRAY_TERRACOTTA, 
        Material.LIGHT_GRAY_TERRACOTTA, 
        Material.YELLOW_TERRACOTTA, 
        Material.WHITE_TERRACOTTA
    };

    public WorldLayerShader(Random random, int seaLevel, int maxHeight, IslandWorldMap islandMap) {
        this.islandMap = islandMap;
        this.seaLevel = seaLevel;
        this.maxHeight = maxHeight;
        this.noiseGenerator = new SimplexOctaveGenerator(random, 4);
        this.noiseGenerator.setScale(0.05d);
    }

    public boolean hasSpecialLayers(Biome biome) {
        String biomeName = biome.toString().toLowerCase();
        return 
        biomeName.contains("badlands") ||
        biomeName.contains("mountain");
    }

    /**
     * Returns the material for the height. If there are no more special materials, this will return null.
     * 
     * @param seed The seed to use for special layer generation.
     * @param worldX The x world coordinate.
     * @param worldZ The z world coordinate.
     * @param y The current layer (AKA the y world coordinate).
     * @param highestPoint The y level of the top layer.
     * @param biomeSet The biomeset for the current column.
     * @return The block data for the material, or null if there are no further special materials.
     */
    public BlockData getMaterialForHeight(long seed, int worldX, int worldZ, int y, int highestPoint, Biome[] biomeSet) {
        Biome mainBiome = biomeSet[0];
        String mainBiomeName = mainBiome.toString().toLowerCase();
        BlockData res = null;

        if (mainBiomeName.contains("badlands")) {
            int seedOffset = (worldX + worldZ) / 1000;
            Random random = new Random(seed + seedOffset);
            random.setSeed(seed + seedOffset);
            int yInterval = (int) (random.nextFloat() * 9) + 1;
            int yBandInitial = (int) (seaLevel + random.nextDouble() * 50);
            
            if (y > yBandInitial) {
                int amountOfLayers = Math.min(Math.max(1, random.nextInt(badlandTerrocota.length)), 4);
                int subLayer = y % yInterval;
                if (subLayer < amountOfLayers) {
                    Material[] selected = new Material[amountOfLayers];
                    for (int i = 0; i < selected.length; i++) {
                        selected[i] = badlandTerrocota[random.nextInt(amountOfLayers)];
                    }
                    res = selected[subLayer].createBlockData();
                } else {
                    res = Material.TERRACOTTA.createBlockData();
                }
            }
        } else if (mainBiomeName.contains("mountain")) {
            if (y > (maxHeight * 0.48d) - getNormalizedNoise(worldX, worldZ, 2.2f) * 8d) {
                res = Material.STONE.createBlockData();
            } else if (y > highestPoint - getSurfaceThickness(worldX, worldZ, mainBiome)) {
                res = getSurfaceMaterial(mainBiome).createBlockData();
            } else if (y > highestPoint - getSurfaceThickness(worldX, worldZ, mainBiome) - getTransitionMaterialThickness(worldX, worldZ, mainBiome)) {
                res = getTransitionMaterial(mainBiome).createBlockData();
            }
        }

        return res;
    }

    public Material getSurfaceMaterial(Biome biome) {
        String biomeName = biome.toString().toLowerCase();
        if (biomeName.contains("beach") || biomeName.contains("desert") || biomeName.contains("warm_ocean")) {
            return Material.SAND;
        } else if (biome == Biome.ICE_SPIKES) {
            return Material.SNOW_BLOCK;
        } else if (biomeName.contains("ocean")) {
            return Material.GRAVEL;
        } else if (biomeName.contains("stone")) {
            return Material.STONE;
        }
        return Material.GRASS_BLOCK;
    }

    public int getSurfaceThickness(int worldX, int worldZ, Biome biome) {
        String biomeName = biome.toString().toLowerCase();

        if (biomeName.contains("beach")) {
            return (int) (getNormalizedNoise(worldX, worldZ, 1.3f) * 5) + 4;
        }
        return 1;
    }
    
    public Material getTransitionMaterial(Biome biome) {
        String biomeName = biome.toString().toLowerCase();
        if (biomeName.contains("beach") || biomeName.contains("desert")) {
            return Material.SANDSTONE;
        } else if (biome == Biome.STONE_SHORE) {
            return Material.STONE;
        } else if (biomeName.contains("ocean")) {
            return Material.GRAVEL;
        }
        return Material.DIRT;
    }
    
    public int getTransitionMaterialThickness(int worldX, int worldZ, Biome biome) {
        return (int) (getNormalizedNoise(worldX, worldZ, 1.3f) * 4) + 4;
    }

    private double getNormalizedNoise(int worldX, int worldZ, float freq) {
        Vector3 key = new Vector3(worldX, worldZ, freq);
        Double res = noiseCache.get(key);
        if (res == null) {
            res = (noiseGenerator.noise(worldX, worldZ, freq, 0.5d) + 1d) / 2d;
            noiseCache.set(key, res);
        }
        return res;
    }
}