package net.yyc12345.teyvatcraft.init;

import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;

import java.util.function.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectionContext;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.SweetBerryBushBlock;
import net.minecraft.state.property.Property;
import net.minecraft.util.Identifier;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.gen.GenerationStep;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.ConfiguredFeatures;
import net.minecraft.world.gen.feature.Feature;
import net.minecraft.world.gen.feature.OreFeatureConfig;
import net.minecraft.world.gen.feature.RandomPatchFeatureConfig;
import net.minecraft.world.gen.feature.ConfiguredFeatures.Decorators;
import net.minecraft.world.gen.placer.BlockPlacer;
import net.minecraft.world.gen.placer.SimpleBlockPlacer;
import net.minecraft.world.gen.stateprovider.BlockStateProvider;
import net.minecraft.world.gen.stateprovider.SimpleBlockStateProvider;
import net.minecraft.world.gen.stateprovider.WeightedBlockStateProvider;
import net.yyc12345.teyvatcraft.blocks.TeyvatSweetBerryBushLikePlantBlock;
import net.yyc12345.teyvatcraft.mixin.FlowerForestAccess;

public class TerrainsManager {

    private static final Block[] NORMAL__FLOWERS = new Block[] {
        BlocksManager.DANDELION_SEED,
        BlocksManager.SMALL_LAMP_GRASS,
        BlocksManager.MINT,
        BlocksManager.SWEET_FLOWER,
        BlocksManager.MATSUTAKE,
        BlocksManager.GLAZE_LILY,
        BlocksManager.PHILANEMO_MUSHROOM,
        BlocksManager.WINDWHEEL_ASTER
    };
    private static final Block[] SWAMP__FLOWERS = new Block[] {
        BlocksManager.CALLA_LILY,
        BlocksManager.SNAPDRAGON,
        BlocksManager.HORSETAIL
    };
    private static final Block[] MOUNTAINS__FLOWERS = new Block[] {
        BlocksManager.CECILIA,
        BlocksManager.QINGXIN
    };

    private static final Block[] NORMAL__SWEET_BERRY_BUSH = new Block[] {
        BlocksManager.JUEYUN_CHILI,
        BlocksManager.BERRY,
        BlocksManager.SILK_FLOWER,
        BlocksManager.VALBERRY,
        BlocksManager.WOLFHOOK,
    };

    // ==================== ore gen
    private static ConfiguredFeature<?, ?> OREGEN_MAGICAL_CRYSTAL_CHUNK_ORE = generateOreFreature(2, 12, 5, BlocksManager.MAGICAL_CRYSTAL_CHUNK_ORE);
    private static ConfiguredFeature<?, ?> OREGEN_CRYSTAL_CHUNK_ORE = generateOreFreature(4, 32, 12, BlocksManager.CRYSTAL_CHUNK_ORE);
    private static ConfiguredFeature<?, ?> OREGEN_WHITE_IRON_CHUNK_ORE = generateOreFreature(4, 64, 24, BlocksManager.WHITE_IRON_CHUNK_ORE);
    private static ConfiguredFeature<?, ?> OREGEN_IRON_CHUNK_ORE = generateOreFreature(8, 64, 24, BlocksManager.IRON_CHUNK_ORE);
    private static ConfiguredFeature<?, ?> OREGEN_STARSILVER_ORE = generateOreFreature(4, 64, 24, BlocksManager.STARSILVER_ORE);
    private static ConfiguredFeature<?, ?> OREGEN_NOCTILUCOUS_JADE_ORE = generateOreFreature(4, 64, 24, BlocksManager.NOCTILUCOUS_JADE_ORE);
    private static ConfiguredFeature<?, ?> OREGEN_COR_LAPIS_ORE = generateOreFreature(4, 64, 24, BlocksManager.COR_LAPIS_ORE);

