// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
pub mod config;
mod events;
mod bots;

use std::{collections::BTreeMap, path::PathBuf, sync::Arc};

use glam::Vec2;

use config::Config;
use events::Events;
use bots::{Bots, BotPlayer};

use super::common::{SANITY_CHECK_PERIOD, player_update_queue::PlayerUpdateQueue, changes::ServerChanges};

use crate::protocol::message::{
	Message, WorldMessage, MiscMessage, LevelMessage,
	text_io::{self, ClientTextInput, ServerTextOutput, ChatType},
	stream::{MessageStream, local::LocalMessageStream},
};
use crate::world::{
	World, WorldDynamic, player::{PlayerId, PlayerStyle, HumanStyle},
	team::TeamId, checkpoint::Checkpoint, flag::Flag, update::WorldUpdate,
	event::{WorldEvent, MoveType}, changes::IgnoreChanges,
};
use crate::utils::{Clock, maths::Circle};

pub struct Server {
	stream: LocalMessageStream,
	updates: PlayerUpdateQueue,
	player_id: PlayerId,
	style: HumanStyle,
	waiting_reset_ack: usize,
	next_sanity_check: f32,

	init_world_data: WorldDynamic,
	world: World,
	init_events: Events,
	events: Events,
	clock: Clock,

	config: Arc<Config>,

	bots: Bots,
	prev_changes: ServerChanges,
	sent_flag_pickup_message: bool,

	lives: u32,
	outcome_timer: Option<(Outcome, f32)>,
}

enum Outcome {
	Completion,
	Failure,
}

impl Server {
	pub fn try_new(config: Arc<Config>, config_path: PathBuf, style: HumanStyle, mut stream: LocalMessageStream, sanity_checks: bool, spectate: bool) -> Result<Server, String> {
		config.validate().map_err(|err| format!("invalid config: {err}"))?;

		let blocks = config.blocks.generate(config_path).map_err(|err| format!("cannot generate block map: {err}"))?;
		let flags = if let Some(flag) = &config.flag { Box::from([Flag::new(flag.pos, flag.colour)]) } else { Box::from([]) };

		let mut world = World::new(config.world.clone(), blocks, config.get_special_areas(), Box::from([]), flags, config.checkpoints.iter().copied().map(Checkpoint::new).collect());

		let player_id = PlayerId::from(0);

		let mut events = if spectate { Vec::new() } else { vec![
			WorldEvent::NewPlayer(player_id, PlayerStyle::Human(style.clone()), None, None),
			WorldEvent::PlayerAmmo(player_id, config.ammo.init.0),
			WorldEvent::PlayerPos(player_id, config.spawnpoint, MoveType::Spawn),
		] };

		let mut bot_players = BTreeMap::new();
		for (i, bot) in config.resolve_bots().map_err(|err| format!("invalid bot config: {err}"))?.enumerate() {
			let bot_id = PlayerId::from(i as u32 + 1);
			events.push(WorldEvent::NewPlayer(bot_id, PlayerStyle::Bot(bot.config.colour), Some(TeamId::default()), Some(bot.config.player.clone())));
			events.push(WorldEvent::PlayerAmmo(bot_id, bot.config.ammo.init.0));
			events.push(WorldEvent::PlayerPos(bot_id, bot.pos, MoveType::Spawn));
			events.push(WorldEvent::PlayerDir(bot_id, bot.dir.0));
			bot_players.insert(bot_id, BotPlayer::new(bot.pos, bot.dir.0, bot.config));
		}
		let bots = Bots::new(bot_players, &world);

		for &pos in &config.ammo.crate_init_spawn {
			events.push(WorldEvent::NewAmmoCrate(pos));
		}
		for &(pos, typ) in &config.powerups.init_spawn {
			events.push(WorldEvent::NewPowerup(pos, typ));
		}

		world.apply_events(&events, &mut IgnoreChanges);

		let lives = config.lives.into();
		let _ = stream.send(Message::Level(LevelMessage::Init {
			player_id,
			world: Box::new(world.clone()),
			lives,
			must_kill: bots.must_kill_remaining(),
			story_text: config.story_text.clone(),
		}));

		let init_world_data = world.get_dynamic_data().clone();
		let events = Events::new(&config);
		let init_events = events.clone();
		Ok(Server {
			stream,
			updates: PlayerUpdateQueue::new(),
			player_id,
			style,
			waiting_reset_ack: 0,
			next_sanity_check: if sanity_checks { SANITY_CHECK_PERIOD } else { f32::INFINITY },
			init_world_data,
			world,
			init_events,
			events,
			clock: Clock::new_world(),
			config,
			bots,
			prev_changes: ServerChanges::default(),
			lives,
			sent_flag_pickup_message: false,
			outcome_timer: None,
		})
	}

	fn send(&mut self, msg: Message) -> Result<(), String> {
		self.stream.send(msg).map_err(|err| format!("failed sending message: {err}"))
	}

