/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 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 com.google.common.primitives.Bytes;
import de.steamwar.Reflection;
import io.netty.buffer.ByteBuf;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;

public class ProtocolUtils {
    private ProtocolUtils() {
    }

    @Deprecated
    public static BiFunction<Object, UnaryOperator<Object>, Object> arrayCloneGenerator(Class<?> elementClass) {
        return (array, worker) -> {
            int length = Array.getLength(array);
            Object result = Array.newInstance(elementClass, length);

            for (int i = 0; i < length; i++) {
                Array.set(result, i, worker.apply(Array.get(array, i)));
            }

            return result;
        };
    }

    public static UnaryOperator<Object> shallowCloneGenerator(Class<?> clazz) {
        BiConsumer<Object, Object> filler = shallowFill(clazz);

        return source -> {
            Object clone = Reflection.newInstance(clazz);
            filler.accept(source, clone);
            return clone;
        };
    }

    public static <T> UnaryOperator<T> shallowTypedCloneGenerator(Class<T> clazz) {
        BiConsumer<Object, Object> filler = shallowFill(clazz);

        return source -> {
            Object clone = Reflection.newInstance(clazz);
            filler.accept(source, clone);
            return (T) clone;
        };
    }

    private static BiConsumer<Object, Object> shallowFill(Class<?> clazz) {
        if (clazz == null) {
            return (source, clone) -> {
            };
        }

        BiConsumer<Object, Object> superFiller = shallowFill(clazz.getSuperclass());

        Field[] fds = clazz.getDeclaredFields();
        List<Field> fields = new ArrayList<>();
        for (Field field : fds) {
            if (Modifier.isStatic(field.getModifiers())) continue;

            field.setAccessible(true);
            fields.add(field);
        }

        return (source, clone) -> {
            superFiller.accept(source, clone);
            try {
                for (Field field : fields) {
                    field.set(clone, field.get(source));
                }
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Could not set field", e);
            }

        };
    }

    public static int posToChunk(int c) {
        int chunk = c / 16;
        if (c < 0) chunk--;
        return chunk;
    }

    @Deprecated
    public static int readVarInt(byte[] array, int startPos) {
        int numRead = 0;
        int result = 0;
        byte read;
        do {
            read = array[startPos + numRead];
            int value = (read & 0b01111111);
            result |= (value << (7 * numRead));

            numRead++;
            if (numRead > 5) {
                break;
            }
        } while ((read & 0b10000000) != 0);

        return result;
    }

    public static int readVarInt(ByteBuf buf) {
        int numRead = 0;
        int result = 0;
        byte read;
        do {
            read = buf.readByte();
            int value = (read & 0b01111111);
            result |= (value << (7 * numRead));

            if (++numRead > 5) throw new SecurityException("VarInt too long");
        } while ((read & 0b10000000) != 0);

        return result;
    }

    public static int[] readVarIntArray(ByteBuf buffer, int length) {
        int[] array = new int[length];

        for(int i = 0; i < length; i++) {
            array[i] = readVarInt(buffer);
        }

        return array;
    }

    public static void writeVarInt(ByteBuf buf, int value) {
        do {
            int temp = value & 0b01111111;
            // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
            value >>>= 7;
            if (value != 0) {
                temp |= 0b10000000;
            }
            buf.writeByte(temp);
        } while (value != 0);
    }

    public static void writeVarIntArray(ByteBuf buf, int[] values){
        for(int varInt : values) {
            writeVarInt(buf, varInt);
        }
    }

    @Deprecated
    public static int readVarIntLength(byte[] array, int startPos) {
        int numRead = 0;
        byte read;
        do {
            read = array[startPos + numRead];
            numRead++;
            if (numRead > 5) {
                break;
            }
        } while ((read & 0b10000000) != 0);

        return numRead;
    }

    @Deprecated
    public static byte[] writeVarInt(int value) {
        List<Byte> buffer = new ArrayList<>(5);
        do {
            byte temp = (byte) (value & 0b01111111);
            // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
            value >>>= 7;
            if (value != 0) {
                temp |= 0b10000000;
            }
            buffer.add(temp);
        } while (value != 0);
        return Bytes.toArray(buffer);
    }

    @Deprecated
    public static class ChunkPos {
        final int x;
        final int z;

        public ChunkPos(int x, int z) {
            this.x = x;
            this.z = z;
        }

        public final int x() {
            return x;
        }

        public final int z() {
            return z;
        }
    }
}
