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

import de.steamwar.Reflection;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.utils.PlaceItemUtils;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.Rotatable;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.util.Vector;

@Linked
@MinVersion(20)
public class SignEditFrom20 implements Listener {

    private static final Class<?> blockPosition = Reflection.getClass("net.minecraft.core.BlockPos");
    private static final Class<?> craftBlock = Reflection.getClass("org.bukkit.craftbukkit.block.CraftBlock");
    private static final Class<?> craftWorld = Reflection.getClass("org.bukkit.craftbukkit.CraftWorld");
    private static final Class<?> generatorAccess = Reflection.getClass("net.minecraft.world.level.LevelAccessor");
    private static final Reflection.Method getPosition = Reflection.getTypedMethod(craftBlock, "getPosition", blockPosition);
    private static final Reflection.Method getWorldHandle = Reflection.getTypedMethod(craftWorld, "getHandle", null);
    private static final Reflection.Method at = Reflection.getTypedMethod(craftBlock, "at", craftBlock, generatorAccess, blockPosition);

    private static final Class<?> openSign = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket");
    private static final Reflection.Field<?> blockPositionFieldAccessor = Reflection.getField(openSign, blockPosition, 0);
    private static final Reflection.Field<?> sideFieldAccessor = Reflection.getField(openSign, boolean.class, 0);

    private static final Class<?> updateSign = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundSignUpdatePacket");
    private static final Reflection.Field<?> getBlockPositionFieldAccessor = Reflection.getField(updateSign, blockPosition, 0);
    private static final Reflection.Field<String[]> stringFieldAccessor = Reflection.getField(updateSign, String[].class, 0);

    @EventHandler
    public void editSign(PlayerInteractEvent event) {
        if (event.getClickedBlock() == null || !event.getClickedBlock().getType().name().contains("SIGN")) return;
        if (event.getAction() == Action.RIGHT_CLICK_BLOCK && !event.getPlayer().isSneaking()) {
            PlaceItemUtils.placeItem(event.getPlayer(), event.getItem(), event.getClickedBlock(), event.getBlockFace(), event.getHand(), false, true, false, true);
            event.setCancelled(true);
        }
        if (!event.getPlayer().isSneaking()) return;

        if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (event.getItem() == null || event.getItem().getType() == Material.AIR) || event.getAction() == Action.LEFT_CLICK_BLOCK) {
            event.setCancelled(true);
            Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
                edit(event.getPlayer(), event.getClickedBlock());
            }, 1);
        }
    }

    private void edit(Player player, Block block) {
        if (!Permission.BUILD.hasPermission(player)) return;
        Sign sign = (Sign) block.getState();
        Side side = signSide(player, block);
        SignSide signSide = sign.getSide(side);
        String[] lines = signSide.getLines();
        for (int i = 0; i < lines.length; i++) {
            signSide.setLine(i, lines[i].replace('§', '&'));
        }
        sign.update(true);

        Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
            Object openSignObject = Reflection.newInstance(openSign);
            blockPositionFieldAccessor.set(openSignObject, getPosition.invoke(block));
            sideFieldAccessor.set(openSignObject, side == Side.FRONT);
            TinyProtocol.instance.sendPacket(player, openSignObject);
        }, 1);

    }

    private Side signSide(Player entity, Block sign) {
        Vector vector = entity.getEyeLocation().toVector().subtract(sign.getLocation().add(0.5, 0.5, 0.5).toVector());
        BlockData blockData = sign.getBlockData();

        BlockFace blockFace = BlockFace.NORTH;
        if (blockData instanceof Directional) {
            blockFace = ((Directional) blockData).getFacing();
        } else if (blockData instanceof Rotatable) {
            blockFace = ((Rotatable) blockData).getRotation();
        }

        Vector signDirection = new Vector(blockFace.getModX(), blockFace.getModY(), blockFace.getModZ());
        return vector.dot(signDirection) > 0 ? Side.FRONT : Side.BACK;
    }

    {
        TinyProtocol.instance.addFilter(updateSign, (player, o) -> {
            Bukkit.getScheduler().runTask(BauSystem.getInstance(), () -> {
                String[] lines = stringFieldAccessor.get(o);

                Block signLoc = (Block) at.invoke(null, getWorldHandle.invoke(player.getWorld()), getBlockPositionFieldAccessor.get(o));
                if (!signLoc.getType().name().contains("SIGN"))
                    return;

                Sign sign = ((Sign) signLoc.getState());
                SignSide signSide = sign.getSide(signSide(player, signLoc));
                for (int i = 0; i < lines.length; i++) {
                    signSide.setLine(i, ChatColor.translateAlternateColorCodes('&', lines[i]));
                }
                sign.update(true);
            });
            return o;
        });
    }
}
