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

    Copyright (C) 2021  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.techhider;

import de.steamwar.Reflection;
import org.bukkit.entity.Player;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.function.BiFunction;

public class ProtocolWrapper8 implements ProtocolWrapper {
    private static final Class<?> chunkCoordinateIntPair = Reflection.getClass("net.minecraft.ChunkCoordIntPair");
    private static final Reflection.Field<?> multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, chunkCoordinateIntPair, 0);
    private static final Reflection.Field<Integer> chunkCoordinateX = Reflection.getField(chunkCoordinateIntPair, int.class, 0);
    private static final Reflection.Field<Integer> chunkCoordinateZ = Reflection.getField(chunkCoordinateIntPair, int.class, 1);
    private static final Class<?> multiBlockChangeInfo = Reflection.getClass("net.minecraft.PacketPlayOutMultiBlockChange$MultiBlockChangeInfo");
    private static final Reflection.Constructor multiBlockChangeInfoConstructor = Reflection.getConstructor(multiBlockChangeInfo, TechHider.multiBlockChangePacket, short.class, TechHider.iBlockData);
    private static final Reflection.Field<?> multiBlockChangeInfoBlock = Reflection.getField(multiBlockChangeInfo, TechHider.iBlockData, 0);
    private static final Reflection.Field<?> multiBlockChangeInfoPos = Reflection.getField(multiBlockChangeInfo, short.class, 0);
    private static final Class<?> multiBlockChangeInfoArray = Reflection.getClass("[L" + Reflection.LEGACY_NET_MINECRAFT_SERVER + ".PacketPlayOutMultiBlockChange$MultiBlockChangeInfo;");
    private static final Reflection.Field<?> multiBlockChangeInfos = Reflection.getField(TechHider.multiBlockChangePacket, multiBlockChangeInfoArray, 0);
    @Override
    public BiFunction<Player, Object, Object> multiBlockChangeGenerator(TechHider techHider) {
        return (p, packet) -> {
            TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator();
            Object chunkCoords = multiBlockChangeChunk.get(packet);
            int chunkX = chunkCoordinateX.get(chunkCoords);
            int chunkZ = chunkCoordinateZ.get(chunkCoords);
            if(locationEvaluator.skipChunk(p, chunkX, chunkZ))
                return packet;

            packet = TechHider.multiBlockChangeCloner.apply(packet);
            Object[] mbcis = (Object[]) multiBlockChangeInfos.get(packet);
            ArrayList<Object> blockChangeInfos = new ArrayList<>(mbcis.length);
            for(Object mbci : mbcis) {
                short pos = (short) multiBlockChangeInfoPos.get(mbci);
                switch(locationEvaluator.check(p, 16*chunkX + (pos >> 12 & 0xF), pos & 0xFF, 16*chunkZ + (pos >> 8 & 0xF))) {
                    case SKIP:
                        blockChangeInfos.add(mbci);
                        break;
                    case CHECK:
                        blockChangeInfos.add(techHider.iBlockDataHidden(multiBlockChangeInfoBlock.get(mbci)) ? multiBlockChangeInfoConstructor.invoke(packet, pos, techHider.getObfuscationTarget()) : mbci);
                        break;
                    default:
                        break;
                }
            }

            if(blockChangeInfos.isEmpty())
                return null;

            multiBlockChangeInfos.set(packet, blockChangeInfos.toArray((Object[])Array.newInstance(multiBlockChangeInfo, 0)));
            return packet;
        };
    }

    private static final Reflection.Field<Integer> tileEntityDataAction = Reflection.getField(TechHider.tileEntityDataPacket, int.class, 0);
    @Override
    public boolean unfilteredTileEntityDataAction(Object packet) {
        return tileEntityDataAction.get(packet) != 9;
    }

    @Override
    public BiFunction<Player, Object, Object> blockBreakHiderGenerator(Class<?> blockBreakPacket, TechHider techHider) {
        return null;
    }
}
