/*
 *   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.rayvisualizer;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.command.SWCommand;
import de.steamwar.entity.CRay;
import de.steamwar.entity.REntityServer;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.*;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.util.RayTraceResult;

import java.util.List;

@Linked
@MinVersion(20)
public class RayVisualizerCommand extends SWCommand implements Listener {

    private BlockData VISIBLE = Material.LIME_CONCRETE.createBlockData();
    private BlockData BLOCKED = Material.RED_CONCRETE.createBlockData();

    private class CRayData {
        private CRay[] rays = new CRay[27 * 400];
        private boolean[] used = new boolean[27 * 400];

        public void reset() {
            for (int i = 0; i < 27 * 400; i++) {
                if (!used[i] && rays[i] != null) {
                    rays[i].die();
                    rays[i] = null;
                }
                used[i] = false;
            }
        }

        private boolean locEquals(Location loc1, Location loc2) {
            if ((long)(loc1.getX() * 1000) != (long)(loc2.getX() * 1000)) return false;
            if ((long)(loc1.getY() * 1000) != (long)(loc2.getY() * 1000)) return false;
            if ((long)(loc1.getZ() * 1000) != (long)(loc2.getZ() * 1000)) return false;
            return true;
        }

        public CRay get(Location from, Location to) {
            for (int i = 0; i < rays.length; i++) {
                if (rays[i] != null && locEquals(from, rays[i].getFrom()) && locEquals(to, rays[i].getTo())) {
                    used[i] = true;
                    return rays[i];
                }
            }
            for (int i = 0; i < rays.length; i++) {
                if (rays[i] != null && locEquals(from, rays[i].getFrom()) && !used[i]) {
                    used[i] = true;
                    return rays[i];
                }
            }
            for (int i = 0; i < rays.length; i++) {
                if (used[i]) continue;
                CRay ray = rays[i];
                if (ray != null) {
                    return ray;
                }
                ray = new CRay(server);
                ray.setBlock(Material.LIME_CONCRETE.createBlockData());
                ray.setWidth(1 / 32f);
                rays[i] = ray;
                used[i] = true;
                return ray;
            }
            return null;
        }
    }

    private CRayData rayData = new CRayData();

    private final REntityServer server = new REntityServer();
    private final World WORLD = Bukkit.getWorlds().get(0);

    public RayVisualizerCommand() {
        super("rayvisualizer");

        Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
            if (server.getPlayers().isEmpty()) return;
            List<TNTPrimed> primedList = WORLD.getEntitiesByClass(TNTPrimed.class)
                    .stream().toList();
            rayData.reset();
            if (primedList.isEmpty()) return;
            calculateRays(primedList, primedList);
        }, 1, 1);
    }

    private void calculateRays(List<TNTPrimed> fromTNTs, List<TNTPrimed> toTNTs) {
        for (TNTPrimed to : toTNTs) {
            int hitting = 0;
            int total = 0;
            for (TNTPrimed from : fromTNTs) {
                if (from == to) continue;
                if (from.getFuseTicks() >= to.getFuseTicks()) continue;
                if (to.getLocation().distanceSquared(from.getLocation()) > 25) continue;
                if (!from.isInWater()) continue;

                Location fromLoc = from.getLocation();
                Location toLoc = to.getLocation().clone().add(-0.49, 0, -0.49);

                final double minX = 0.5 * (1 - Math.floor(2 * 0.98 + 1) / (2 * 0.98 + 1));
                final double minZ = 0.5 * (1 - Math.floor(2 * 0.98 + 1) / (2 * 0.98 + 1));
                final double spacing = 0.98 / (2 * 0.98 + 1);

                for (int dx = 0; dx < 3; dx++) {
                    for (int dy = 0; dy < 3; dy++) {
                        for (int dz = 0; dz < 3; dz++) {
                            Location end = toLoc.clone().add(minX + dx * spacing, 0 + dy * spacing, minZ + dz * spacing);
                            RayTraceResult result = fromLoc.getWorld().rayTraceBlocks(fromLoc, end.clone().subtract(fromLoc).toVector(), end.distance(fromLoc), FluidCollisionMode.NEVER, true);
                            CRay cRay = rayData.get(fromLoc, end);
                            if (cRay == null) continue;
                            cRay.setFrom(fromLoc);
                            cRay.setTo(end);
                            if (result != null && result.getHitBlock() != null) {
                                cRay.setBlock(BLOCKED);
                            } else {
                                cRay.setBlock(VISIBLE);
                                hitting++;
                            }
                        }
                    }
                }
                total += 27;
            }

            if (total == 0) {
                to.setCustomNameVisible(false);
            } else {
                int remainder = hitting % 27;
                int whole = hitting / 27;
                to.setCustomNameVisible(true);
                to.setCustomName("§e" + whole + " " + remainder + "/27");
            }
        }
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
        boolean rayvisualizer = Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("rayvisualizer", false);
        if (rayvisualizer) server.addPlayer(event.getPlayer());
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        server.removePlayer(event.getPlayer());
    }

    @EventHandler
    public void onBauMemberUpdate(BauMemberUpdateEvent event) {
        Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
            for (Player player : Bukkit.getOnlinePlayers()) {
                if (Permission.BUILD.hasPermission(player)) {
                    boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false);
                    if (rayvisualizer) server.addPlayer(player);
                } else {
                    server.removePlayer(player);
                }
            }
        }, 1);
    }

    @Register
    public void toggle(@Validator Player player) {
        boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false);
        Config.getInstance().get(player).put("rayvisualizer", !rayvisualizer);
        if (!rayvisualizer) {
            SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_ENABLE", player));
            server.addPlayer(player);
        } else {
            SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_DISABLE", player));
            server.removePlayer(player);
        }
    }
}
