/*
 * Copyright (c) 2020-2025 Polyhedral Development
 *
 * The Terra Core Addons are licensed under the terms of the MIT License. For more details,
 * reference the LICENSE file in this module's root directory.
 */

package com.dfsek.terra.addons.biome.pipeline;

import com.dfsek.seismic.type.sampler.Sampler;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.StreamSupport;

import com.dfsek.terra.addons.biome.pipeline.api.BiomeChunk;
import com.dfsek.terra.addons.biome.pipeline.api.Pipeline;
import com.dfsek.terra.addons.biome.pipeline.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.api.biome.PipelineBiome;
import com.dfsek.terra.api.registry.key.StringIdentifiable;
import com.dfsek.terra.api.util.Column;
import com.dfsek.terra.api.util.cache.SeededVector2Key;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;


public class PipelineBiomeProvider implements BiomeProvider {

    private final LoadingCache<SeededVector2Key, BiomeChunk> biomeChunkCache;
    private final int chunkSize;
    private final int resolution;
    private final Sampler mutator;
    private final double noiseAmp;
    private final Set<Biome> biomes;

    public PipelineBiomeProvider(Pipeline pipeline, int resolution, Sampler mutator, double noiseAmp) {
        this.resolution = resolution;
        this.mutator = mutator;
        this.noiseAmp = noiseAmp;
        this.chunkSize = pipeline.getChunkSize();
        this.biomeChunkCache = Caffeine.newBuilder()
            .maximumSize(64)
            .build(pipeline::generateChunk);

        Set<PipelineBiome> biomeSet = new HashSet<>();
        pipeline.getSource().getBiomes().forEach(biomeSet::add);
        Iterable<PipelineBiome> result = biomeSet;
        for(Stage stage : pipeline.getStages()) {
            result = stage.getBiomes(result);
        }
        this.biomes = new HashSet<>();
        Iterable<PipelineBiome> finalResult = result;
        result.forEach(pipelineBiome -> {
            if(pipelineBiome.isPlaceholder()) {

                StringBuilder biomeList = new StringBuilder("\n");
                StreamSupport.stream(finalResult.spliterator(), false)
                    .sorted(Comparator.comparing(StringIdentifiable::getID))
                    .forEach(delegate -> biomeList
                        .append("    - ")
                        .append(delegate.getID())
                        .append(':')
                        .append(delegate.getClass().getCanonicalName())
                        .append('\n'));
                throw new IllegalArgumentException("Biome Pipeline leaks placeholder biome \"" + pipelineBiome.getID() +
                                                   "\". Ensure there is a stage to guarantee replacement of the placeholder biome. " +
                                                   "Biomes: " +
                                                   biomeList);
            }
            this.biomes.add(pipelineBiome.getBiome());
        });
    }

    @Override
    public Biome getBiome(int x, int y, int z, long seed) {
        return getBiome(x, z, seed);
    }

    public Biome getBiome(int x, int z, long seed) {

        x += (int) (mutator.getSample(seed + 1, x, z) * noiseAmp);
        z += (int) (mutator.getSample(seed + 2, x, z) * noiseAmp);

        x /= resolution;
        z /= resolution;

        int chunkX = Math.floorDiv(x, chunkSize);
        int chunkZ = Math.floorDiv(z, chunkSize);

        int chunkWorldX = chunkX * chunkSize;
        int chunkWorldZ = chunkZ * chunkSize;

        int xInChunk = x - chunkWorldX;
        int zInChunk = z - chunkWorldZ;

        return biomeChunkCache.get(new SeededVector2Key(chunkWorldX, chunkWorldZ, seed)).get(xInChunk, zInChunk).getBiome();
    }

    @Override
    public Iterable<Biome> getBiomes() {
        return biomes;
    }

    @Override
    public Optional<Biome> getBaseBiome(int x, int z, long seed) {
        return Optional.of(getBiome(x, z, seed));
    }

    @Override
    public Column<Biome> getColumn(int x, int z, long seed, int min, int max) {
        return new BiomePipelineColumn(this, min, max, x, z, seed);
    }

    @Override
    public int resolution() {
        return resolution;
    }
}
