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

import de.steamwar.core.BountifulWrapper;
import de.steamwar.core.Core;
import lombok.Getter;
import lombok.NonNull;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.entity.Display;
import org.bukkit.entity.EntityType;
import org.bukkit.util.Transformation;
import org.joml.Quaternionf;
import org.joml.Vector3f;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * !! This class cannot be used in Versions lower than or equal to 1.19.4 !!
 */
@Getter
public abstract class RDisplay extends REntity {

    protected final Consumer<Object> updatePacketSink = o -> server.updateEntity(this, o);

    public static final Transformation DEFAULT_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(0, 0, 0, 1), new Vector3f(1, 1, 1), new Quaternionf(0, 0, 0, 1));

    private Transformation transform;
    private int interpolationDuration;
    private float viewRange;
    private float shadowRadius;
    private float shadowStrength;
    private float displayWidth;
    private float displayHeight;
    private int interpolationDelay;
    private Display.Billboard billboard;
    private Color glowColorOverride;
    private Display.Brightness brightness;

    protected RDisplay(REntityServer server, EntityType entityType, Location location) {
        super(server, entityType, location, 0);
        this.transform = DEFAULT_TRANSFORM;
        this.interpolationDuration = 0;
        this.viewRange = 1.0F;
        this.shadowRadius = 0.0F;
        this.shadowStrength = 1.0F;
        this.displayWidth = 0.0F;
        this.displayHeight = 0.0F;
        this.interpolationDelay = 0;
        this.billboard = Display.Billboard.FIXED;
        this.glowColorOverride = null;
        this.brightness = null;
    }

    @Override
    protected void postSpawn(Consumer<Object> packetSink) {
        super.postSpawn(packetSink);
        sendPacket(packetSink,
                this::getTransformData,
                this::getInterpolationDuration,
                this::getViewRange,
                this::getShadowRadius,
                this::getShadowStrength,
                this::getDisplayWidth,
                this::getDisplayHeight,
                this::getInterpolationDelay,
                this::getBillboard,
                this::getGlowColorOverride,
                this::getBrightness
        );
    }

    @SafeVarargs
    protected final void sendPacket(Consumer<Object> packetSink, BiConsumer<Boolean, BiConsumer<Object, Object>>... dataSinkSinks) {
        List<Object> keyValueData = new ArrayList<>();
        boolean ignoreDefault = packetSink == updatePacketSink;
        for (BiConsumer<Boolean, BiConsumer<Object, Object>> dataSinkSink : dataSinkSinks) {
            dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> {
                keyValueData.add(dataWatcher);
                keyValueData.add(value);
            });
        }
        if (!keyValueData.isEmpty()) {
            packetSink.accept(getDataWatcherPacket(keyValueData.toArray()));
        }
    }

    public void setTransform(@NonNull Transformation transform) {
        this.transform = transform;
        sendPacket(updatePacketSink, this::getTransformData);
    }

    private static final Object translationWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 11 : 10, Vector3f.class);
    private static final Object leftRotationWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 13 : 12, Quaternionf.class);
    private static final Object scaleWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 12 : 11, Vector3f.class);
    private static final Object rightRotationWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 14 : 13, Quaternionf.class);

    private void getTransformData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
        if (ignoreDefault || !transform.equals(DEFAULT_TRANSFORM)) {
            dataSink.accept(translationWatcher, transform.getTranslation());
            dataSink.accept(leftRotationWatcher, transform.getLeftRotation());
            dataSink.accept(scaleWatcher, transform.getScale());
            dataSink.accept(rightRotationWatcher, transform.getRightRotation());
        }
    }

    public void setInterpolationDuration(int interpolationDuration) {
        this.interpolationDuration = interpolationDuration;
        sendPacket(updatePacketSink, this::getInterpolationDuration);
    }

    private static final Object transformationInterpolationDurationWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 9 : 8, Integer.class);
    private static final Object positionOrRotationInterpolationDurationWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 10 : 9, Integer.class);

    private void getInterpolationDuration(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || interpolationDelay != 0) {
            packetSink.accept(transformationInterpolationDurationWatcher, interpolationDuration);
            packetSink.accept(positionOrRotationInterpolationDurationWatcher, interpolationDuration);
        }
    }

    public void setViewRange(float viewRange) {
        this.viewRange = viewRange;
        sendPacket(updatePacketSink, this::getViewRange);
    }

    private static final Object viewRangeWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 17 : 16, Float.class);

    private void getViewRange(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || viewRange != 1.0F) {
            packetSink.accept(viewRangeWatcher, viewRange);
        }
    }

    public void setShadowRadius(float shadowRadius) {
        this.shadowRadius = shadowRadius;
        sendPacket(updatePacketSink, this::getShadowRadius);
    }

    private static final Object shadowRadiusWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 18 : 17, Float.class);

    private void getShadowRadius(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || shadowRadius != 0.0F) {
            packetSink.accept(shadowRadiusWatcher, shadowRadius);
        }
    }

    public void setShadowStrength(float shadowStrength) {
        this.shadowStrength = shadowStrength;
        sendPacket(updatePacketSink, this::getShadowStrength);
    }

    private static final Object shadowStrengthWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 19 : 18, Float.class);

    private void getShadowStrength(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || shadowStrength != 1.0F) {
            packetSink.accept(shadowStrengthWatcher, shadowStrength);
        }
    }

    public void setDisplayWidth(float displayWidth) {
        this.displayWidth = displayWidth;
        sendPacket(updatePacketSink, this::getDisplayWidth);
    }

    private static final Object displayWidthWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 20 : 19, Float.class);

    private void getDisplayWidth(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || displayWidth != 0.0F) {
            packetSink.accept(displayWidthWatcher, displayWidth);
        }
    }

    public void setDisplayHeight(float displayHeight) {
        this.displayHeight = displayHeight;
        sendPacket(updatePacketSink, this::getDisplayHeight);
    }

    private static final Object displayHeightWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 21 : 20, Float.class);

    private void getDisplayHeight(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || displayHeight != 0.0F) {
            packetSink.accept(displayHeightWatcher, displayHeight);
        }
    }

    public void setInterpolationDelay(int interpolationDelay) {
        this.interpolationDelay = interpolationDelay;
        sendPacket(updatePacketSink, this::getInterpolationDelay);
    }

    private static final Object interpolationDelayWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 8 : 7, Integer.class);

    private void getInterpolationDelay(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || interpolationDelay != 0) {
            packetSink.accept(interpolationDelayWatcher, interpolationDelay);
        }
    }

    public void setBillboard(Display.Billboard billboard) {
        this.billboard = billboard;
        sendPacket(updatePacketSink, this::getBillboard);
    }

    private static final Object billboardWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 15 : 14, Byte.class);

    private void getBillboard(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || billboard != Display.Billboard.FIXED) {
            packetSink.accept(billboardWatcher, (byte) billboard.ordinal());
        }
    }

    public void setGlowColorOverride(Color glowColorOverride) {
        this.glowColorOverride = glowColorOverride;
        sendPacket(updatePacketSink, this::getGlowColorOverride);
    }

    private static final Object glowColorOverrideWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 22 : 21, Integer.class);

    private void getGlowColorOverride(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || glowColorOverride != null) {
            packetSink.accept(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB());
        }
    }

    public void setBrightness(Display.Brightness brightness) {
        this.brightness = brightness;
        sendPacket(updatePacketSink, this::getBrightness);
    }

    private static final Object brightnessWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 16 : 15, Integer.class);

    private void getBrightness(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
        if (ignoreDefault || brightness != null) {
            packetSink.accept(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20);
        }
    }
}
