/*
 *   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 lombok.Getter;
import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Display;
import org.bukkit.util.Consumer;
import org.bukkit.util.Transformation;
import org.joml.Quaternionf;
import org.joml.Vector3f;

import java.util.Objects;

/**
 * Can be used for Lines of short length.
 */
@Getter
public class CRay extends CEntity {

    public static final float DEFAULT_WIDTH = 1 / 16f;

    private Location from;
    private Location to;
    private float width = DEFAULT_WIDTH;
    private BlockData blockData = RBlockDisplay.DEFAULT_BLOCK;
    private boolean hide = false;

    public CRay(REntityServer server) {
        super(server);
    }

    private <T> CRay checkAndSet(T currentValue, T newValue, Consumer<T> setter) {
        if (Objects.equals(currentValue, newValue)) return this;
        setter.accept(newValue);
        tick();
        return this;
    }

    public CRay setFrom(Location from) {
        return checkAndSet(this.from, from, location -> this.from = location);
    }

    public CRay setTo(Location to) {
        return checkAndSet(this.to, to, location -> this.to = location);
    }

    public CRay setWidth(float width) {
        return checkAndSet(this.width, width, location -> this.width = width);
    }

    public CRay setBlock(BlockData blockData) {
        if (this.blockData.equals(blockData)) return this;
        if (blockData == null) {
            this.blockData = RBlockDisplay.DEFAULT_BLOCK;
        } else {
            this.blockData = blockData;
        }
        if (display != null) {
            display.setBlock(blockData);
        }
        return this;
    }

    @Override
    public void hide(boolean hide) {
        if (hide == this.hide) return;
        this.hide = hide;
        if (hide) {
            if (display != null) display.hide(true);
        } else {
            tick();
        }
    }

    @Override
    void tick() {
        if (from == null || to == null) return;
        if (hide) return;
        updateDisplay();
    }

    private RBlockDisplay display;

    private void updateDisplay() {
        if (display == null) {
            display = new RBlockDisplay(server, new Location(null, 0, 0, 0));
            display.setBrightness(new Display.Brightness(15, 15));
            display.setViewRange(560);
            display.setBlock(blockData);
            entities.add(display);
        } else {
            display.hide(false);
        }

        Vector3f pointA = new Vector3f((float) from.getX(), (float) from.getY(), (float) from.getZ());
        Vector3f pointB = new Vector3f((float) to.getX(), (float) to.getY(), (float) to.getZ());
        Vector3f direction = new Vector3f(pointB).sub(pointA);
        float length = direction.length();

        display.move(from);

        Vector3f normalizedDir = new Vector3f(direction).normalize();

        final Vector3f defaultAxis = new Vector3f(1, 0, 0);
        Quaternionf rotation = new Quaternionf().rotationTo(defaultAxis, normalizedDir);

        Vector3f scale = new Vector3f(length, width, width);

        Transformation transformation = new Transformation(new Vector3f(0, 0, 0), rotation, scale, new Quaternionf());
        display.setTransform(transformation);
    }
}
