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

use super::{config::Config, super::common::{events::EffectController, changes::ServerChanges}};

use crate::world::{
	World, WorldRng, DELTA_TIME, event::WorldEvent, blocks::Blocks,
	ammo_crate::RADIUS as AMMO_CRATE_RADIUS, powerup::{PowerupType, RADIUS as
	POWERUP_RADIUS}, effects::{Effect, EffectInfo}, player::PlayerId,
	flag::{Flag, FlagState, CaptureAnimation},
};
use crate::utils::maths::{Rect, CollidingRect, Circle};

#[derive(Clone)]
pub struct Events {
	ammo_rng: WorldRng,
	next_ammo_crate: f32,
	effect_controller: EffectController,
	powerup_rng: WorldRng,
	next_powerup: f32,
}

impl Events {
	pub fn new(config: &Config) -> Events {
		Events {
			ammo_rng: WorldRng::seed_from_u64(config.ammo.seed),
			next_ammo_crate: config.ammo.crate_spawn_period,
			effect_controller: EffectController::new(config.effects.powerup_effect_reduction),
			powerup_rng: WorldRng::seed_from_u64(config.powerups.seed),
			next_powerup: config.powerups.spawn_period,
		}
	}

	pub fn get_pre_events(&mut self, world: &World, config: &Config) -> Vec<WorldEvent> {
		let mut events = Vec::new();

		if world.get_ammo_crates().len() < config.ammo.crate_limit {
			self.next_ammo_crate -= DELTA_TIME;
			if self.next_ammo_crate <= 0.0 {
				self.spawn_ammo_crate(&mut events, world.get_blocks(), config);
				self.next_ammo_crate += config.ammo.crate_spawn_period;
			}
		}

		if world.get_powerups().len() < config.powerups.limit {
			self.next_powerup -= DELTA_TIME;
			if self.next_powerup <= 0.0 {
				self.spawn_powerup(&mut events, world);
				self.next_powerup += config.powerups.spawn_period;
			}
		}

		events
	}

	pub fn get_post_events(&mut self, world: &World, changes: &ServerChanges, config: &Config, get_ammo_supply: impl Fn(PlayerId) -> u32, can_pickup_flag: bool, player_id: PlayerId) -> Vec<WorldEvent> {
		let mut events = Vec::new();

		for &id in &changes.ammo_crates_collected {
			let Some(player) = world.get_players().get(&id) else { continue; };

			let new_ammo = player.get_ammo_count() + get_ammo_supply(id);
			events.push(WorldEvent::PlayerAmmo(id, new_ammo));
		}

		for &(id, effect) in &changes.effects_to_apply {
			if changes.dead.contains(&id) { continue; }
			let Some(player) = world.get_players().get(&id) else { continue; };

			self.effect_controller.powerup_collected(id, effect, EffectInfo { time: config.effects.effect_time, power: config.effects.get_effect_power(effect) });

			if effect == Effect::Regeneration {
				events.push(WorldEvent::PlayerHealth(id, player.add_health(config.effects.health_powerup_health_increase)));
			}
		}

		for &id in &changes.dead {
			self.effect_controller.reset_player_effects(id);
		}

		if
			can_pickup_flag &&
			let Some(flag) = world.get_flags().first() &&
			let Some(pos) = flag.get_fixed_pos() &&
			let Some(player) = world.get_players().get(&player_id) &&
			player.colliding_rect(&Flag::rect_from_pos(pos))
		{
			events.push(WorldEvent::FlagState(0, FlagState::Following(player_id, CaptureAnimation::new(pos - player.get_pos()))));
		}

		self.effect_controller.update(world, &mut events, DELTA_TIME);
		events
	}

	fn spawn_ammo_crate(&mut self, events: &mut Vec<WorldEvent>, blocks: &Blocks, config: &Config) {
		if config.ammo.crate_spawn_ranges.is_empty() { return; }
		for _ in 0..20 {
			let range = &config.ammo.crate_spawn_ranges[self.ammo_rng.next_u32() as usize % config.ammo.crate_spawn_ranges.len()];
			let pos = range.get_p0() + self.ammo_rng.r#gen::<Vec2>() * (range.get_p1() - range.get_p0());
			if blocks.iter_range(pos, AMMO_CRATE_RADIUS).count() == 0 {
				events.push(WorldEvent::NewAmmoCrate(pos));
				return;
			}
		}
	}

	fn spawn_powerup(&mut self, events: &mut Vec<WorldEvent>, world: &World) {
		for _ in 0..20 {
			let pos = world.get_size() * (self.powerup_rng.r#gen::<Vec2>() - 0.5);
			if !world.get_blocks().iter_range(pos, POWERUP_RADIUS).any(|block| (pos, POWERUP_RADIUS).colliding_rect(block.get_rect())) {
				let typ = match self.powerup_rng.r#gen::<u32>() % 5 {
					0 => PowerupType::Speed,
					1 => PowerupType::Reload,
					2 => PowerupType::Health,
					3 => PowerupType::Damage,
					_ => PowerupType::Forcefield,
				}; // Don't want to bother implementing teleportation
				events.push(WorldEvent::NewPowerup(pos, typ));
				return;
			}
		}
	}
}
