package eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace;

import de.articdive.jnoise.JNoise;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.util.CommonProperties;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.SquarePlateTerrainGenerator;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

class TrafficLightRace extends StatelessGame {
    private LightPhase phase = LightPhase.RED;
    private int phaseCounter = 0;

    private final int width;
    private final int length;
    private final int preRun = 10;
    private final int afterRun = 10;

    private final List<Pos> trafficLights = new ArrayList<>();

    public TrafficLightRace(int width, int length) {
        super(Dimension.THE_END.key, "Ampelrennen", new FirstWinsScore());
        this.width = width;
        this.length = length;

        setGenerator(
                new SquarePlateTerrainGenerator(width, length + preRun + afterRun)
                        .setPlateHeight(50)
                        .setGenerateBorders(true)
        );
    }

    @Override
    protected void onLoad(@NotNull CompletableFuture<Void> callback) {
        final JNoise slowBatches = JNoise.newBuilder()
                .fastSimplex()
                .setSeed(rnd.nextLong())
                .setFrequency(0.1)
                .build();

        AbsoluteBlockBatch batch = new AbsoluteBlockBatch();

        for (int x = 0; x <= width; x++) {
            for (int z = 0; z <= preRun; z++) {
                batch.setBlock(x, 50, z, BlockPallet.GROUND.rnd());
            }

            for (int z = preRun; z <= length + preRun; z++) {
                batch.setBlock(x, 50, z, slowBatches.getNoise(x, z) > 0.6 ? Block.SOUL_SAND : BlockPallet.STREET.rnd());
            }

            for (int z = preRun + length; z <= preRun + length + afterRun; z++) {
                batch.setBlock(x, 50, z, BlockPallet.GROUND.rnd());
            }

            batch.setBlock(x, 51, preRun-1, Block.OAK_FENCE.withProperties(CommonProperties.fenceEastWest));
            batch.setBlock(x, 50, preRun + length, Block.GOLD_BLOCK);
        }

        record TrafficLightsInput(int x, int z) {}

        Consumer<TrafficLightsInput> generateTrafficLight = (input) -> {
            batch.setBlock(input.x, 51, input.z, Block.ANVIL);

            for(int y = 52; y <= 59; y++) {
                batch.setBlock(input.x, y, input.z, Block.ANDESITE_WALL);
            }

            for (int x = input.x-3; x <= input.x+3; x++) {
                batch.setBlock(x, 60, input.z, Block.SMOOTH_STONE_SLAB);
            }

            batch.setBlock(input.x-2, 59, input.z, Block.STONE_BRICK_WALL);
            trafficLights.add(new Pos(input.x-2, 58, input.z));
            trafficLights.add(new Pos(input.x-2, 57, input.z));

            batch.setBlock(input.x+2, 59, input.z, Block.STONE_BRICK_WALL);
            trafficLights.add(new Pos(input.x+2, 58, input.z));
            trafficLights.add(new Pos(input.x+2, 57, input.z));

            for (Pos trafficLight : trafficLights) {
                batch.setBlock(trafficLight, Block.WHITE_WOOL);
            }
        };

        for (int count = 0; count <= this.rnd.nextInt(1, 2); count++) {
            generateTrafficLight.accept(new TrafficLightsInput(0, this.rnd.nextInt(preRun + 10, length + preRun - 10)));
            generateTrafficLight.accept(new TrafficLightsInput(width, this.rnd.nextInt(preRun + 10, length + preRun - 10)));
        }

        if(width > 30) {
            for (int count = 0; count <= this.rnd.nextInt(1, 2); count++) {
                generateTrafficLight.accept(new TrafficLightsInput(width/2, this.rnd.nextInt(preRun, length + preRun)));
            }
        }

        generateTrafficLight.accept(new TrafficLightsInput(0, length + preRun));
        generateTrafficLight.accept(new TrafficLightsInput(width, length + preRun));

        BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
    }

    @Override
    protected void onStart() {
        AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
        for(int x = 0; x <= width; x++) {
            batch.setBlock(x, 51, preRun-1, Block.AIR);
        }

        BatchUtil.loadAndApplyBatch(batch, this, () -> playSound(Sound.sound(SoundEvent.BLOCK_WOOD_BREAK, Sound.Source.BLOCK, 1f, 1f)));

        scheduler().submitTask(() -> {
            if(!super.isRunning) return TaskSchedule.stop();

            phaseCounter++;
            if(phaseCounter >= 3) phaseCounter = 0;

            if(phaseCounter == 0) phase = LightPhase.RED;
            if(phaseCounter == 1) phase = LightPhase.GREEN;
            if(phaseCounter == 2) phase = LightPhase.YELLOW;

            getPlayers().forEach(player -> {
                for(int i = 0; i < 9; i++) {
                    player.getInventory().setItemStack(i, phase.item);
                }
            });

            AbsoluteBlockBatch changeLightsBatch = new AbsoluteBlockBatch();
            for (Pos trafficLight : trafficLights) {
                changeLightsBatch.setBlock(trafficLight, phase.item.material().block());
            }
            BatchUtil.loadAndApplyBatch(changeLightsBatch, this, () -> {});

            return phase.taskScheduleRandomDuration();
        }, ExecutionType.TICK_END);
    }

    @Override
    protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
        if(isBeforeBeginning) return;

        if(phase.equals(LightPhase.RED) && playerMoveEvent.getNewPosition().z()-0.01 > playerMoveEvent.getPlayer().getPosition().z()) {
            playerMoveEvent.getPlayer().setVelocity(new Vec(0, 8, -15));
            playerMoveEvent.getPlayer().playSound(Sound.sound(SoundEvent.ENTITY_BLAZE_SHOOT, Sound.Source.PLAYER, 1f, 1f));
        }

        if(playerMoveEvent.getNewPosition().z() > preRun+length) {
            getScore().insertResult(playerMoveEvent.getPlayer());
            playerMoveEvent.getPlayer().getInventory().clear();
            playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
        }
    }

    @Override
    public Pos getSpawn() {
        return new Pos((double) width/2, 51, 3);
    }
}
