package pp.battleship.model;

import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;

/**
 * The {@code Shell} class represents a projectile fired by a ship in the Battleship game.
 * It models the position and rotation of the projectile and allows for its movement along
 * a Bezier curve.
 */
public class Shell implements Item {
    /**
     * Initial position of the shell
     */
    private final static Vector3f INIT_POS = new Vector3f(-3, 7, -3);
    /**
     * The overshot difference vector used to get a shallower flight path
     */
    private final static Vector3f OVER_SHOT_DIFF = new Vector3f(-1, -1, -1);
    // Target shot position
    private final Vector3f shotPosition;
    // Position on top of shotPosition used for Bezier curve
    private final Vector3f overShotPosition;
    // Current position of the shell
    private Vector3f position;
    // Current rotation of the shell
    private final Quaternion rotation;

    /**
     * Constructs a new {@code Shell} object using the given {@code Shot} target.
     * The initial position, target position, and overshot position are calculated.
     *
     * @param shot The target {@code Shot} object containing the destination coordinates.
     */
    public Shell(Shot shot) {
        this.shotPosition = new Vector3f(shot.getX(), 0, shot.getY());
        this.overShotPosition = new Vector3f(shotPosition.x, INIT_POS.y, shotPosition.z).add(OVER_SHOT_DIFF);
        this.position = INIT_POS;
        this.rotation = new Quaternion();
    }

    /**
     * Gets the current position of the shell.
     *
     * @return The current position as a {@code Vector3f}.
     */
    public Vector3f getPosition() {
        return this.position;
    }

    /**
     * Gets the current rotation of the shell.
     *
     * @return The current rotation as a {@code Quaternion}.
     */
    public Quaternion getRotation() {
        return this.rotation;
    }

    /**
     * Moves the shell along a Bezier curve based on the given time factor {@code t}.
     * The position and rotation of the shell are updated.
     *
     * @param t The time factor between 0 and 1, representing the progress of the shell's flight.
     */
    public void move(float t) {
        if (t > 1f) t = 1f;
        Vector3f newPosition = bezInt(INIT_POS, overShotPosition, shotPosition, t);
        updateRotation(position, newPosition);
        this.position = newPosition;
    }

    /**
     * Performs a quadratic Bezier interpolation between three points based on the time factor {@code t}.
     *
     * @param p1 The start position.
     * @param p2 The overshot position.
     * @param p3 The target position.
     * @param t  The time factor for interpolation.
     * @return The interpolated position as a {@code Vector3f}.
     */
    private Vector3f bezInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
        Vector3f inA = linInt(p1, p2, t);
        Vector3f inB = linInt(p2, p3, t);
        return linInt(inA, inB, t);
    }

    /**
     * Performs linear interpolation between two points {@code p1} and {@code p2} based on the time factor {@code t}.
     *
     * @param p1 The start position.
     * @param p2 The end position.
     * @param t  The time factor for interpolation.
     * @return The interpolated position as a {@code Vector3f}.
     */
    private Vector3f linInt(Vector3f p1, Vector3f p2, float t) {
        float x = p1.getX() + t * (p2.getX() - p1.getX());
        float y = p1.getY() + t * (p2.getY() - p1.getY());
        float z = p1.getZ() + t * (p2.getZ() - p1.getZ());
        return new Vector3f(x, y, z);
    }

    /**
     * Updates the rotation of the shell to face the new position along its flight path.
     *
     * @param oldPos The previous position of the shell.
     * @param newPos The new position of the shell.
     */
    private void updateRotation(Vector3f oldPos, Vector3f newPos) {
        Vector3f direction = newPos.subtract(oldPos).normalize();
        if (direction.lengthSquared() > 0) {
            this.rotation.lookAt(direction, Vector3f.UNIT_Y);
        }
    }

    @Override
    public <T> T accept(Visitor<T> visitor) {
        return visitor.visit(this);
    }

    @Override
    public void accept(VoidVisitor visitor) {
        visitor.visit(this);
    }
}
