/*
 * This file is a part of the SteamWar software.
 *
 * Copyright (C) 2022  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.util;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.linkage.Linked;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.TileState;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Piston;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;

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

@Linked
public class PistonCalculator implements Listener {

    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
        if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
        if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
        if (!event.hasItem() || event.getItem().getType() != Material.SLIME_BALL) return;
        if (event.getClickedBlock() == null) return;
        Block clickedBlock = event.getClickedBlock();
        Material blockType = clickedBlock.getType();
        if (!(blockType == Material.PISTON || blockType == Material.STICKY_PISTON)) return;
        Piston piston = (Piston) clickedBlock.getBlockData();

        if (blockType == Material.PISTON && piston.isExtended()) {
            BauSystem.MESSAGE.sendPrefixless("PISTON_INFO", event.getPlayer(), ChatMessageType.ACTION_BAR, "§a", 0);
            return;
        }

        boolean pulling = blockType == Material.STICKY_PISTON && (clickedBlock.getRelative(piston.getFacing()).getType() == Material.AIR || piston.isExtended());

        CalculationResult result = calc(clickedBlock, piston.getFacing(), (pulling ? piston.getFacing().getOppositeFace() : piston.getFacing()));
        result.entityServer.addPlayer(event.getPlayer());
        BauSystem.MESSAGE.sendPrefixless("PISTON_INFO", event.getPlayer(), ChatMessageType.ACTION_BAR, result.unmovable ? "§c" : (result.tooMany ? "§e" : "§a"), result.amount);
    }

    private final BlockFace[] FACES = new BlockFace[]{BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST};

    private CalculationResult calc(Block origin, BlockFace facing, BlockFace direction) {
        Set<Block> blockSet = new HashSet<>();
        Set<Location> unmovable = new HashSet<>();

        Block calcOrigin = origin;
        if (facing != direction) calcOrigin = origin.getRelative(facing, 3);

        List<Block> toCalc = new LinkedList<>();
        calcDirection(origin, null, calcOrigin, facing != direction ? origin.getRelative(facing) : null, facing, direction, blockSet, toCalc, unmovable);

        while (!toCalc.isEmpty()) {
            Block current = toCalc.remove(0);
            blockSet.add(current);

            Material type = current.getType();
            if (type != Material.SLIME_BLOCK && type != Material.HONEY_BLOCK) continue;
            Material oppositeType = type == Material.SLIME_BLOCK ? Material.HONEY_BLOCK : Material.SLIME_BLOCK;

            for (BlockFace face : FACES) {
                Block block = current.getRelative(face);
                if (block.getType().isAir()) continue;
                if (!isPiston(block) && (block.getPistonMoveReaction() == PistonMoveReaction.BLOCK || block.getPistonMoveReaction() == PistonMoveReaction.IGNORE || block.getPistonMoveReaction() == PistonMoveReaction.PUSH_ONLY || block.getState() instanceof TileState || block.getPistonMoveReaction() == PistonMoveReaction.BREAK)) continue;
                if (block.getType() != oppositeType) {
                    if (!blockSet.contains(block)) toCalc.add(block);
                    calcDirection(null, origin, block, null, facing, direction, blockSet, toCalc, unmovable);
                }
            }
        }

        blockSet.remove(origin);
        if (facing != direction) blockSet.remove(origin.getRelative(facing, 1));

        REntityServer entityServer = new REntityServer();
        for (Location loc : unmovable) {
            RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(entityServer, loc.clone().add(0.5, 0, 0.5), Material.RED_STAINED_GLASS);
            rFallingBlockEntity.setGlowing(true);
            rFallingBlockEntity.setNoGravity(true);
            rFallingBlockEntity.setInvisible(true);
        }
        Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), entityServer::close, 20);
        return new CalculationResult(blockSet.size(), blockSet.size() > 12, !unmovable.isEmpty(), entityServer);
    }

    private void calcDirection(Block origin, Block blockOrigin, Block calcOrigin, Block ignore, BlockFace facing, BlockFace direction, Set<Block> blockSet, List<Block> toCalc, Set<Location> unmovable) {
        for (int i = 1; i < 24; i++) {
            Block block = calcOrigin.getRelative(direction, i);
            if (block.equals(ignore)) return;
            if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) return;
            if (!isPiston(block) && (block.getPistonMoveReaction() == PistonMoveReaction.BLOCK || block.getPistonMoveReaction() == PistonMoveReaction.IGNORE || block.getState() instanceof TileState)) {
                unmovable.add(block.getLocation());
                return;
            }
            if (block.getType().isAir()) return;
            if (facing == direction && block.equals(blockOrigin)) {
                unmovable.add(block.getLocation());
                return;
            }
            if (facing != direction && (block.equals(origin) || block.getRelative(facing.getOppositeFace()).equals(origin))) return;
            if (!blockSet.contains(block)) toCalc.add(block);
        }
    }

    private boolean isPiston(Block block) {
        BlockData blockData = block.getBlockData();
        if (blockData instanceof Piston) {
            return !((Piston) blockData).isExtended();
        }
        return false;
    }

    @AllArgsConstructor
    @Getter
    private static class CalculationResult {
        private int amount;
        private boolean tooMany;
        private boolean unmovable;
        private REntityServer entityServer;
    }
}
