package eu.mhsl.minecraft.pixelblocks.pixelblock;

import eu.mhsl.minecraft.pixelblocks.Main;
import eu.mhsl.minecraft.pixelblocks.utils.Direction;
import eu.mhsl.minecraft.pixelblocks.utils.LocationUtil;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.Rotatable;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;

public class PixelBlockWorld {
    private final PixelBlock parentPixelBlock;
    private final World world;

    int worldGrassBorderWidth = 10;
    int pixelsPerBlock = Main.configuration().pixelsPerBlock();

    // Kapselt das Namensschema der Pixelwelten: <dataFolder>/worlds/<blockUUID>
    private static String worldsBasePath;

    private static @NotNull String getWorldsBasePath() {
        if(worldsBasePath == null) {
            worldsBasePath = Main.plugin().getDataFolder().getPath() + File.separator + "worlds";
        }
        return worldsBasePath;
    }

    public static boolean isPixelWorld(@NotNull World world) {
        return world.getName().startsWith(getWorldsBasePath());
    }

    public static @Nullable UUID getUUIDFromWorld(@NotNull World world) {
        if(!isPixelWorld(world)) return null;
        String worldName = world.getName();
        try {
            return UUID.fromString(worldName.substring(worldName.lastIndexOf(File.separatorChar) + 1));
        } catch(IllegalArgumentException e) {
            return null;
        }
    }

    public PixelBlockWorld(PixelBlock parentPixelBlock) {
        this.parentPixelBlock = parentPixelBlock;
        this.world = this.loadOrCreatePixelWorld();
        this.setBuildingPlatform();
    }

    public boolean allowPlacements(Location blockLocation) {
        Location origin = getBuildOrigin();
        int offset = pixelsPerBlock - 1;
        return blockLocation.x() >= origin.x() && blockLocation.x() <= origin.x() + offset
            && blockLocation.z() >= origin.z() && blockLocation.z() <= origin.z() + offset
            && blockLocation.y() >= origin.y() && blockLocation.y() <= origin.y() + offset;
    }

    public @NotNull String getWorldPathName() {
        return getWorldsBasePath() + File.separator + this.parentPixelBlock.getBlockUUID();
    }

    public @NotNull Location getSpawnLocation() {
        return this.getPortalLocation().add(1.5, 1, 2.5);
    }

    public @NotNull Location getPortalLocation() {
        return this.getBuildOrigin().add((double) pixelsPerBlock / 2 - 2, 0, -worldGrassBorderWidth + 3);
    }

    public @NotNull List<Player> getPlayersInWorld() {
        return this.world.getPlayers();
    }

    public @NotNull List<Entity> getEntitiesInWorld() {
        return this.world.getEntities();
    }

    public @NotNull Location getBuildOrigin() {
        return new Location(this.world, 0, -60, 0);
    }

    public @NotNull Location getBorderOrigin() {
        return getBuildOrigin().subtract(1, 1, 1);
    }

    public @NotNull Location getPlatformOrigin() {
        return getBorderOrigin().subtract(worldGrassBorderWidth, 0, worldGrassBorderWidth);
    }

    public @NotNull Location getPlatformOriginEnd() {
        return getBorderOrigin().add(worldGrassBorderWidth + pixelsPerBlock, 0, worldGrassBorderWidth + pixelsPerBlock);
    }

    public record PixelData(Vector relativeLocation, BlockData block, @Nullable Directional directional, @Nullable Rotatable rotatable, BlockState state, double scale) {
    }

    public List<PixelData> getPixels(Direction direction) {
        List<PixelData> pixelData = new ArrayList<>();
        Location origin = this.getBuildOrigin();
        int max = pixelsPerBlock - 1;

        for(int x = 0; x < pixelsPerBlock; x++) {
            for(int y = 0; y < pixelsPerBlock; y++) {
                for(int z = 0; z < pixelsPerBlock; z++) {
                    int blockX = switch(direction) {
                        case south -> x;
                        case north -> max - x;
                        case east -> max - z;
                        case west -> z;
                    };
                    int blockZ = switch(direction) {
                        case south -> z;
                        case north -> max - z;
                        case east -> x;
                        case west -> max - x;
                    };

                    Block block = this.world.getBlockAt(origin.getBlockX() + blockX, origin.getBlockY() + y, origin.getBlockZ() + blockZ);
                    BlockData blockData = block.getBlockData();
                    if(blockData.getMaterial().isAir()) continue;

                    @Nullable Directional directional = blockData instanceof Directional face ? face : null;
                    @Nullable Rotatable rotatable = blockData instanceof Rotatable rotation ? rotation : null;
                    pixelData.add(new PixelData(new Vector(x, y, z), blockData, directional, rotatable, block.getState(), (double) 1 / pixelsPerBlock));
                }
            }
        }

        return pixelData;
    }

