package ca.recrown.islandsurvivalcraft.world.generation;


import org.bukkit.World;
import org.bukkit.block.Biome;

import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.caching.CacheValue;
import ca.recrown.islandsurvivalcraft.caching.CoordinateIdentifier;
import ca.recrown.islandsurvivalcraft.caching.Identifier;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;

//Note: technically, the validators have to be run on land, and so, some condition checks may not be nessecary.
public class BiomePerIslandGenerator implements IslandBiomeGenerator, CoordinateValidatable, CoordinateTargetValidatable {
    private final Cache<ChunkBiomes> chunkBiomesCache;
    private final Cache<Boolean> chunkGenStatusCache;
    private final TemperatureMapGenerator temperatureMapGenerator;
    private boolean initialized;
    private IslandWorldMapper worldIslandMap;
    private BiomeSelector biomeSelector;
    private World world;
    private DepthFirstSearch mapWideDFS;
    private DepthFirstSearch propagator;
    private LocalIslandPropagator propagatorInfo;
    CoordinateIdentifier chunkCoords;
    ChunkBiomes localChunkBiomes;
    
    float temperature;

    public BiomePerIslandGenerator() {
        this.temperatureMapGenerator = new TemperatureMapGenerator();
        chunkBiomesCache = new Cache<>(1024);
        chunkGenStatusCache = new Cache<>(1024);
        propagatorInfo = new LocalIslandPropagator();
    }
    
    @Override
    public void initialize(final World world, final IslandWorldMapper mapGenerator, final BiomeSelector biomeSelector) {
        if (initialized) throw new IllegalStateException("Biome generator already initialized.");
        initialized = true;
        this.worldIslandMap = mapGenerator;
        this.biomeSelector = biomeSelector;
        this.world = world;
        this.temperatureMapGenerator.setSeed(world.getSeed());
        mapWideDFS = new DepthFirstSearch(worldIslandMap);
        mapWideDFS.setEndPosition(0, 0);
        propagator = new DepthFirstSearch(256, this);
    }

    @Override
    public IslandBiomeGenerator getInstance() {
        return new BiomePerIslandGenerator();
    }

    @Override
    public Biome GenerateBiome(final int chunkX, final int chunkZ, final int localX, final int localZ) {
        if (chunkCoords == null || chunkCoords.getX() != chunkX || chunkCoords.getY() != chunkZ) {
            chunkCoords = new CoordinateIdentifier(chunkX, chunkZ);
            chunkGenStatusCache.retrieveCache(chunkCoords).setValue(true);
            localChunkBiomes = new ChunkBiomes();
            chunkBiomesCache.retrieveCache(chunkCoords).setValue(localChunkBiomes);
        }
        int worldX = 16 * chunkX + localX;
        int worldZ = 16 * chunkZ + localZ;

        this.temperature = temperatureMapGenerator.getTemperature(worldX, worldZ);
        if (worldIslandMap.isIsland(worldX, worldZ) && localChunkBiomes.biomes[localX][localZ] == null) {
            mapWideDFS.setStartPosition(worldX, worldZ);
            if (!mapWideDFS.findTarget(this)) {
                if (propagatorInfo.mainBiome == null) propagatorInfo.mainBiome = biomeSelector.getLandBiome(temperature);
                if (propagatorInfo.shoreBiome == null) propagatorInfo.shoreBiome = biomeSelector.getShoreBiome(propagatorInfo.mainBiome, temperature);
                if (propagatorInfo.shallowBiome == null) propagatorInfo.shallowBiome = biomeSelector.getOceanBiome(temperature);
            }
            propagator.setStartPosition(worldX, worldZ);
            propagator.findTarget(propagatorInfo);
        }
        if (localChunkBiomes.biomes[localX][localZ] == null) {
            localChunkBiomes.biomes[localX][localZ] = biomeSelector.getOceanBiome(temperature);
        }
        return localChunkBiomes.biomes[localX][localZ];
    }

    @Override
    public boolean validate(int x, int y) {
        return (x / 16) == chunkCoords.getX() && (y / 16) == chunkCoords.getY() && worldIslandMap.isIsland(x, y);
    }

    @Override
    public boolean isCoordinateTarget(int x, int y) {
        if ((propagatorInfo.shoreBiome == null || propagatorInfo.mainBiome == null) && worldIslandMap.isLand(x, y)) {
            if (propagatorInfo.shoreBiome == null && worldIslandMap.isShore(x, y)) {
                propagatorInfo.shoreBiome = getBiomeAt(x, y);
            } else if (propagatorInfo.mainBiome != null) {
                propagatorInfo.mainBiome = getBiomeAt(x, y);
            }
        } else if (propagatorInfo.shallowBiome == null) {
            propagatorInfo.shallowBiome = getBiomeAt(x, y);
        }
        return (propagatorInfo.shallowBiome != null && propagatorInfo.mainBiome != null && propagatorInfo.shoreBiome != null);
    }

    private Biome getBiomeAt(int worldX, int worldZ) {
        int chunkX = worldX / 16;
        int chunkZ = worldZ / 16;
        int localX = Math.abs(worldX % 16);
        int localZ = Math.abs(worldZ % 16);

        Identifier chunkBiomeID = new CoordinateIdentifier(chunkX, chunkZ);
        Identifier chunkGenID = new CoordinateIdentifier(chunkX, chunkZ);

        CacheValue<ChunkBiomes> chunkBiomeVal = chunkBiomesCache.retrieveCache(chunkBiomeID);
        CacheValue<Boolean> chunkGenVal = chunkGenStatusCache.retrieveCache(chunkGenID);

        if (chunkGenVal.isEmpty()) chunkGenVal.setValue(world.isChunkGenerated(worldX / 16, worldZ / 16));

        if (chunkGenVal.getValue()) {
            if (chunkBiomeVal.isEmpty()) {
                chunkBiomeVal.setValue(new ChunkBiomes());
            }

            if (chunkBiomeVal.getValue().biomes[localX][localZ] == null) {
                chunkBiomeVal.getValue().biomes[localX][localZ] = world.getBiome(worldX, 0, worldZ);
            }
            return chunkBiomeVal.getValue().biomes[localX][localZ] = world.getBiome(worldX, 0, worldZ);
        }
        return null;
    }

    private class ChunkBiomes {
        public final Biome[][] biomes = new Biome[16][16];
    }

    private class LocalIslandPropagator implements CoordinateTargetValidatable {
        public Biome mainBiome = null;
        public Biome shoreBiome = null;
        public Biome shallowBiome = null;

        @Override
        public boolean isCoordinateTarget(int x, int y) {
            int localX = Math.abs(x % 16);
            int localZ = Math.abs(y % 16);
            if (worldIslandMap.isLand(x, y)) {
                if (worldIslandMap.isShore(x, y)) {
                    localChunkBiomes.biomes[localX][localZ] = shoreBiome;
                } else {
                    localChunkBiomes.biomes[localX][localZ] = mainBiome;
                }
            } else if (worldIslandMap.isShallowPortion(x, y)) {
                localChunkBiomes.biomes[localX][localZ] = shallowBiome;
            }
            return false;
        }
    }
}