// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
#[cfg(test)]
mod tests;

use serde::{Serialize, Deserialize};

use super::{direction::Direction, input_state::InputState};

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct PlayerUpdate {
	pub dir: Direction,
	pub input_state: InputState,
}

/**
 * Redundant player update information that can be sent out.
 *
 * The purpose of this is to have the client send out redundant information
 * about the most recent player updates on networked connections to mitigate the
 * effects of packet loss.
 *
 * When the server receives a bulk update, it can add it to the queue in case
 * there's a head-of-line block on the player update stream.
 *
 * Serialisation format: The message begins with a little-endian u64 for the
 * sequence number, and then the remaining sequence of four-byte blocks encode
 * the player updates with an associated count for compression.
 *
 * Each of these four byte blocks consists of a u8 for the number of player
 * updates, a little-endian u16 for the direction and finally a u8 for the input
 * state.
 */
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlayerBulkUpdate {
	sequence_number: u64,
	updates: Vec<(PlayerUpdate, u8)>, // INVARIANT: The u8 in the tuple is never zero
}

impl PlayerBulkUpdate {
	pub fn new(sequence_number: u64) -> PlayerBulkUpdate {
		PlayerBulkUpdate { sequence_number, updates: Vec::new() }
	}

	#[cfg(feature = "client")]
	pub fn push(&mut self, update: PlayerUpdate) {
		if let Some(last) = self.updates.last_mut() && update == last.0 && last.1 < u8::MAX {
			last.1 += 1;
			return;
		}

		self.updates.push((update, 1));
	}

	/**
	 * Serialises the bulk update into a message that is limited to the following
	 * size.
	 *
	 * Returns Some(data) for the serialised message, or None if the size limit
	 * is too small or the data is invalid.
	 */
	#[cfg(any(feature = "client", test))]
	pub fn serialise(&self, limit: usize) -> Option<Vec<u8>> {
		let count_limit = match limit.checked_sub(8) {
			Some(limit) => limit / 4,
			None => return None,
		};

		let count = self.updates.len().min(count_limit);

		/*
		 * If the sequence number can fit but nothing else, then there's no point
		 * of sending the datagram, so skip it to save bandwidth. Also it's
		 * considered a violation of the protocol so wouldn't deserialise anyway.
		 */
		if count == 0 { return None; }

		let mut iter = self.updates.iter();
		let mut sequence_number = self.sequence_number;

		for update in iter.by_ref().take(self.updates.len() - count) {
			sequence_number += update.1 as u64;
		}

		let mut data = Vec::with_capacity(8 + count * 4);

		data.extend_from_slice(&sequence_number.to_le_bytes());
		for update in iter {
			if update.1 == 0 {
				return None;
			}

			data.push(update.1);
			data.extend_from_slice(&update.0.dir.to_bits().to_le_bytes());
			data.push(update.0.input_state.to_bits());
		}

		Some(data)
	}

	pub fn deserialise(data: &[u8]) -> Result<PlayerBulkUpdate, String> {
		let (sequence_number, updates) = data.split_at_checked(8).ok_or_else(|| String::from("too little data"))?;

		let sequence_number = u64::from_le_bytes(sequence_number.try_into().unwrap());

		if updates.len() % 4 != 0 {
			return Err(String::from("incomplete update received"))
		}

		let mut bulk_update = PlayerBulkUpdate::new(sequence_number);

		for data in updates.chunks_exact(4) {
			let count = data[0];
			if count == 0 {
				return Err(String::from("update count of zero"));
			}
			let dir = Direction::from_bits(u16::from_le_bytes(data[1..=2].try_into().unwrap()))?;
			let input_state = InputState::from_bits(data[3]);
			bulk_update.updates.push((PlayerUpdate { dir, input_state }, count));
		}

		if bulk_update.updates.is_empty() {
			// Considering this case a protocol violation
			return Err(String::from("no updates provided"));
		}

		Ok(bulk_update)
	}

	/**
	 * Removes all updates before the sequence number, returning None if the
	 * resulting updates would be empty.
	 *
	 * Note that this method also returns None if the beginning sequence number
	 * is after the amount received. It's possible for me to improve this and
	 * handle that case by buffering that data to be used later, but I can't be
	 * bothered to do that and from experimentation this case basically never
	 * turns up.
	 */
	pub fn truncate(mut self, sequence_number: u64) -> Option<PlayerBulkUpdate> {
		let mut dist = sequence_number.checked_sub(self.sequence_number)?;

		for (i, update) in self.updates.iter_mut().enumerate() {
			if dist < update.1 as u64 {
				update.1 -= dist as u8;
				self.updates.drain(..i);
				self.sequence_number = sequence_number;
				return Some(self);
			}

			dist -= update.1 as u64;
		}

		None
	}
}

pub struct PlayerBulkUpdateIterator {
	index: usize,
	updates: Vec<(PlayerUpdate, u8)>,
}

impl IntoIterator for PlayerBulkUpdate {
	type Item = PlayerUpdate;
	type IntoIter = PlayerBulkUpdateIterator;

	fn into_iter(self) -> PlayerBulkUpdateIterator {
		PlayerBulkUpdateIterator {
			index: 0,
			updates: self.updates,
		}
	}
}

impl Iterator for PlayerBulkUpdateIterator {
	type Item = PlayerUpdate;

	fn next(&mut self) -> Option<PlayerUpdate> {
		self.updates.get_mut(self.index).map(|update| {
			let ret = update.0;

			/*
			 * Using the invariant that this is never zero on the first call, so
			 * things won't panic from underflow in dev mode.
			 */
			update.1 -= 1;
			if update.1 == 0 {
				self.index += 1;
			}
			ret
		})
	}
}
