/*
    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 net.minecraft.core.SectionPosition;
import net.minecraft.network.protocol.game.PacketPlayOutBlockBreak;
import net.minecraft.world.level.block.entity.TileEntitySign;
import net.minecraft.world.level.block.entity.TileEntityTypes;
import net.minecraft.world.level.block.state.IBlockData;
import org.bukkit.entity.Player;

import java.util.ArrayList;
import java.util.function.BiFunction;

public class ProtocolWrapper18 implements ProtocolWrapper {

    private static final Reflection.Field<SectionPosition> multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPosition.class, 0);
    private static final Reflection.Field<short[]> multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0);
    private static final Reflection.Field<IBlockData[]> multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, IBlockData[].class, 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 = TechHider.blockPositionX.get(chunkCoords);
            int chunkY = TechHider.blockPositionY.get(chunkCoords);
            int chunkZ = TechHider.blockPositionZ.get(chunkCoords);
            if(locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ))
                return packet;

            packet = TechHider.multiBlockChangeCloner.apply(packet);
            final short[] oldPos = multiBlockChangePos.get(packet);
            final IBlockData[] oldBlocks = multiBlockChangeBlocks.get(packet);
            ArrayList<Short> poss = new ArrayList<>(oldPos.length);
            ArrayList<IBlockData> blocks = new ArrayList<>(oldPos.length);
            for(int i = 0; i < oldPos.length; i++) {
                short pos = oldPos[i];
                IBlockData block = oldBlocks[i];
                switch(locationEvaluator.check(p, 16*chunkX + (pos >> 8 & 0xF), 16*chunkY + (pos & 0xF), 16*chunkZ + (pos >> 4 & 0xF))) {
                    case SKIP:
                        poss.add(pos);
                        blocks.add(block);
                        break;
                    case CHECK:
                        poss.add(pos);
                        blocks.add(techHider.iBlockDataHidden(block) ? (IBlockData) techHider.getObfuscationTarget() : block);
                        break;
                    default:
                        break;
                }
            }

            if(blocks.isEmpty())
                return null;

            short[] newPos = new short[poss.size()];
            for(int i = 0; i < newPos.length; i++)
                newPos[i] = poss.get(i);

            multiBlockChangePos.set(packet, newPos);
            multiBlockChangeBlocks.set(packet, blocks.toArray(new IBlockData[0]));
            return packet;
        };
    }

    private static final Reflection.Field<TileEntityTypes> tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, TileEntityTypes.class, 0);
    private static final TileEntityTypes<?> signType = Reflection.getField(TileEntityTypes.class, TileEntityTypes.class, 0, TileEntitySign.class).get(null);
    @Override
    public boolean unfilteredTileEntityDataAction(Object packet) {
        return tileEntityType.get(packet) != signType;
    }

    @Override
    public BiFunction<Player, Object, Object> blockBreakHiderGenerator(Class<?> blockBreakPacket, TechHider techHider) {
        return (p, packet) -> {
            PacketPlayOutBlockBreak breakPacket = (PacketPlayOutBlockBreak) packet;
            switch (techHider.getLocationEvaluator().checkBlockPos(p, breakPacket.b())) {
                case SKIP:
                    return packet;
                case CHECK:
                    if(!techHider.iBlockDataHidden(breakPacket.c()))
                        return packet;
                case HIDE:
                    return new PacketPlayOutBlockBreak(breakPacket.b(), (IBlockData) techHider.getObfuscationTarget(), breakPacket.d(), breakPacket.a());
                case HIDE_AIR:
                default:
                    return new PacketPlayOutBlockBreak(breakPacket.b(), (IBlockData) TechHider.AIR, breakPacket.d(), breakPacket.a());
            }
        };
    }
}
