package net.halbear.aotg.custom.WorldGen.GenerationFeatures;

import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.halbear.aotg.registries.ModTreeGen;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.configurations.TreeConfiguration;
import net.minecraft.world.level.levelgen.feature.foliageplacers.FoliagePlacer;
import net.minecraft.world.level.levelgen.feature.trunkplacers.TrunkPlacer;
import net.minecraft.world.level.levelgen.feature.trunkplacers.TrunkPlacerType;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;

public class BushTwistyBranchingTrunkPlacer extends TrunkPlacer {
    private int BEGINNING_TRUNK_WIDTH = 2;
    private float BranchPossibility = 0.40f;
    public BushTwistyBranchingTrunkPlacer(int baseHeight, int heightRandA, int heightRandB) {
        super(baseHeight, heightRandA, heightRandB);
    }

    public BushTwistyBranchingTrunkPlacer SetTrunkWidth(int BEGINNING_TRUNK_WIDTH){
        this.BEGINNING_TRUNK_WIDTH = BEGINNING_TRUNK_WIDTH;
        return this;
    }

    public BushTwistyBranchingTrunkPlacer SetBranchPossibility(float BranchPossibility){
        this.BranchPossibility = BranchPossibility;
        return this;
    }

    public static final MapCodec<BushTwistyBranchingTrunkPlacer> CODEC = RecordCodecBuilder.mapCodec(instance ->
            trunkPlacerParts(instance).apply(instance, BushTwistyBranchingTrunkPlacer::new)
    );

    @Override
    protected TrunkPlacerType<?> type() {
        return ModTreeGen.BUSH_TWISTY_TRUNK_PLACER.get();
    }

    @Override
    public List<FoliagePlacer.FoliageAttachment> placeTrunk(WorldGenLevel worldGenLevel, BiConsumer<BlockPos, BlockState> biConsumer, RandomSource randomSource, int freeSpace, BlockPos blockPos, TreeConfiguration treeConfiguration) {
        int TRUNK_WIDTH = 2;
        List<FoliagePlacer.FoliageAttachment> leafNodes = new ArrayList<>();
        int mainHeight = this.getTreeHeight(randomSource);
        BlockPos currentPos = blockPos;

        for (int i = 0; i < mainHeight; i++) {
            TRUNK_WIDTH = (int)Math.max(2 * (1.0-((i*0.75)/(double)mainHeight)),1);
            for (int xOffset = 0; xOffset < TRUNK_WIDTH; xOffset++) {
                for (int zOffset = 0; zOffset < TRUNK_WIDTH; zOffset++) {
                    BlockPos thickLogPos = currentPos.offset(xOffset, 0, zOffset);
                    if(i < 2 && randomSource.nextFloat() < 0.40f){
                        generateRoots(worldGenLevel, biConsumer, randomSource, thickLogPos, treeConfiguration);
                    }
                    if ((randomSource.nextInt(10) < 9) || TRUNK_WIDTH < 3)
                        this.placeLog(worldGenLevel, biConsumer, randomSource, thickLogPos, treeConfiguration);
                }
            }
            if (randomSource.nextFloat() < 0.6f && i > 1) {
                currentPos = currentPos.offset(randomSource.nextInt(3) - 1, 0, randomSource.nextInt(3) - 1);
            }

            if (i > 2 && randomSource.nextFloat() < BranchPossibility) {
                BlockPos branchStart = currentPos.offset(randomSource.nextInt(TRUNK_WIDTH), 0, randomSource.nextInt(TRUNK_WIDTH));
                BlockPos branchEnd = generateBranch(worldGenLevel, biConsumer, randomSource, branchStart, treeConfiguration,i, leafNodes);

                leafNodes.add(new FoliagePlacer.FoliageAttachment(branchEnd, randomSource.nextInt(2) + (Math.max((int)(i/(double)(mainHeight/1.25))*3,1)), false));
            } else if(i > 2 && i == mainHeight - 1){
                for(int l = 0; l < 2 + randomSource.nextInt(3); l++){
                    BlockPos branchStart = currentPos.offset(randomSource.nextInt(TRUNK_WIDTH), 0, randomSource.nextInt(TRUNK_WIDTH));
                    BlockPos branchEnd = generateBranch(worldGenLevel, biConsumer, randomSource, branchStart, treeConfiguration,i, leafNodes);

                    leafNodes.add(new FoliagePlacer.FoliageAttachment(branchEnd, randomSource.nextInt(2) + (Math.max((int)(i/(double)(mainHeight/1.25))*3,1)), false));
                }
            }

            currentPos = currentPos.above();
        }

        for (int xOffset = 0; xOffset < TRUNK_WIDTH; xOffset++) {
            for (int zOffset = 0; zOffset < TRUNK_WIDTH; zOffset++) {
                leafNodes.add(new FoliagePlacer.FoliageAttachment(currentPos.offset(xOffset, 0, zOffset), 0, false));
            }
        }
        return leafNodes;
    }

    private BlockPos generateRoots(WorldGenLevel level, BiConsumer<BlockPos, BlockState> blockSetter,
                                    RandomSource random, BlockPos branchStart, TreeConfiguration config) {
        int dirX = random.nextBoolean() ? 1 : -1;
        int dirZ = random.nextBoolean() ? 1 : -1;
        int branchLength = 2 + random.nextInt(6);

        BlockPos bPos = branchStart;
        for (int b = 0; b < branchLength; b++) {
            int stepX = random.nextFloat() < 0.7f ? dirX : 0;
            int stepZ = random.nextFloat() < 0.7f ? dirZ : 0;
            int stepY = random.nextFloat() < 0.4f ? -1 : 0;
            bPos = bPos.offset(stepX, stepY, stepZ);
            this.placeLog(level, blockSetter, random, bPos, config);
        }
        return bPos;
    }

    private BlockPos generateBranch(WorldGenLevel level, BiConsumer<BlockPos, BlockState> blockSetter,
            RandomSource random, BlockPos branchStart, TreeConfiguration config, int CurrentTreeHeight,List<FoliagePlacer.FoliageAttachment> leafNodes) {
        int dirX = random.nextBoolean() ? 1 : -1;
        int dirZ = random.nextBoolean() ? 1 : -1;
        int branchLength = 3 + random.nextInt(Math.min(CurrentTreeHeight,12));

        BlockPos bPos = branchStart;
        for (int b = 0; b < branchLength; b++) {
            int stepX = random.nextFloat() < 0.7f ? dirX : 0;
            int stepZ = random.nextFloat() < 0.7f ? dirZ : 0;
            int stepY = random.nextFloat() < 0.4f ? 1 : 0;
            bPos = bPos.offset(stepX, stepY, stepZ);
            this.placeLog(level, blockSetter, random, bPos, config);
            if (random.nextDouble() < 0.25) {
                leafNodes.add(new FoliagePlacer.FoliageAttachment(bPos, random.nextInt(2), false));
            }
        }
        return bPos;
    }

    public static int checkPointPosition(double xp, double yp, double xc, double yc, double r) {
        double dx = xp - xc;
        double dy = yp - yc;
        double squaredDistance = (dx * dx) + (dy * dy);

        double squaredRadius = r * r * 1.25;

        if (squaredDistance < squaredRadius) {
            return -1;
        } else if (squaredDistance == squaredRadius) {
            return 0;
        } else {
            return 1;
        }
    }
}
