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

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

import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.bukkit.block.Biome;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
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.GeneralUtilities;
import ca.recrown.islandsurvivalcraft.utilities.caching.Cache;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.BiomeMap;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;
import ca.recrown.islandsurvivalcraft.world.TemperatureMap;
import ca.recrown.islandsurvivalcraft.world.generation.DummyWorld;

@TestInstance(Lifecycle.PER_CLASS)
public class UniqueBiomeGeneratorTest {
    private final int SEED = 249398015;
    private final DummyWorld dummyWorld = new DummyWorld();
    private volatile Cache<Point2, Double> blockValueCache;
    private volatile Cache<Point2, Biome[]> biomeCache;
    private volatile Cache<Point2, Boolean> chunkExistenceCache;
    private final BiomeMap biomeSelector = new BiomeMap(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) {
            Random random = new Random(SEED);
            IslandWorldMap mapper = new IslandWorldMap(random);
            TemperatureMap temperatureMapGenerator = new TemperatureMap(random);
            BiomeGenerator biomeGenerator = new UniqueBiomeGenerator();

            Biome[][][] biomes = new Biome[GeneralUtilities.CHUNK_SIZE][GeneralUtilities.CHUNK_SIZE][4];
            for (int localX = 0; localX < GeneralUtilities.CHUNK_SIZE; localX++) {
                for (int localZ = 0; localZ < GeneralUtilities.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.");
                }
            }
        }
    }

    @BeforeAll
    public void setup() {
    }
    
    @BeforeEach
    public void individualSetup() {
        blockValueCache = new Cache<>(524288);
        biomeCache = new Cache<>(524288);
        chunkExistenceCache = new Cache<>(16384);
    }

    @AfterEach
    public void individualCleanup() {
        blockValueCache.clearCache();
        biomeCache.clearCache();
        chunkExistenceCache.clearCache();
    }

    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    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);
        LinkedList<Future<?>> tasks = new LinkedList<>();
        tasks.add(ex.submit(g1));
        tasks.add(ex.submit(g2));
        tasks.add(ex.submit(g3));
        tasks.add(ex.submit(g4));
        tasks.add(ex.submit(g5));
        tasks.add(ex.submit(g6));

        while (!tasks.isEmpty()) {
            try {
                tasks.pop().get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                assertFalse(false, e.getCause().getMessage());
            }
        }
    }

    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    public void testBiomeGenerationMultithread1608ChunksSmallCache() {
        this.blockValueCache = new Cache<>(1024);
        this.biomeCache = new Cache<>(1024);
        this.chunkExistenceCache = new Cache<>(1024);

        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);
        LinkedList<Future<?>> tasks = new LinkedList<>();
        tasks.add(ex.submit(g1));
        tasks.add(ex.submit(g2));
        tasks.add(ex.submit(g3));
        tasks.add(ex.submit(g4));
        tasks.add(ex.submit(g5));
        tasks.add(ex.submit(g6));

        while (!tasks.isEmpty()) {
            try {
                tasks.pop().get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                assertFalse(false, e.getCause().getMessage());
            }
        }
    }

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

    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    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);
        LinkedList<Future<?>> tasks = new LinkedList<>();
        tasks.add(ex.submit(g1));
        tasks.add(ex.submit(g2));
        tasks.add(ex.submit(g3));
        tasks.add(ex.submit(g4));
        tasks.add(ex.submit(g5));
        tasks.add(ex.submit(g6));

        while (!tasks.isEmpty()) {
            try {
                tasks.pop().get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                assertFalse(false, e.getCause().getMessage());
            }
        }
    }

    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    public void testBiomeGenerationMultithread1608ChunksScatteredColumnsSmallCache() {
        this.blockValueCache = new Cache<>(1024);
        this.biomeCache = new Cache<>(1024);
        this.chunkExistenceCache = new Cache<>(1024);
        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);
        LinkedList<Future<?>> tasks = new LinkedList<>();
        tasks.add(ex.submit(g1));
        tasks.add(ex.submit(g2));
        tasks.add(ex.submit(g3));
        tasks.add(ex.submit(g4));
        tasks.add(ex.submit(g5));
        tasks.add(ex.submit(g6));

        while (!tasks.isEmpty()) {
            try {
                tasks.pop().get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                assertFalse(false, e.getCause().getMessage());
            }
        }
    }

    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    public void testBiomeGenerationMultithread6000ChunksScatteredColumns() {
        int chunksToDoEach = 1000;
        Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
        Runnable g2 = new BiomeGenTask(chunksToDoEach, 3);
        Runnable g3 = new BiomeGenTask(chunksToDoEach, 6);
        Runnable g4 = new BiomeGenTask(chunksToDoEach, 9);
        Runnable g5 = new BiomeGenTask(chunksToDoEach, 12);
        Runnable g6 = new BiomeGenTask(chunksToDoEach, 15);

        ExecutorService ex = Executors.newFixedThreadPool(6);
        LinkedList<Future<?>> tasks = new LinkedList<>();
        tasks.add(ex.submit(g1));
        tasks.add(ex.submit(g2));
        tasks.add(ex.submit(g3));
        tasks.add(ex.submit(g4));
        tasks.add(ex.submit(g5));
        tasks.add(ex.submit(g6));

        while (!tasks.isEmpty()) {
            try {
                tasks.pop().get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                assertFalse(false, e.getCause().getMessage());
            }
        }
    }

    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    public void testBiomeGenerationMultithread6000ChunksScatteredColumnsSmallCache() {
        this.blockValueCache = new Cache<>(1024);
        this.biomeCache = new Cache<>(1024);
        this.chunkExistenceCache = new Cache<>(1024);

        int chunksToDoEach = 1000;
        Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
        Runnable g2 = new BiomeGenTask(chunksToDoEach, 3);
        Runnable g3 = new BiomeGenTask(chunksToDoEach, 6);
        Runnable g4 = new BiomeGenTask(chunksToDoEach, 9);
        Runnable g5 = new BiomeGenTask(chunksToDoEach, 12);
        Runnable g6 = new BiomeGenTask(chunksToDoEach, 15);

        ExecutorService ex = Executors.newFixedThreadPool(6);
        LinkedList<Future<?>> tasks = new LinkedList<>();
        tasks.add(ex.submit(g1));
        tasks.add(ex.submit(g2));
        tasks.add(ex.submit(g3));
        tasks.add(ex.submit(g4));
        tasks.add(ex.submit(g5));
        tasks.add(ex.submit(g6));

        while (!tasks.isEmpty()) {
            try {
                tasks.pop().get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                assertFalse(false, e.getCause().getMessage());
            }
        }
    }
}