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

use glam::Vec2;
use serde::{Serialize, Deserialize};

use super::{player::{Player, PlayerId, forcefield::Forcefield}, team::TeamId, blocks::Blocks, WorldRng};

use crate::utils::{rng::Float, maths::{Circle, CollidingCircle, CollidingRect, glam_fix::Fix}};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bullet {
	pos: Vec2, vel: Vec2, dir: Vec2,
	damage: f32,
	damage_mul: f32,
	time: f32,
	lifetime: f32,
	player_owner: Option<PlayerId>,
	team_owner: Option<TeamId>,
}

pub const RADIUS: f32 = 0.125;

impl Bullet {
	pub fn new(player: &Player, player_id: PlayerId, damage_mul: f32, spread: f32) -> Bullet {
		let dir = Vec2::from(player.get_dir()).rotate(Vec2::from_angle(spread));
		let config = &player.get_config().bullets;
		Bullet {
			pos: player.get_pos(),
			vel: player.get_vel() + dir * config.speed,
			damage: config.damage * damage_mul,
			damage_mul,
			dir,
			time: 0.0,
			lifetime: config.lifetime,
			player_owner: Some(player_id),
			team_owner: player.get_team(),
		}
	}

	pub fn random_spread(player: &Player, rng: &mut WorldRng) -> f32 {
		(rng.next_f32() - 0.5) * player.get_config().bullets.spread
	}

	#[cfg(feature = "client")] pub fn get_smooth_pos(&self, time_diff: f32) -> Vec2 { self.pos + self.vel * time_diff }
	#[cfg(feature = "client")] pub fn get_vel(&self) -> Vec2 { self.vel }

	pub fn get_damage(&self) -> f32 { self.damage }
	#[cfg(feature = "client")] pub fn get_damage_mul(&self) -> f32 { self.damage_mul }
	#[cfg(feature = "client")] pub fn get_time(&self) -> f32 { self.time }
	pub fn get_player_owner(&self) -> Option<PlayerId> { self.player_owner }

	pub fn update(&mut self, dt: f32, forcefields: &[Forcefield]) -> bool {
		let v1 = self.vel.dot(self.dir) * self.dir; // Projection
		let v2 = self.vel - v1; // Orthogonal projection
		let mut acc = Bullet::get_drag(v1, dt, 0.00390625) + Bullet::get_drag(v2, dt, 0.5);

		for ffield in forcefields.iter().filter(|ffield| self.should_deflect(ffield.player_id, ffield.team_id)) {
			if let Some(strength) = Forcefield::strength(self.pos, ffield.pos) {
				let dir = (self.pos - ffield.pos).normalise_or_zero();
				acc += strength * ffield.strength * dir;
			}
		}

		self.vel += acc * dt;
		self.pos += self.vel * dt;
		self.time += dt;
		self.is_alive()
	}

	fn get_drag(vel: Vec2, dt: f32, drag_k: f32) -> Vec2 {
		let speed = vel.length();

		let part_drag_size = speed * drag_k;
		let mut drag = -vel * part_drag_size;
		let drag_size = speed * part_drag_size;

		// Limits the drag similar to the player physics
		let drag_limit = speed / dt.abs(); // Delta time can be negative if reversing physics
		if drag_size > drag_limit {
			drag *= drag_limit / drag_size;
		}

		drag
	}

	pub fn should_deflect(&self, player_id: PlayerId, team_id: Option<TeamId>) -> bool {
		self.should_collide(player_id, team_id, false) // Prevents deflection from the same team even if friendly fire is enabled
	}

	fn should_collide(&self, player_id: PlayerId, team_id: Option<TeamId>, friendly_fire: bool) -> bool {
		Some(player_id) != self.player_owner && (TeamId::opponents(team_id, self.team_owner) || friendly_fire)
	}

	fn disown_player(&mut self, player_id: PlayerId) {
		if self.player_owner == Some(player_id) {
			self.player_owner = None;
		}
	}

	fn is_alive(&self) -> bool {
		self.time <= self.lifetime
	}
}

impl Circle for Bullet {
	fn get_pos(&self) -> Vec2 { self.pos }
	fn get_radius(&self) -> f32 { RADIUS }
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct BulletManager(Vec<Bullet>);

impl BulletManager {
	pub fn new() -> BulletManager { BulletManager::default() }

	pub(super) fn clear(&mut self) {
		self.0.clear();
	}

	pub fn add(&mut self, bullet: Bullet) {
		self.0.push(bullet);
	}

	pub fn update(&mut self, dt: f32, forcefields: &[Forcefield]) {
		self.0.retain_mut(|bullet| bullet.update(dt, forcefields));
	}

	pub(super) fn disown(&mut self, id: PlayerId) {
		for bullet in &mut self.0 {
			bullet.disown_player(id);
		}
	}

	pub fn collide_player(&mut self, player: &Player, player_id: PlayerId, friendly_fire: bool) -> Vec<Bullet> {
		self.0.extract_if(.., |bullet| bullet.colliding_circle(player) && bullet.should_collide(player_id, player.get_team(), friendly_fire)).collect()
	}

	pub fn collide_blocks(&mut self, blocks: &Blocks) -> Vec<Bullet> {
		self.0.extract_if(.., |bullet| blocks.iter_range(bullet.pos, RADIUS).any(|block| (bullet.pos, RADIUS).colliding_rect(block.get_rect()))).collect()
	}
}

impl Deref for BulletManager {
	type Target = [Bullet];

	fn deref(&self) -> &Self::Target {
		self.0.as_slice()
	}
}
