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

import com.comphenix.tinyprotocol.Reflection;
import com.comphenix.tinyprotocol.TinyProtocol;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.ViaAPI;
import de.steamwar.core.Core;
import de.steamwar.core.CraftbukkitWrapper;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;

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

/**
 * TinyProtocol can't translate BlockEntities during 1.16 to 1.17 conversions du to removed partial chunk update support. This class cancels PartialChunkUpdates for this players and sends them a complete chunk instead.
 * This class can only be loaded on 1.9 to 1.15 with active ViaVersion.
 **/
public class PartialChunkFixer {

    private static final int PROTOCOL1_17 = 755;
    private static final Class<?> mapChunk = Reflection.getClass("{nms}.PacketPlayOutMapChunk");
    private static final Reflection.FieldAccessor<Boolean> fullChunkFlag = Reflection.getField(mapChunk, boolean.class, 0);
    private static final Reflection.FieldAccessor<Integer> chunkX = Reflection.getField(mapChunk, int.class, 0);
    private static final Reflection.FieldAccessor<Integer> chunkZ = Reflection.getField(mapChunk, int.class, 1);

    private final ViaAPI<Player> via = Via.getAPI();

    private final List<ResendChunk> chunksToResend = new ArrayList<>();

    public PartialChunkFixer() {
        TinyProtocol.instance.addFilter(mapChunk, this::chunkFilter);
        Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> {
            synchronized (chunksToResend) {
                for(ResendChunk chunk : chunksToResend) {
                    CraftbukkitWrapper.impl.sendChunk(chunk.player, chunk.x, chunk.z);
                }
                chunksToResend.clear();
            }
        }, 1, 1);
    }

    private Object chunkFilter(Player player, Object packet) {
        if(via.getPlayerVersion(player) >= PROTOCOL1_17 && !fullChunkFlag.get(packet)) {
            // partial chunk update
            synchronized (chunksToResend) {
                chunksToResend.add(new ResendChunk(player, chunkX.get(packet), chunkZ.get(packet)));
            }
            return null;
        }
        return packet;
    }

    private static class ResendChunk {
        private final Player player;
        private final int x;
        private final int z;

        private ResendChunk(Player player, int x, int z) {
            this.player = player;
            this.x = x;
            this.z = z;
        }
    }
}
