package ca.recrown.islandsurvivalcraft.world.generation;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.bukkit.block.Biome;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.TestInstance.Lifecycle;

import ca.recrown.islandsurvivalcraft.Utilities;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;

@TestInstance(Lifecycle.PER_CLASS)
public class UniBiomeIslandGeneratorTest {
    private final int SEED = 249398015;
    private final DummyWorld dummyWorld = new DummyWorld();
    private final Cache<Point2, Double> blockValueCache = new Cache<>(102400);
    private final Cache<Point2, Biome[]> biomeCache = new Cache<>(102400);
    private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(16384);
    private final Random rand = new Random(SEED);

    private class BiomeGenTask implements Runnable {
        private final int amount;
        private final int shift;

        public BiomeGenTask(int amount, int shift) {
            this.shift = shift;
            this.amount = amount;
        }

        @Override
        public void run() {
            for (int x = 0; x < amount; x++) {
                generateBiome(x, shift);
            }

        }

        public void generateBiome(int chunkX, int chunkZ) {
            IslandWorldMapper mapper = new IslandWorldMapper(rand, blockValueCache);
            TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(SEED);
            BiomeSelector biomeSelector = new BiomeSelector(rand);
            biomeSelector.initialize();
            BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();

            Biome[][] biomes = new Biome[Utilities.CHUNK_SIZE][Utilities.CHUNK_SIZE];
            for (int localX = 0; localX < Utilities.CHUNK_SIZE; localX++) {
                for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
                    if (biomes[localX][localZ] == null) {
                        biomeGenerator.generateBiomeColumn(biomes, dummyWorld, chunkX, chunkZ, localX, localZ, mapper,
                                biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache);
                    }
                    if (biomes[localX][localZ] == null)
                        throw new IllegalStateException("Biome was null.");
                }
            }
        }
    }

    @Test
    public void testBiomeGenerationMultithread1608Chunks() {
        int chunksToDoEach = 268;
        Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
        Runnable g2 = new BiomeGenTask(chunksToDoEach, 1);
        Runnable g3 = new BiomeGenTask(chunksToDoEach, 2);
        Runnable g4 = new BiomeGenTask(chunksToDoEach, 3);
        Runnable g5 = new BiomeGenTask(chunksToDoEach, 4);
        Runnable g6 = new BiomeGenTask(chunksToDoEach, 5);

        ExecutorService ex = Executors.newFixedThreadPool(6);
        ex.execute(g1);
        ex.execute(g2);
        ex.execute(g3);
        ex.execute(g4);
        ex.execute(g5);
        ex.execute(g6);

        try {
            ex.shutdown();
            assertTrue(ex.awaitTermination(1, TimeUnit.MINUTES), "timed out.");
        } catch (InterruptedException e) {
            assertFalse(false);
        }
    }

    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    public void testBiomeGenerationSingleThread1608Chunks() {
        Runnable g1 = new BiomeGenTask(1608, 0);
        g1.run();
    }

    @Test
    public void testBiomeGenerationMultithread1608ChunksScatteredColumns() {
        int chunksToDoEach = 268;
        Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
        Runnable g2 = new BiomeGenTask(chunksToDoEach, 2);
        Runnable g3 = new BiomeGenTask(chunksToDoEach, 4);
        Runnable g4 = new BiomeGenTask(chunksToDoEach, 6);
        Runnable g5 = new BiomeGenTask(chunksToDoEach, 8);
        Runnable g6 = new BiomeGenTask(chunksToDoEach, 10);

        ExecutorService ex = Executors.newFixedThreadPool(6);
        ex.execute(g1);
        ex.execute(g2);
        ex.execute(g3);
        ex.execute(g4);
        ex.execute(g5);
        ex.execute(g6);

        try {
            ex.shutdown();
            assertTrue(ex.awaitTermination(1, TimeUnit.MINUTES), "timed out.");
        } catch (InterruptedException e) {
            assertFalse(false);
        }
    }
}