// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use glam::Vec2;

use crate::world::player::direction::Direction;

#[derive(PartialEq, Eq)]
pub enum Aim {
	/**
	 * It doesn't matter which direction the player shoots, they will always hit
	 * their target.
	 */
	Anywhere,

	/**
	 * The player shooting in this direction will result in a perfect hit under a
	 * few assumptions. These assumptions are that the target continues to move
	 * at a constant speed and that the bullet moves at a constant speed (not
	 * true because of drag) and lasts forever (also not true).
	 */
	Direction(Direction),

	/**
	 * This method can return `None` if no such direction that guarantees a hit
	 * (under these assumptions) is found. This can be if the target is moving
	 * away faster than any bullet the player can fire.
	 *
	 * Any bullets shot by the player cannot reach the target (under the
	 * assumptions), regardless of the aiming direction. This can be if the
	 * target is moving away faster than any bullet the player can fire.
	 */
	Unreachable,
}

/**
 * Calculates information about how the player should aim to shoot a moving target.
 *
 * `dpos` is the target position subtracted by the player's position, and `dvel`
 * is similar for velocity.
 */
pub fn aim(dpos: Vec2, dvel: Vec2, bullet_speed: f32) -> Aim {
	/*
	 * It's unclear what this code does, but it's the result of solving the equation
	 * 	   x_b(t_c) = x_T(t_c)
	 * 	=> x_p_0 + (d * v_b + v_p) * t_c = x_T_0 + v_T * t_c
	 * where x_p_0 is the initial player position, x_T_0 is the initial target
	 * position, v_p is the player velocity, v_T is the target velocity, v_b is
	 * the bullet speed, x_b is the bullet position over time, x_T is the target
	 * position over time and t_c is the time of collision.
	 *
	 * This equation is first solved to find `d` in terms of `dpos` (x_T_0 -
	 * x_p_0), `dvel` (v_T_0 - v_p_0) and an unknown time of collision `t_c`. To
	 * then find this value of `d`, you use the further constraint that |d| = 1
	 * and substitute the RHS into `d`. Simplifying everything produces a
	 * quadratic equation which can then be solved to find `t_c` and then find
	 * `d`.
	 */

	// If the players are on top of each other, any direction works
	// Without this early exit, the other player will falsely be considered unreachable
	if dpos == Vec2::ZERO {
		return Aim::Anywhere;
	}

	// Quadratic coefficients
	let (a, b, c) = (dpos.length_squared(), 2.0 * dpos.dot(dvel), dvel.length_squared() - bullet_speed.powi(2));

	let disc = b.powi(2) - 4.0 * a * c;
	if disc < 0.0 {
		return Aim::Unreachable;
	}

	// There should be one solution and that is positive, so use the larger root
	let s = (-b + disc.sqrt()) / (2.0 * a);

	// Negative means unreachable
	if s > 0.0 { Aim::Direction(Direction::from(s * dpos + dvel)) }
	else { Aim::Unreachable }
}
