/*
 *   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.bausystem.features.shieldprinting;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.shieldprinting.impl.*;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService;
import de.steamwar.inventory.SWItem;
import de.steamwar.inventory.SWListInv;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Powerable;
import org.bukkit.block.data.type.*;
import org.bukkit.boss.BarColor;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.util.Vector;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

public class ShieldPrinting implements Listener {

    private static final World WORLD = Bukkit.getWorlds().get(0);

    static {
        BauSystem.runTaskTimer(BauSystem.getInstance(), () -> {
            ShieldPrintingCommand.SHIELD_PRINTING_MAP.values().forEach(shieldPrinting -> {
                shieldPrinting.fallingBlocks.replaceAll((entity, location) -> {
                    if (entity.isDead()) return null;
                    return location;
                });
            });
        }, 1, 1);
    }

    /**
     * Vector of current position, Vector of origin
     */
    private Map<Vector, Vector> shieldMap = new HashMap<>();
    private Map<Vector, BlockData> shieldData = new HashMap<>();

    private final Region region;

    public ShieldPrinting(Region region) {
        this.region = region;
        Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
        updateBossbars();
    }

    public void copy() {
        for (Map.Entry<Vector, Vector> entry : shieldMap.entrySet()) {
            shieldData.put(entry.getValue(), entry.getKey().toLocation(WORLD).getBlock().getBlockData());
        }
        updateBossbars();
    }

    public void apply(Player player) {
        Map<Material, BlockDataConfiguration<?>[]> stateConfiguration = new HashMap<>();
        for (Map.Entry<Vector, BlockData> entry : shieldData.entrySet()) {
            BlockData blockData = entry.getKey().toLocation(WORLD).getBlock().getBlockData();
            if (entry.getValue().getMaterial() != blockData.getMaterial()) {
                continue;
            }
            if (entry.getValue().equals(blockData)) {
                continue;
            }
            if (entry.getValue() instanceof Powerable) {
                get(entry, stateConfiguration)[0] = new PowerableConfiguration(entry.getValue().getMaterial());
            }
            if (entry.getValue() instanceof Piston) {
                get(entry, stateConfiguration)[1] = new PistonConfiguration(entry.getValue().getMaterial());
            }
            if (entry.getValue() instanceof Wall) {
                get(entry, stateConfiguration)[2] = new WallConfiguration(entry.getValue().getMaterial());
            }
            if (entry.getValue() instanceof Fence) {
                get(entry, stateConfiguration)[3] = new FenceConfiguration(entry.getValue().getMaterial());
            }
            if (entry.getValue() instanceof Gate || entry.getValue() instanceof TrapDoor) {
                get(entry, stateConfiguration)[4] = new OpenableConfiguration(entry.getValue().getMaterial());
            }
        }
        if (!stateConfiguration.isEmpty()) {
            List<SWListInv.SWListEntry<BlockDataConfiguration<?>>> entries = new ArrayList<>();
            Runnable entriesUpdater = () -> {
                entries.clear();

                for (Map.Entry<Material, BlockDataConfiguration<?>[]> entry : stateConfiguration.entrySet()) {
                    for (BlockDataConfiguration<?> blockDataConfiguration : entry.getValue()) {
                        if (blockDataConfiguration == null) continue;
                        entries.add(new SWListInv.SWListEntry<>(blockDataConfiguration.get(player), blockDataConfiguration));
                    }
                }
            };
            entriesUpdater.run();

            AtomicReference<SWListInv<BlockDataConfiguration<?>>> invReference = new AtomicReference<>();
            SWListInv<BlockDataConfiguration<?>> inv = new SWListInv<>(player, BauSystem.MESSAGE.parse("SHIELD_PRINTING_GUI_NAME", player), false, entries, (clickType, blockDataConfiguration) -> {
                if (clickType.isRightClick()) {
                    blockDataConfiguration.previous();
                } else {
                    blockDataConfiguration.next();
                }
                entriesUpdater.run();
                invReference.get().open();
            });
            invReference.set(inv);

            inv.setItem(49, new SWItem(Material.DISPENSER, BauSystem.MESSAGE.parse("SHIELD_PRINTING_GUI_APPLY", player), clickType -> {
                paste(stateConfiguration);
                player.getOpenInventory().close();
                BauSystem.MESSAGE.send("SHIELD_PRINTING_APPLY", player);
            }));
            inv.open();
            return;
        }
        paste(stateConfiguration);
        BauSystem.MESSAGE.send("SHIELD_PRINTING_APPLY", player);
    }

    private BlockDataConfiguration<?>[] get(Map.Entry<Vector, BlockData> entry, Map<Material, BlockDataConfiguration<?>[]> stateConfiguration) {
        return stateConfiguration.computeIfAbsent(entry.getValue().getMaterial(), material -> new BlockDataConfiguration[5]);
    }

    private void paste(Map<Material, BlockDataConfiguration<?>[]> stateConfiguration) {
        for (Map.Entry<Vector, BlockData> entry : shieldData.entrySet()) {
            BlockData copied = entry.getValue();
            if (copied.getMaterial().isAir()) {
                continue;
            }

            Block block = entry.getKey().toLocation(WORLD).getBlock();
            if (entry.getValue().getMaterial() != block.getType()) {
                block.setBlockData(entry.getValue(), false);
                continue;
            }
            if (entry.getValue().equals(block.getBlockData())) {
                continue;
            }

            BlockDataConfiguration[] stateConfigurations = stateConfiguration.get(entry.getValue().getMaterial());
            if (stateConfigurations == null) {
                continue;
            }

            BlockData worldOriginal = block.getBlockData();
            copied = copied.clone();
            for (BlockDataConfiguration blockDataConfiguration : stateConfigurations) {
                if (blockDataConfiguration == null) continue;
                blockDataConfiguration.apply(copied, worldOriginal);
            }
            block.setBlockData(copied, false);
        }
    }

    public void disable() {
        HandlerList.unregisterAll(this);
        shieldMap.clear();
        shieldData.clear();

        for (Player player : Bukkit.getOnlinePlayers()) {
            BossBarService.instance.remove(player, region, "shieldprinting");
        }
    }

    @EventHandler
    public void onBlockPistonExtend(BlockPistonExtendEvent event) {
        update(event.getDirection(), event.getBlocks());
    }

    @EventHandler
    public void onBlockPistonRetract(BlockPistonRetractEvent event) {
        update(event.getDirection(), event.getBlocks());
    }

    private void update(BlockFace direction, List<Block> blockList) {
        Set<Vector> toRemove = new HashSet<>();
        Map<Vector, Vector> temp = new HashMap<>();
        for (Block block : blockList) {
            if (Region.getRegion(block.getLocation()) != region) continue;
            if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) continue;
            Vector vector = block.getLocation().toVector();
            Vector origin = vector.clone();
            vector = vector.add(direction.getDirection());
            if (shieldMap.containsKey(origin)) {
                toRemove.add(origin);
                temp.put(vector, shieldMap.get(origin));
            } else {
                temp.put(vector, origin);
            }
        }
        shieldMap.keySet().removeAll(toRemove);
        shieldMap.putAll(temp);

        toRemove.clear();
        for (Map.Entry<Vector, Vector> entry : shieldMap.entrySet()) {
            if (entry.getKey().equals(entry.getValue())) {
                toRemove.add(entry.getKey());
            }
        }
        shieldMap.keySet().removeAll(toRemove);

        updateBossbars();
    }

    private Map<Entity, Location> fallingBlocks = new HashMap<>();

    @EventHandler
    public void onEntitySpawn(EntitySpawnEvent event) {
        if (event.getEntityType() != EntityType.FALLING_BLOCK) return;
        if (Region.getRegion(event.getLocation()) != region) return;
        fallingBlocks.put(event.getEntity(), event.getLocation().getBlock().getLocation());
    }

    @EventHandler
    public void onEntityChangeBlock(EntityChangeBlockEvent event) {
        if (!event.getBlock().getType().isAir()) return;
        if (event.getEntityType() != EntityType.FALLING_BLOCK) return;
        if (Region.getRegion(event.getBlock().getLocation()) != region) return;
        Location origin = fallingBlocks.remove(event.getEntity());
        if (origin == null) return;
        Location destination = event.getBlock().getLocation();
        Vector originOrigin = shieldMap.remove(origin.toVector());
        shieldMap.put(destination.toVector(), originOrigin != null ? originOrigin : origin.toVector());

        updateBossbars();
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        updateBossbar(event.getPlayer());
    }

    @EventHandler
    public void onBauMemberUpdate(BauMemberUpdateEvent event) {
        event.getNewSpectator().forEach(player -> BossBarService.instance.remove(player, region, "shieldprinting"));
        event.getNewBuilder().forEach(this::updateBossbar);
    }

    private void updateBossbars() {
        for (Player player : Bukkit.getOnlinePlayers()) {
            updateBossbar(player);
        }
    }

    private void updateBossbar(Player player) {
        BauSystemBossbar bossbar = BossBarService.instance.get(player, region, "shieldprinting");
        bossbar.setColor(BarColor.YELLOW);
        bossbar.setTitle(BauSystem.MESSAGE.parse(shieldData.isEmpty() ? "SHIELD_PRINTING_BOSSBAR" : "SHIELD_PRINTING_BOSSBAR_COPIED", player, shieldMap.size(), shieldData.size()));
    }
}