    private static ConfiguredFeature<?, ?> FLOWERGEN_NORMAL = generateFlowerLikePlantFreature(NORMAL__FLOWERS);
    private static ConfiguredFeature<?, ?> FLOWERGEN_SWAMP = generateFlowerLikePlantFreature(SWAMP__FLOWERS);
    private static ConfiguredFeature<?, ?> FLOWERGEN_MOUNTAINS = generateFlowerLikePlantFreature(MOUNTAINS__FLOWERS);
    private static ConfiguredFeature<?, ?> SBBGEN_NORMAL = generateSweetBerryBushLikePlantFreature(NORMAL__SWEET_BERRY_BUSH);

    // ================ feature generator
    private static ConfiguredFeature<?, ?> generateOreFreature(int veinSize, int maxY, int repeat, Block blk) {
        return Feature.ORE
        .configure(new OreFeatureConfig(
            OreFeatureConfig.Rules.BASE_STONE_OVERWORLD,
            blk.getDefaultState(),
            veinSize))
        .rangeOf(maxY)
        .spreadHorizontally()
        .repeat(repeat);
    }
    private static ConfiguredFeature<?, ?> generateFlowerLikePlantFreature(Block[] flowerBlks) {
        WeightedBlockStateProvider provider = new WeightedBlockStateProvider();
        for(Block item : flowerBlks) {
            provider.addState(item.getDefaultState(), 1);
        }

        return Feature.FLOWER
        .configure((new RandomPatchFeatureConfig.Builder(
            (BlockStateProvider)provider, 
            (BlockPlacer)SimpleBlockPlacer.INSTANCE))
            .tries(32)
            .build())
        .decorate(Decorators.SPREAD_32_ABOVE)
        .decorate(Decorators.SQUARE_HEIGHTMAP)
        .repeat(16);
    }
    private static ConfiguredFeature<?, ?> generateSweetBerryBushLikePlantFreature(Block[] plantBlks) {
        WeightedBlockStateProvider provider = new WeightedBlockStateProvider();
        for(Block item : plantBlks) {
            provider.addState(item.getDefaultState().with(SweetBerryBushBlock.AGE, Integer.valueOf(3)), 1);
        }

        return Feature.RANDOM_PATCH
        .configure((new RandomPatchFeatureConfig.Builder(
            (BlockStateProvider)provider, 
            (BlockPlacer)SimpleBlockPlacer.INSTANCE))
            .tries(64)
            .whitelist((Set<Block>)ImmutableSet.of(Blocks.GRASS_BLOCK.getDefaultState().getBlock()))
            .cannotProject()
            .build())
        .decorate(Decorators.SQUARE_HEIGHTMAP_SPREAD_DOUBLE)
        .applyChance(24);
    }

    // ================ biome register
    private static Predicate<BiomeSelectionContext> getAllBiomeSelector() {
        return BiomeSelectors.foundInOverworld();
    }
    private static Predicate<BiomeSelectionContext> getPlainBiomeSelector() {
        return BiomeSelectors.includeByKey(
            BiomeKeys.PLAINS,
            BiomeKeys.FOREST,
            BiomeKeys.BIRCH_FOREST,
            BiomeKeys.TALL_BIRCH_FOREST,
            BiomeKeys.BIRCH_FOREST_HILLS,
            BiomeKeys.TALL_BIRCH_HILLS
        );
    }
    private static Predicate<BiomeSelectionContext> getMountainsBiomeSelector() {
        return BiomeSelectors.includeByKey(
            BiomeKeys.MOUNTAINS,
            BiomeKeys.GRAVELLY_MOUNTAINS,
            BiomeKeys.WOODED_MOUNTAINS,
            BiomeKeys.MODIFIED_GRAVELLY_MOUNTAINS
        );
    }
    private static Predicate<BiomeSelectionContext> getSwampBiomeSelector() {
        return BiomeSelectors.includeByKey(
            BiomeKeys.SWAMP,
            BiomeKeys.SWAMP_HILLS
        );
    }
    private static Predicate<BiomeSelectionContext> getBadlandsBiomeSelector() {
        return BiomeSelectors.includeByKey(
            BiomeKeys.BADLANDS,
            BiomeKeys.ERODED_BADLANDS,
            BiomeKeys.WOODED_BADLANDS_PLATEAU,
            BiomeKeys.MODIFIED_BADLANDS_PLATEAU,
            BiomeKeys.BADLANDS_PLATEAU
        );
    }
    private static Predicate<BiomeSelectionContext> getFrozenBiomeSelector() {
        return BiomeSelectors.includeByKey(
            BiomeKeys.SNOWY_TUNDRA,
            BiomeKeys.ICE_SPIKES,
            BiomeKeys.SNOWY_TAIGA,
            BiomeKeys.SNOWY_TAIGA_MOUNTAINS,
            BiomeKeys.FROZEN_RIVER,
            BiomeKeys.SNOWY_BEACH,
            BiomeKeys.SNOWY_MOUNTAINS
        );
    }

