/*
    This file is a part of the SteamWar software.

    Copyright (C) 2020  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.commands;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.CraftbukkitWrapper;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.world.TPSUtils;
import de.steamwar.bausystem.world.Welt;
import de.steamwar.command.SWCommand;
import de.steamwar.command.SWCommandUtils;
import de.steamwar.command.TypeMapper;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CommandTPSLimiter extends SWCommand {

    private static CommandTPSLimiter instance = null;

    {
        instance = this;
    }

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

    private long lastTime = System.nanoTime();
    private long currentTime = System.nanoTime();

    private double delay = 0;
    private int loops = 0;
    private long sleepDelay = 0;

    private BukkitTask tpsLimiter = null;

    private List<String> tabCompletions = new ArrayList<>(Arrays.asList("0,5", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"));

    public CommandTPSLimiter() {
        super("tpslimit");
        if (TPSUtils.isWarpAllowed()) {
            for (int i = 20; i <= 60; i += 5) {
                tabCompletions.add(i + "");
            }
        }
    }

    @Register(help = true)
    public void genericHelp(Player p, String... args) {
        p.sendMessage(BauSystem.PREFIX + "Jetziges TPS limit: " + currentTPSLimit);
        p.sendMessage("§8/§etpslimit §8[§7TPS§8|§edefault§8] §8- §7Setzte die TPS auf dem Bau");
    }

    @Register({"default"})
    public void defaultCommand(Player p) {
        if (!permissionCheck(p)) return;
        currentTPSLimit = 20;
        sendNewTPSLimitMessage();
        tpsLimiter();
    }

    @Register
    public void valueCommand(Player p, double tpsLimitDouble) {
        if (!permissionCheck(p)) return;
        if (tpsLimitDouble < 0.5 || tpsLimitDouble > (TPSUtils.isWarpAllowed() ? 60 : 20)) {
            sendInvalidArgumentMessage(p);
            return;
        }
        currentTPSLimit = tpsLimitDouble;
        sendNewTPSLimitMessage();
        tpsLimiter();
    }

    @ClassMapper(value = double.class, local = true)
    public TypeMapper<Double> doubleTypeMapper() {
        return SWCommandUtils.createMapper(s -> {
            try {
                return Double.parseDouble(s.replace(',', '.'));
            } catch (NumberFormatException e) {
                return 0D;
            }
        }, s -> tabCompletions);
    }

    private boolean permissionCheck(Player player) {
        if (Welt.noPermission(player, Permission.WORLD)) {
            player.sendMessage(BauSystem.PREFIX + "§cDu darfst hier nicht den TPS-Limiter nutzen");
            return false;
        }
        return true;
    }

    private void sendNewTPSLimitMessage() {
        Bukkit.getOnlinePlayers().forEach(p -> p.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText("§eTPS limit auf " + currentTPSLimit + " gesetzt.")));
    }

    private void sendInvalidArgumentMessage(Player player) {
        player.sendMessage(BauSystem.PREFIX + "§cNur Zahlen zwischen 0,5 und " + (TPSUtils.isWarpAllowed() ? 60 : 20) + ", und 'default' erlaubt.");
    }

    private void tpsLimiter() {
        delay = 20 / currentTPSLimit;
        loops = (int) Math.ceil(delay);
        sleepDelay = (long) (50 * delay) / loops;

        TPSUtils.setTPS(currentTPSLimit);
        if (currentTPSLimit >= 20) {
            if (tpsLimiter == null) return;
            tpsLimiter.cancel();
            tpsLimiter = null;
        } else {
            if (tpsLimiter != null) return;
            tpsLimiter = Bukkit.getScheduler().runTaskTimer(BauSystem.getPlugin(), () -> {
                CraftbukkitWrapper.impl.createTickCache(WORLD);

                for (int i = 0; i < loops; i++) {
                    sleepUntilNextTick(sleepDelay);
                    CraftbukkitWrapper.impl.sendTickPackets();
                }
            }, 0, 1);
        }
    }

    private void sleepUntilNextTick(long neededDelta) {
        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();
        }
    }

    public static double getCurrentTPSLimit() {
        return (double) Math.round(currentTPSLimit * 10.0D) / 10.0D;
    }

    public static void setTPS(double d) {
        if (d < 0.5) d = 0.5;
        if (d > (TPSUtils.isWarpAllowed() ? 60 : 20)) d = (TPSUtils.isWarpAllowed() ? 60 : 20);
        if (instance != null) {
            currentTPSLimit = d;
            instance.tpsLimiter();
        }
    }

}