    private World loadOrCreatePixelWorld() {
        final WorldCreator worldCreator = new WorldCreator(getWorldPathName());

        worldCreator.type(WorldType.FLAT);
        worldCreator.generator(new ChunkGenerator() {
        });

        World world = Bukkit.createWorld(worldCreator);
        Objects.requireNonNull(world);

        world.setGameRule(GameRules.RANDOM_TICK_SPEED, 0);
        world.setGameRule(GameRules.FIRE_SPREAD_RADIUS_AROUND_PLAYER, 0);
        world.setGameRule(GameRules.SPAWN_MOBS, false);
        world.setGameRule(GameRules.ADVANCE_WEATHER, false);
        world.setGameRule(GameRules.SPREAD_VINES, false);
        world.setGameRule(GameRules.ADVANCE_TIME, false);

        WorldBorder worldBorder = world.getWorldBorder();
        worldBorder.setCenter(getBuildOrigin().add((double) pixelsPerBlock / 2, 0, (double) pixelsPerBlock / 2));
        worldBorder.setSize(pixelsPerBlock + (2 * worldGrassBorderWidth));
        worldBorder.setWarningDistance(0);
        worldBorder.setDamageAmount(0);
        return world;
    }

    private static final List<Material> FLOWERS = List.of(
        Material.DANDELION,
        Material.POPPY,
        Material.BLUE_ORCHID,
        Material.ALLIUM,
        Material.AZURE_BLUET,
        Material.RED_TULIP,
        Material.ORANGE_TULIP,
        Material.WHITE_TULIP,
        Material.CORNFLOWER,
        Material.LILY_OF_THE_VALLEY,
        Material.SHORT_GRASS,
        Material.TALL_GRASS
    );

    private void setBuildingPlatform() {
        Bukkit.getScheduler().runTask(Main.plugin(), () -> {
            int platformSize = (pixelsPerBlock + 2) + 2 * worldGrassBorderWidth;
            Location platformOrigin = this.getPlatformOrigin();
            for(int x = 0; x < platformSize; x++) {
                for(int z = 0; z < platformSize; z++) {
                    platformOrigin.clone().add(x, 0, z).getBlock().setType(Material.GRASS_BLOCK);
                    platformOrigin.clone().add(x, -1, z).getBlock().setType(Material.DIRT);
                }
            }

            int borderSize = pixelsPerBlock + 2;
            Location borderOrigin = this.getBorderOrigin();
            for(int x = 0; x < borderSize; x++) {
                for(int z = 0; z < borderSize; z++) {
                    if(x == 0 || z == 0 || x == borderSize - 1 || z == borderSize - 1) {
                        borderOrigin.clone().add(x, 0, z).getBlock().setType(Material.RED_CONCRETE);
                    }
                }
            }

            Random random = new Random();
            LocationUtil.iterateBlocks(getPlatformOrigin().add(1, 1, 1), getPlatformOriginEnd().add(0, 1, 0), location -> {
                if(allowPlacements(location)) return;
                if(!location.clone().subtract(0, 1, 0).getBlock().getType().equals(Material.GRASS_BLOCK)) return;

                if(FLOWERS.contains(location.getBlock().getType())) location.getBlock().setType(Material.AIR);
                if(!location.getBlock().getType().equals(Material.AIR)) return;

                if(random.nextInt(30) == 0) {
                    location.getBlock().setType(FLOWERS.get(random.nextInt(FLOWERS.size())));
                }
            });

            Location portalLocation = this.getPortalLocation();
            for(int x = 0; x < 4; x++) {
                for(int y = 0; y < 5; y++) {
                    portalLocation.clone().add(x, y, 0).getBlock().setType(Material.OBSIDIAN);
                }
            }
            for(int x = 1; x < 3; x++) {
                for(int y = 1; y < 4; y++) {
                    portalLocation.clone().add(x, y, 0).getBlock().setType(Material.NETHER_PORTAL);
                }
            }
        });
    }
}
