/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.towerrun.generator;

import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.session.ClipboardHolder;
import de.steamwar.sql.SchematicData;
import de.steamwar.sql.SchematicNode;
import de.steamwar.towerrun.TowerRun;
import de.steamwar.towerrun.config.WorldConfig;
import de.steamwar.towerrun.game.TowerRunGame;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.*;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.type.Door;
import org.bukkit.scheduler.BukkitRunnable;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;

public class TowerGenerator {

    private static final Random random = new Random();
    private final WorldConfig.TowerGeneratorConfig config;
    private final List<SchematicNode> allSchematics;
    private final Clipboard roof;

    @Getter
    private int height;

    @Getter
    private Location spawn;

    @Getter
    private List<Location> keys = new ArrayList<>();

    public TowerGenerator(WorldConfig.TowerGeneratorConfig config) {
        File file = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "Roof.schem");
        if (!file.exists()) {
            Bukkit.shutdown();
            throw new SecurityException("TowerRun schematic not found");
        }
        roof = loadSchematic(file);

        this.config = config;
        allSchematics = SchematicNode.getAllSchematicsOfType(config.schematicType);
        spawn = WorldConfig.SPAWN;
    }

    private Clipboard loadSchematic(File file) {
        Clipboard clipboard;
        try (ClipboardReader reader = Objects.requireNonNull(ClipboardFormats.findByFile(file)).getReader(new FileInputStream(file))) {
            clipboard = reader.read();
        } catch (NullPointerException | IOException e) {
            Bukkit.shutdown();
            throw new SecurityException("TowerRun schematic not found", e);
        }
        return clipboard;
    }

    public void generate() {
        new BukkitRunnable() {
            int height = random.nextInt(config.maxHeight - config.minHeight) + config.minHeight;
            int y = TowerGenerator.this.config.y;
            int noBombFloors;
            int noKeyFloors;

            {
                TowerGenerator.this.height = 0;
                noBombFloors = random.nextInt(config.maxNoBombFloors - config.minNoBombFloors) + config.minNoBombFloors;
                noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
                keys.clear();
            }

            @Override
            public void run() {
                if (height > 0) {
                    SchematicNode schematicNode = allSchematics.get(random.nextInt(allSchematics.size()));
                    SchematicData schematicData = new SchematicData(schematicNode);
                    int currentY;
                    int width;
                    int depth;
                    try {
                        Clipboard clipboard = schematicData.load();
                        try (EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(new BukkitWorld(Bukkit.getWorlds().get(0)), -1)) {
                            ClipboardHolder ch = new ClipboardHolder(clipboard);
                            Operations.completeBlindly(ch.createPaste(e).to(BlockVector3.at(config.x, y, config.z)).build());
                        }
                        width = clipboard.getDimensions().x();
                        depth = clipboard.getDimensions().z();
                        currentY = y;
                        y += clipboard.getDimensions().y();
                        height -= clipboard.getDimensions().y();
                        TowerGenerator.this.height += clipboard.getDimensions().y();
                    } catch (IOException e) {
                        allSchematics.remove(schematicNode);
                        return;
                    }
                    spawn = WorldConfig.SPAWN.clone().add(0, y, 0);

                    List<Container> chestBlocks = new ArrayList<>();
                    for (int x = config.x; x < config.x + width; x++) {
                        for (int z = config.z; z < config.z + depth; z++) {
                            for (int y = currentY; y < this.y; y++) {
                                Block block = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
                                BlockState blockState = block.getState();
                                if (blockState instanceof Chest) {
                                    chestBlocks.add((Container) blockState);
                                }
                            }
                        }
                    }

                    noBombFloors--;
                    if (noBombFloors < 0 && random.nextDouble() < config.tntChance) {
                        noBombFloors = random.nextInt(config.maxNoBombFloors - config.minNoBombFloors) + config.minNoBombFloors;
                        int bombCount = random.nextInt(config.maxBombs - config.minBombs) + config.minBombs;
                        for (int i = 0; i < bombCount; i++) {
                            WorldConfig.Region region = config.tntRegions[random.nextInt(config.tntRegions.length)];
                            int x = random.nextInt(region.max.getBlockX() - region.min.getBlockX()) + region.min.getBlockX();
                            int z = random.nextInt(region.max.getBlockZ() - region.min.getBlockZ()) + region.min.getBlockZ();
                            int y = currentY + 1;
                            Bukkit.getWorlds().get(0).getBlockAt(x, y, z).setType(Material.TNT, true);
                        }
                    }

                    noKeyFloors--;
                    if (!chestBlocks.isEmpty() && noKeyFloors < 0 && random.nextDouble() < config.keyChance) {
                        noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
                        for (int i = 0; i < 2 && !chestBlocks.isEmpty(); i++) {
                            Container container = chestBlocks.remove(random.nextInt(chestBlocks.size()));
                            keys.add(container.getLocation());
                        }

                        for (WorldConfig.TowerGeneratorDoorBlock doorBlock : config.doorBlocks) {
                            int x = doorBlock.getX();
                            int y = currentY + doorBlock.getDy();
                            int z = doorBlock.getZ();
                            Block block = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
                            for (int i = 0; i < 5; i++) {
                                if (block.getType().isAir()) {
                                    block.setType(Material.IRON_BLOCK, false);
                                    block = block.getRelative(0, 1, 0);
                                } else {
                                    break;
                                }
                            }

                            Bukkit.getScheduler().runTaskLater(TowerRun.getInstance(), () -> {
                                Block door = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
                                door.setType(Material.IRON_DOOR, false);
                                door = door.getRelative(0, 1, 0);
                                door.setType(Material.IRON_DOOR, false);
                            }, 10);
                            Bukkit.getScheduler().runTaskLater(TowerRun.getInstance(), () -> {
                                Block door = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
                                Door doorState = (Door) door.getBlockData();
                                doorState.setFacing(BlockFace.EAST);
                                door.setBlockData(doorState, false);

                                door = door.getRelative(0, 1, 0);
                                doorState = (Door) door.getBlockData();
                                doorState.setFacing(BlockFace.EAST);
                                doorState.setHalf(Bisected.Half.TOP);
                                door.setBlockData(doorState, false);
                            }, 20);
                        }
                    }

                    for (WorldConfig.TowerGeneratorFillRegion fillRegion : config.fillRegions) {
                        for (int x = fillRegion.getMinX(); x < fillRegion.getMaxX(); x++) {
                            for (int z = fillRegion.getMinZ(); z < fillRegion.getMaxZ(); z++) {
                                for (int y = currentY; y < this.y; y++) {
                                    Block block = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
                                    if (!block.getType().isAir()) {
                                        continue;
                                    }
                                    if (random.nextDouble() < fillRegion.getPercentage()) {
                                        block.setType(fillRegion.getMaterial(), true);
                                    }
                                }
                            }
                        }
                    }
                } else {
                    cancel();
                    try (EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(new BukkitWorld(Bukkit.getWorlds().get(0)), -1)) {
                        ClipboardHolder ch = new ClipboardHolder(roof);
                        Operations.completeBlindly(ch.createPaste(e).to(BlockVector3.at(config.x, y, config.z)).build());
                    }
                    TowerRunGame.start();
                }
            }
        }.runTaskTimer(TowerRun.getInstance(), 0, 10);
    }
}