    private static void registerOreGeneration(String mIdentifier, ConfiguredFeature<?, ?> genFeature, Predicate<BiomeSelectionContext> biomeSelector) {
        RegistryKey<ConfiguredFeature<?, ?>> regKey = RegistryKey.of(Registry.CONFIGURED_FEATURE_WORLDGEN,
            new Identifier("teyvatcraft", mIdentifier));
        Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, regKey.getValue(), genFeature);
        BiomeModifications.addFeature(biomeSelector, GenerationStep.Feature.UNDERGROUND_ORES, regKey);
    }

    private static void registerPlantGeneration(String mIdentifier, ConfiguredFeature<?, ?> genFeature, Predicate<BiomeSelectionContext> biomeSelector) {
        RegistryKey<ConfiguredFeature<?, ?>> regKey = RegistryKey.of(Registry.CONFIGURED_FEATURE_WORLDGEN,
            new Identifier("teyvatcraft", mIdentifier));
        Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, regKey.getValue(), genFeature);
        BiomeModifications.addFeature(biomeSelector, GenerationStep.Feature.VEGETAL_DECORATION, regKey);
    }

    public static void RegisterAll() {
        // ore gen
        registerOreGeneration("oregen_magical_crystal_ore", OREGEN_MAGICAL_CRYSTAL_CHUNK_ORE, getAllBiomeSelector());
        registerOreGeneration("oregen_crystal_ore", OREGEN_CRYSTAL_CHUNK_ORE, getAllBiomeSelector());
        registerOreGeneration("oregen_white_iron_ore", OREGEN_WHITE_IRON_CHUNK_ORE, getAllBiomeSelector());
        registerOreGeneration("oregen_iron_ore", OREGEN_IRON_CHUNK_ORE, getAllBiomeSelector());
        registerOreGeneration("oregen_starsilver_ore", OREGEN_STARSILVER_ORE, getFrozenBiomeSelector());
        registerOreGeneration("oregen_noctilucous_jade_ore", OREGEN_NOCTILUCOUS_JADE_ORE, getBadlandsBiomeSelector());
        registerOreGeneration("oregen_cor_lapis_ore", OREGEN_COR_LAPIS_ORE, getBadlandsBiomeSelector());

        // flower gen
        registerPlantGeneration("flowergen_normal", FLOWERGEN_NORMAL, getPlainBiomeSelector());
        registerPlantGeneration("flowergen_swamp", FLOWERGEN_SWAMP, getSwampBiomeSelector());
        registerPlantGeneration("flowergen_mountains", FLOWERGEN_MOUNTAINS, getMountainsBiomeSelector());

        registerPlantGeneration("sbbgen_normal", SBBGEN_NORMAL, getPlainBiomeSelector());

        // patch flower forest
        BlockState[] flowerForestArray = FlowerForestAccess.getFlowers();
        List<BlockState> flowerForestList = new ArrayList<BlockState>();
        for(BlockState item : flowerForestArray) {
            flowerForestList.add(item);
        }
        for(Block item : NORMAL__FLOWERS) {
            flowerForestList.add(item.getDefaultState());
        }
        BlockState[] newFlowerForestArray = flowerForestList.toArray(new BlockState[flowerForestList.size()]);
        FlowerForestAccess.setFlowers(newFlowerForestArray);

    }
}
