package eu.mhsl.minenet.minigames.world.generator.terrain;

import de.articdive.jnoise.JNoise;
import eu.mhsl.minenet.minigames.util.NumberUtil;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.PlateTerrainGenerator;
import eu.mhsl.minenet.minigames.world.generator.structures.generatable.PeakRock;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.Random;

public class SquarePlateTerrainGenerator extends PlateTerrainGenerator {
    protected final Random rnd = new Random();

    private final int width;
    private final int length;


    public SquarePlateTerrainGenerator(int width, int length) {
        this.width = width;
        this.length = length;
    }

    private final JNoise base = JNoise.newBuilder()
            .fastSimplex()
            .setSeed(rnd.nextLong())
            .setFrequency(0.01)
            .build();

    private final JNoise batches = JNoise.newBuilder()
            .fastSimplex()
            .setSeed(rnd.nextLong())
            .setFrequency(0.05)
            .build();

    private final JNoise peaks = JNoise.newBuilder()
            .fastSimplex()
            .setSeed(rnd.nextLong())
            .setFrequency(0.1)
            .build();


    @Override
    public void generate(@NotNull GenerationUnit unit) {
        Point start = unit.absoluteStart().withY(0);

        // don't generate more than 500 blocks outwards
        Point chunkStart = unit.absoluteStart();
        if(chunkStart.z() > length + 500 || chunkStart.z() < -500) return;
        if(chunkStart.x() < -500 || chunkStart.x() > width + 500) return;

        for (int x = 0; x < unit.size().x(); x++) {
            for (int z = 0; z < unit.size().z(); z++) {
                Point bottom = start.add(x, 0, z);

                if(generatePlate()) {
                    if(bottom.x() <= width && bottom.x() >= 0 && bottom.z() <= length && bottom.z() >= 0) {
                        unit.modifier().fill(bottom, bottom.add(1, plateHeight, 1), platePallet.rnd());
                        continue;
                    }
                }

                if(generateBorders) {
                    Runnable generateBorder = () -> unit.modifier().fill(bottom, bottom.add(1, DimensionType.VANILLA_MAX_Y, 1), Block.BARRIER);

                    if(bottom.z() <= length+1 && bottom.z() >= -1 && bottom.x() >= -1 && bottom.x() <= width+1) {
                        if(bottom.x() == -1 || bottom.x() == width+1) {
                            generateBorder.run();
                        }
                        if(bottom.z() == -1 || bottom.z() == length + 1) {
                            generateBorder.run();
                        }
                    }
                }

                unit.modifier().setBlock(bottom, Block.GRASS_BLOCK);

                synchronized (base) {
                    double baseNoise = base.getNoise(bottom.x(), bottom.z());
                    double[] possibleHeights = {
                        minTwo(NumberUtil.map(bottom.distance(new Pos(0, 0, 0)), 0, 400, -((double) this.width / 5), 200)) + baseNoise * 8,
                        minTwo(NumberUtil.map(bottom.distance(new Pos(width, 0, 0)), 0, 400, -((double) this.width / 5), 200)) + baseNoise * 8,
                        minTwo(NumberUtil.map(bottom.distance(new Pos(0, 0, length)), 0, 400, -((double) this.width / 5), 200)) + baseNoise * 8,
                        minTwo(NumberUtil.map(bottom.distance(new Pos(width, 0, length)), 0, 400, -((double) this.width / 5), 200)) + baseNoise * 8
                    };

                    double currentHeight = Arrays.stream(possibleHeights).min().getAsDouble();

                    synchronized (batches) {
                        double elementNoise = batches.getNoise(bottom.x(), bottom.z());

                        unit.modifier().fill(
                                bottom,
                                bottom.add(1, 1, 1)
                                        .withY(currentHeight),
                                elementNoise < 0.9 ? elementNoise > 0 ? Block.GRASS_BLOCK : Block.SOUL_SAND : Block.STONE
                        );
                    }

                    synchronized (peaks) {
                        double peakNoise = peaks.getNoise(bottom.x(), bottom.z());

                        if(peakNoise > 0.97 && bottom.distance(new Pos(0, 0, 0)) > (this.width + 20)) {

                            Point center = bottom.add(1, currentHeight-3, 1);
                            unit.fork(setter -> new PeakRock(center, BlockPallet.STONE).generateStructure(setter));
                        }
                    }


                }
            }
        }

    }

    private static double minTwo(double input) {
        if(input < 2) return 2;
        return input;
    }
}
