use core::fmt;
use std::cmp::Ordering;
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
#[derive(
    Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Encode, Decode,
)]
pub enum Player {
    #[default]
    Player1,
    Player2,
    Player3,
    Player4,
}
impl Player {
    pub const MAX_PLAYERS: usize = 4;
    pub fn to_num(self) -> u8 {
        match self {
            Self::Player1 => 1,
            Self::Player2 => 2,
            Self::Player3 => 3,
            Self::Player4 => 4,
        }
    }
    fn next(self) -> Option<Self> {
        self.to_num()
            .checked_add(1)
            .and_then(|num| Self::try_from(num).ok())
    }
}
impl fmt::Display for Player {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "player {}", self.to_num())
    }
}
impl PartialOrd for Player {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for Player {
    fn cmp(&self, other: &Self) -> Ordering {
        self.to_num().partial_cmp(&other.to_num()).unwrap()
    }
}
impl TryFrom<u8> for Player {
    type Error = String;
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            1 => Ok(Player::Player1),
            2 => Ok(Player::Player2),
            3 => Ok(Player::Player3),
            4 => Ok(Player::Player4),
            value => Err(format!(
                "Player number must be between 1 and 4, got {value}."
            )),
        }
    }
}
pub struct PlayerRange {
    start: Player,
    stop: Player,
    current: Option<Player>,
}
impl PlayerRange {
    pub fn up_to(stop: Player) -> Self {
        Self::new(Player::Player1, stop)
    }
    pub fn new(start: Player, stop: Player) -> Self {
        assert!(start <= stop);
        Self {
            start,
            stop,
            current: Some(start),
        }
    }
    pub fn contains(&self, player: Player) -> bool {
        self.start <= player && player <= self.stop
    }
}
impl Iterator for PlayerRange {
    type Item = Player;
    fn next(&mut self) -> Option<Self::Item> {
        match self.current {
            Some(current) => {
                if current == self.stop {
                    self.current = None;
                } else {
                    self.current = current.next();
                }
                Some(current)
            }
            None => {
                self.current = Some(self.start);
                None
            }
        }
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        let size = match self.current {
            Some(current) => 1 + (self.stop.to_num() - current.to_num()) as usize,
            None => 0,
        };
        (size, Some(size))
    }
}
impl ExactSizeIterator for PlayerRange {}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_range() {
        let mut range = PlayerRange::new(Player::Player2, Player::Player4);
        assert_eq!(range.next(), Some(Player::Player2));
        assert_eq!(range.next(), Some(Player::Player3));
        assert_eq!(range.next(), Some(Player::Player4));
        assert_eq!(range.next(), None);
        assert_eq!(range.next(), Some(Player::Player2));
        assert_eq!(range.next(), Some(Player::Player3));
        assert_eq!(range.next(), Some(Player::Player4));
        assert_eq!(range.next(), None);
    }
    #[test]
    fn test_range_up_to() {
        let mut range = PlayerRange::up_to(Player::Player3);
        assert_eq!(range.next(), Some(Player::Player1));
        assert_eq!(range.next(), Some(Player::Player2));
        assert_eq!(range.next(), Some(Player::Player3));
        assert_eq!(range.next(), None);
    }
}