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

import de.steamwar.core.Core;
import de.steamwar.core.VersionDependent;
import io.netty.buffer.ByteBuf;
import lombok.Getter;
import org.bukkit.entity.Player;

import java.util.Collections;
import java.util.Set;
import java.util.function.BiFunction;

public interface ChunkHider {
    ChunkHider impl = VersionDependent.getVersionImpl(Core.getInstance());

    Class<?> mapChunkPacket();
    BiFunction<Player, Object, Object> chunkHiderGenerator(TechHider techHider);

    @Getter
    class SectionHider {
        private final Player player;
        private final TechHider techHider;
        private final ByteBuf in;
        private final ByteBuf out;

        private final int chunkX;
        private final int chunkY;
        private final int chunkZ;
        private final int offsetX;
        private final int offsetY;
        private final int offsetZ;

        private final boolean skipSection;

        private boolean paletted;
        private int bitsPerBlock;
        private int air;
        private int target;
        private Set<Integer> obfuscate;

        public SectionHider(Player player, TechHider techHider, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) {
            this.player = player;
            this.techHider = techHider;
            this.in = in;
            this.out = out;
            this.chunkX = chunkX;
            this.chunkY = chunkY;
            this.chunkZ = chunkZ;
            this.offsetX = 16*chunkX;
            this.offsetY = 16*chunkY;
            this.offsetZ = 16*chunkZ;
            this.skipSection = techHider.getLocationEvaluator().skipChunkSection(player, chunkX, chunkY, chunkZ);

            this.paletted = false;
            this.bitsPerBlock = 0;
            this.air = TechHider.AIR_ID;
            this.target = techHider.getObfuscationTargetId();
            this.obfuscate = techHider.getObfuscateIds();
        }

        public boolean blockPrecise() {
            return techHider.getLocationEvaluator().blockPrecise(player, chunkX, chunkY, chunkZ);
        }

        public TechHider.State test(int x, int y, int z) {
            return techHider.getLocationEvaluator().check(player, offsetX+x, offsetY+y, offsetZ+z);
        }

        public void copyBlockCount() {
            out.writeShort(in.readShort());
        }

        public void copyBitsPerBlock() {
            bitsPerBlock = in.readByte();
            out.writeByte(bitsPerBlock);
        }

        public int copyVarInt() {
            int value = ProtocolUtils.readVarInt(in);
            ProtocolUtils.writeVarInt(out, value);
            return value;
        }

        public void skipPalette() {
            int paletteLength = copyVarInt();
            for(int i = 0; i < paletteLength; i++)
                copyVarInt();
        }

        public void processPalette() {
            if(skipSection) {
                skipPalette();
                return;
            }

            int paletteLength = copyVarInt();
            if(paletteLength == 0)
                return;

            paletted = true;
            air = 0;
            target = 0;

            for(int i = 0; i < paletteLength; i++) {
                int entry = ProtocolUtils.readVarInt(in);
                if(obfuscate.contains(entry))
                    entry = techHider.getObfuscationTargetId();

                if(entry == TechHider.AIR_ID)
                    air = i;
                else if(entry == techHider.getObfuscationTargetId())
                    target = i;

                ProtocolUtils.writeVarInt(out, entry);
            }
            obfuscate = Collections.emptySet();
        }

        public void skipDataArray() {
            int dataArrayLength = copyVarInt();
            out.writeBytes(in, dataArrayLength*8);
        }

        public long[] readDataArray() {
            long[] array = new long[copyVarInt()];
            for(int i = 0; i < array.length; i++)
                array[i] = in.readLong();

            return array;
        }

        public void writeDataArray(long[] array) {
            for(long l : array)
                out.writeLong(l);
        }
    }
}
