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

pub struct Line(pub Vec2, pub Vec2);

pub struct LightConfig {
	pub att_a: f32,
	pub att_b: f32,
}

impl Default for LightConfig {
	fn default() -> LightConfig {
		LightConfig {
			att_a: 900.0,
			att_b: 1.0,
		}
	}
}

impl Line {
	/**
	 * Calculates the intensity this line provides to the point.
	 *
	 * This algorithm works by integrating the intensity provided by each point
	 * on the line. This function calculates:
	 * 	l \int_0^1 α(||p - lerp(p0, p1, t)||) dt
	 *
	 * where α(d) = 1 / (ad² + b) is the attenuation function for distance d, l
	 * is the length of the line and p is the input point.
	 *
	 * The integrand simplifies to the form of 1 / (at² + bt + c) with a
	 * negative discriminant which luckily can be solved analytically.
	 */
	pub fn intensity(&self, pos: Vec2, config: &LightConfig) -> f32 {
		let len_squared = self.0.distance_squared(self.1);
		let a = config.att_a * len_squared;
		let b = config.att_a * 2.0 * ((self.0.x - pos.x) * (self.1.x - self.0.x) + (self.0.y - pos.y) * (self.1.y - self.0.y));
		let c = config.att_a * self.0.distance_squared(pos) + config.att_b;
		len_squared.sqrt() * (Line::antideriv_recip_quadratic(a, b, c, 1.0) - Line::antideriv_recip_quadratic(a, b, c, 0.0))
	}

	/**
	 * Returns the evaluation of an antiderivative of the function
	 * 	f(x) = 1 / (ax² + bx + c).
	 *
	 * Only handles the case when the discriminant is negative because the
	 * polynomial by definition is positive for all values (due to being related
	 * to the distance and having positive coefficients).
	 *
	 * Panics if a = 0 or the discriminant isn't negative.
	 *
	 * Source: https://en.wikipedia.org/wiki/List_of_integrals_of_rational_functions#Integrands_of_the_form_xm_/_(a_x2_+_b_x_+_c)n
	 */
	fn antideriv_recip_quadratic(a: f32, b: f32, c: f32, x: f32) -> f32 {
		assert_ne!(a, 0.0, "Make sure the line's endpoints aren't the same.");

		let neg_dist = 4.0 * a * c - b * b;

		/*
		 * NOTE: This can fail sometimes (probably floating-point error) when
		 * `att_a` is high, try increasing `att_b` to reduce this from happening.
		 */
		assert!(neg_dist > 0.0);

		let sqrt_neg_disc = neg_dist.sqrt();
		2.0 / sqrt_neg_disc * ((2.0 * a * x + b) / sqrt_neg_disc).atan()
	}
}