	pub fn update(&mut self, dt: f32) -> Result<(), String> {
		if let Some((outcome, time)) = &mut self.outcome_timer {
			*time += dt;
			if *time > 1.5 {
				match outcome {
					Outcome::Completion => self.send(Message::Level(LevelMessage::Completion))?,
					Outcome::Failure => {
						self.send(Message::Level(LevelMessage::Reset))?;
						self.bots.reset();
						self.world.set_dynamic_data(self.init_world_data.clone());
						self.events = self.init_events.clone();
						self.lives = self.config.lives.into();
						self.sent_flag_pickup_message = false;
						self.waiting_reset_ack += 1;
					},
				}

				self.outcome_timer = None;
			}
		}

		let steps = self.clock.advance(dt);

		loop {
			match self.stream.receive() {
				Ok(Some(Message::PlayerUpdate(update, steps))) => self.updates.push(update, steps),
				Ok(Some(Message::PlayerBulkUpdate(bulk_update))) => self.updates.bulk_update(bulk_update),
				Ok(Some(Message::ClientTextInput(ClientTextInput::Chat(_, msg)))) => {
					let msg = Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Chat(ChatType::Global, self.style.name.clone(), self.style.colour, msg)));
					self.send(msg)?;
				},
				Ok(Some(Message::ClientTextInput(ClientTextInput::Command(_)))) => {
					let msg = Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::CommandResponse(text_io::from_const!("Commands not supported in level server."))));
					self.send(msg)?;
				},
				Ok(Some(Message::ClientTextInput(ClientTextInput::Extension(_)))) => (),
				Ok(Some(Message::Level(LevelMessage::Reset))) => {
					self.updates.reset();
					self.waiting_reset_ack = self.waiting_reset_ack.saturating_sub(1);
				},
				Ok(Some(_)) => return Err(String::from("player violated the protocol, sending an invalid message")),
				Ok(None) => break,
				Err(err) => return Err(format!("player disconnected with error: {err}")),
			}
		}

		self.updates.balance_queue(dt); // Not sure if this is needed

		for _ in 0..steps {
			let mut player_updates = BTreeMap::new();
			let (update, mut ack) = self.updates.pop();
			if self.waiting_reset_ack > 0 {
				ack = 0; // Prevents warning on the client
			}
			if self.world.has_player(self.player_id) {
				player_updates.insert(self.player_id, update);
			}

			let on_update = |id, update| _ = player_updates.entry(id).and_modify(|_| debug_assert!(false)).or_insert(update);
			self.bots.get_player_updates(&self.world, &self.prev_changes, on_update);

			let mut changes = ServerChanges::default();
			let pre_events = self.events.get_pre_events(&self.world, &self.config);
			self.world.apply_events(&pre_events, &mut changes);

			self.world.update(&player_updates, &mut changes);

			let all_killed = self.bots.must_kill_remaining() == 0;
			let mut post_events = self.events.get_post_events(&self.world, &changes, &self.config, |id| {
				if id == self.player_id {
					self.config.ammo.crate_supply
				} else {
					self.bots.get_ammo_crate_supply(id).unwrap_or_default()
				}
			}, all_killed, self.player_id);
			for &id in &changes.dead {
				if id == self.player_id {
					post_events.push(WorldEvent::PlayerPos(id, self.world.get_active_checkpoint_pos().unwrap_or(self.config.spawnpoint), MoveType::Spawn));
					post_events.push(WorldEvent::PlayerVel(id, Vec2::ZERO));
					self.lives = self.lives.saturating_sub(1);
					self.send(Message::Level(LevelMessage::Lives(self.lives)))?;
					if self.lives == 0 {
						self.set_outcome(Outcome::Failure);
						post_events.push(WorldEvent::RemovePlayer(id));
					}
				} else {
					post_events.push(WorldEvent::RemovePlayer(id));
					self.bots.remove_player(id);
					self.send(Message::Level(LevelMessage::MustKill(self.bots.must_kill_remaining())))?;
				}
			}

			if all_killed {
				if let Some(flag) = &self.config.flag && !self.sent_flag_pickup_message {
					self.send(Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Server(flag.pickup_message.clone()))))?;
					self.sent_flag_pickup_message = true;
				}

				if self.config.flag.is_none() || (
					self.world.get_flags().first().is_some_and(|flag| flag.get_following() == Some(self.player_id)) &&
					self.world.get_players().get(&self.player_id).is_some_and(|player| player.get_pos().distance_squared(self.config.spawnpoint) <= 16.0)
				) {
					self.set_outcome(Outcome::Completion);
				}
			}

			self.world.apply_events(&post_events, &mut changes);

			self.prev_changes = changes;

			self.send(Message::World(WorldMessage::WorldUpdate(WorldUpdate {
				pre_events, players: player_updates, post_events,
			}, ack)))?;
		}

		if let Some(msg) = self.get_sanity_check(dt) {
			self.send(msg)?;
		}

		Ok(())
	}

	fn set_outcome(&mut self, outcome: Outcome) {
		if self.outcome_timer.is_none() {
			self.outcome_timer = Some((outcome, 0.0));
		}
	}

	fn get_sanity_check(&mut self, dt: f32) -> Option<Message> { // Directly copy-pasted from multiplayer server
		self.next_sanity_check -= dt;
		(self.next_sanity_check <= 0.0).then(|| {
			self.next_sanity_check += SANITY_CHECK_PERIOD;
			Message::World(WorldMessage::SanityCheck(Box::new(self.world.get_dynamic_data().clone())))
		})
	}
}
