// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
#![cfg_attr(not(feature = "client"), allow(unused))]
pub mod stream;
pub mod text_io;
pub mod scores;
pub mod timer;

use serde::{Serialize, Deserialize};

use scores::{Scores, DScores};
use text_io::{ClientTextInput, ServerTextOutput};
use timer::Timer;

use crate::world::{World, WorldDynamic, update::WorldUpdate, player::{PlayerId, update::{PlayerUpdate, PlayerBulkUpdate}}};

#[derive(Clone)]
pub enum Message {
	World(WorldMessage),
	Misc(MiscMessage),
	PlayerUpdate(PlayerUpdate, u64), // Client → Server, frequently sent updates of the player's input with an associated update count (to compress multiple messages)
	PlayerBulkUpdate(PlayerBulkUpdate), // Client → Server, optionally sent out as redundant player update information
	ClientTextInput(ClientTextInput), // Client → Server, either a chat message or command
	Level(LevelMessage), // Server → Client, only sent in level servers
}

#[derive(Clone, Serialize, Deserialize)]
pub enum WorldMessage {
	InitWorld { // Server → Client, when the client joins a game
		player_id: PlayerId, // ID of the player to be joined
		world: Box<World>,
		spectator_count: usize,
		extension: Box<[u8]>,
	},
	WorldUpdate(WorldUpdate, u64), // Server → Client, sent for every update to the world's physics and includes an acknowledgement of how many updates the server has received
	SanityCheck(Box<WorldDynamic>), // Server → Client, used for the client to check if the server's world matches theirs

	/*
	 * These messages are world messages so that they are sent on the same QUIC
	 * stream as the "actual" world messages.
	 *
	 * This is important for the timer so that can be in sync with the world
	 * updates, and is important for the scores to satisfy the invariant that if
	 * a player id is in the scores, that id is also in the world.
	 */
	Timer(Option<Timer>), // Server → Client, updates about the timer to be displayed
	InitScores(Scores), // Server → Client, the initial scores of the game
	DScores(DScores), // Server → Client, changes in scores
	GameStateTransition(GameStateTransition), // Server → Client, indicates how the game state changed (used by the client to play a sound)
	Extension(Box<[u8]>), // Server → Client, to allow for non-breaking extensions to the protocol without updating the SERP version
}

#[derive(Clone, Serialize, Deserialize)]
#[derive(Debug)]
pub enum GameStateTransition {
	Start,
	End,
	Win,
}

#[derive(Clone, Serialize, Deserialize)]
pub enum MiscMessage {
	ServerTextOutput(ServerTextOutput), // Server → Client, a variety of chat-related messages
	SpectatorCount(usize), // Server → Client, reports to the users the number of spectators
	Extension(Box<[u8]>), // Server → Client, to allow for non-breaking extensions to the protocol without updating the SERP version
}

/**
 * A message that is only used in level servers.
 *
 * The purpose of having a special type of message that isn't in multiplayer
 * servers is so frequent changes to how levels work can be made without
 * breaking the protocol used in multiplayer, resulting in previous clients
 * being incompatible.
 */
#[derive(Clone)]
pub enum LevelMessage {
	Init {
		player_id: PlayerId, // ID of the player to be joined
		world: Box<World>,
		lives: u32,
		must_kill: usize,
		story_text: Vec<StoryText>,
	}, // Server → Client, sends the initial world to use and the world to reset to
	Completion, // Server → Client, sent when the level is completed and the client should exit
	Reset, // Server → Client and Client → Server, first sent by the server to reset the level and then sent by the client to acknowledge that reset
	Lives(u32), // Server → Client, sends the number of lives to the client
	MustKill(usize), // Server → Client, updates the number of remaining bots that need to be killed
}

pub type StoryText = Vec<(Box<str>, StoryTextColour)>;

#[derive(Clone, Deserialize)]
pub enum StoryTextColour {
	Normal,
	Yellow,
	Red,
}
