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

import de.steamwar.Reflection;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.utils.PlayerMovementWrapper;
import de.steamwar.core.Core;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiFunction;

@UtilityClass
public class TPSLimitUtils {

    private static long currentTime = System.nanoTime();
    private static BukkitTask tpsLimiter = null;
    private static Queue<Runnable> packetQueue = new ConcurrentLinkedQueue<>();

    public void unlimit() {
        if (tpsLimiter != null) tpsLimiter.cancel();
        tpsLimiter = null;
    }

    public void limit(double tps) {
        if (tpsLimiter != null) tpsLimiter.cancel();

        double delay = 20 / tps;
        int loops = (int) Math.ceil(delay);
        long sleepDelay = (long) (50 * delay) / loops;

        tpsLimiter = Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> {
            PacketCache.sendCache();
            for (int i = 0; i < loops; i++) {
                sleepUntilNextTick(sleepDelay);
                PacketCache.sendCache();
                while (true) {
                    Runnable runnable = packetQueue.poll();
                    if (runnable == null) break;
                    runnable.run();
                }
            }
            PacketCache.clearCache();
        }, 0, 1);
    }

    private void sleepUntilNextTick(long neededDelta) {
        long lastTime = currentTime;
        currentTime = System.nanoTime();

        long timeDelta = (currentTime - lastTime) / 1000000;
        if (neededDelta - timeDelta < 0) return;

        try {
            Thread.sleep(neededDelta - timeDelta);
            currentTime = System.nanoTime();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /*
    static {
        long timeInterval = 50;
        final long[] lastTime = {System.currentTimeMillis()};
        final double[] tps = {20.0};
        Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> {
            long currentTime = System.currentTimeMillis();
            if (currentTime > lastTime[0]) {
                tps[0] = (double)timeInterval / (double)(currentTime - lastTime[0]) * 20.0;
            }

            lastTime[0] = currentTime;

            Bukkit.getOnlinePlayers().forEach(player -> {
                SWUtils.sendToActionbar(player, String.valueOf((int) (tps[0] * 10.0) / 10.0));
            });
        }, timeInterval / 50L, timeInterval / 50L);
    }
     */

    private static final Class<?> position = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos");
    private static final Class<?> positionLook = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot");
    static {
        BiFunction<Player, Object, Object> positionSetter = (player, o) -> {
            if (tpsLimiter != null) {
                Object object = PlayerMovementWrapper.impl.convertToOut(player, o);
                packetQueue.add(() -> {
                    PlayerMovementWrapper.impl.setPosition(player, o);
                    Bukkit.getOnlinePlayers().forEach(p -> {
                        if (p == player) return;
                        TinyProtocol.instance.sendPacket(p, object);
                    });
                });
                return null;
            }
            return o;
        };
        TinyProtocol.instance.addFilter(position, positionSetter);
        TinyProtocol.instance.addFilter(positionLook, positionSetter);
    }
}
