//! Chessboard vocabulary.

use crate::bitboard::*;

macro_rules! container {
    ($v:vis, $a:ident, $b:ident, $n:literal) => {
        #[doc = "Container with values for each [`"]
		#[doc = stringify!($a)]
		#[doc = "`]."]
        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
        $v struct $b<T>(pub(crate) [T; $n]);
        #[allow(unused)]
        impl<T> $b<T> {
            #[inline]
            pub const fn new(values: [T; $n]) -> Self {
                Self(values)
            }
            #[inline]
            pub const fn get(&self, k: $a) -> &T {
                &self.0[k as usize]
            }
            #[inline]
            pub const fn get_mut(&mut self, k: $a) -> &mut T {
                &mut self.0[k as usize]
            }
            #[inline]
            pub fn with<F>(f: F) -> Self
            where
                F: FnMut($a) -> T,
            {
                Self($a::all().map(f))
            }
        }
    };
}

/// The players.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Color {
    White,
    Black,
}

container!(pub, Color, ByColor, 2);

impl Color {
    #[inline]
    pub const fn all() -> [Self; 2] {
        [Self::White, Self::Black]
    }

    #[inline]
    pub(crate) fn home_rank(self) -> Rank {
        match self {
            Self::White => Rank::First,
            Self::Black => Rank::Eighth,
        }
    }

    #[inline]
    pub(crate) fn promotion_rank(self) -> Rank {
        match self {
            Self::White => Rank::Eighth,
            Self::Black => Rank::First,
        }
    }

    #[inline]
    pub(crate) fn forward(self) -> Direction {
        match self {
            Self::White => Direction::North,
            Self::Black => Direction::South,
        }
    }
}

impl core::ops::Not for Color {
    type Output = Self;
    #[inline]
    fn not(self) -> Self::Output {
        match self {
            Self::Black => Self::White,
            Self::White => Self::Black,
        }
    }
}

/// A column of the board.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum File {
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    H,
}

container!(pub(crate), File, ByFile, 8);

impl File {
    #[inline]
    pub const fn all() -> [Self; 8] {
        [
            Self::A,
            Self::B,
            Self::C,
            Self::D,
            Self::E,
            Self::F,
            Self::G,
            Self::H,
        ]
    }

    #[inline]
    pub const fn new(index: u8) -> Option<Self> {
        if index < 8 {
            Some(unsafe { Self::new_unchecked(index) })
        } else {
            None
        }
    }

    /// **Safety:** The caller must ensure that `index < 8`.
    #[inline]
    pub const unsafe fn new_unchecked(index: u8) -> Self {
        debug_assert!(index < 8);
        unsafe { core::mem::transmute(index) }
    }

    #[inline]
    pub const fn to_ascii(self) -> u8 {
        self as u8 + b'a'
    }

    #[inline]
    pub const fn from_ascii(file: u8) -> Option<Self> {
        if file <= b'h' {
            match file.checked_sub(b'a') {
                Some(i) => Some(unsafe { Self::new_unchecked(i) }),
                None => None,
            }
        } else {
            None
        }
    }

    #[inline]
    pub const fn to_char(self) -> char {
        self.to_ascii() as char
    }

    #[inline]
    pub const fn from_char(file: char) -> Option<Self> {
        if file as u32 <= 255 {
            Self::from_ascii(file as u8)
        } else {
            None
        }
    }

    #[inline]
    pub const fn bitboard(self) -> Bitboard {
        Bitboard(0x0101010101010101 << (self as u8))
    }
}

impl core::fmt::Display for File {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "{}", self.to_char())
    }
}

/// A row of the board.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Rank {
    First,
    Second,
    Third,
    Fourth,
    Fifth,
    Sixth,
    Seventh,
    Eighth,
}

container!(pub(crate), Rank, ByRank, 8);

impl Rank {
    #[inline]
    pub const fn all() -> [Self; 8] {
        [
            Self::First,
            Self::Second,
            Self::Third,
            Self::Fourth,
            Self::Fifth,
            Self::Sixth,
            Self::Seventh,
            Self::Eighth,
        ]
    }

    #[inline]
    pub const fn new(index: u8) -> Option<Self> {
        if index < 8 {
            Some(unsafe { Self::new_unchecked(index) })
        } else {
            None
        }
    }

    /// **Safety:** The caller must ensure that `index < 8`.
    #[inline]
    pub const unsafe fn new_unchecked(index: u8) -> Self {
        debug_assert!(index < 8);
        unsafe { core::mem::transmute(index) }
    }

    #[inline]
    pub const fn to_ascii(self) -> u8 {
        self as u8 + b'1'
    }

    #[inline]
    pub const fn from_ascii(rank: u8) -> Option<Self> {
        if rank <= b'8' {
            match rank.checked_sub(b'1') {
                Some(i) => Some(unsafe { Self::new_unchecked(i) }),
                None => None,
            }
        } else {
            None
        }
    }

    #[inline]
    pub const fn to_char(self) -> char {
        self.to_ascii() as char
    }

    #[inline]
    pub const fn from_char(rank: char) -> Option<Self> {
        if rank as u32 <= 255 {
            Self::from_ascii(rank as u8)
        } else {
            None
        }
    }

    #[inline]
    pub const fn bitboard(self) -> Bitboard {
        Bitboard(0xFF << ((self as u64) << 3))
    }

    #[inline]
    pub const fn mirror(self) -> Self {
        unsafe { Self::new_unchecked(!(self as u8)) }
    }
}

impl core::fmt::Display for Rank {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "{}", self.to_char())
    }
}

/// A square of the board.
#[rustfmt::skip]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Square{
    A1, B1, C1, D1, E1, F1, G1, H1,
    A2, B2, C2, D2, E2, F2, G2, H2,
    A3, B3, C3, D3, E3, F3, G3, H3,
    A4, B4, C4, D4, E4, F4, G4, H4,
    A5, B5, C5, D5, E5, F5, G5, H5,
    A6, B6, C6, D6, E6, F6, G6, H6,
    A7, B7, C7, D7, E7, F7, G7, H7,
    A8, B8, C8, D8, E8, F8, G8, H8,
}

container!(pub, Square, BySquare, 64);

impl Square {
    #[inline]
    #[rustfmt::skip]
    pub const fn all() -> [Self; 64] {
        [
            Self::A1, Self::B1, Self::C1, Self::D1, Self::E1, Self::F1, Self::G1, Self::H1,
            Self::A2, Self::B2, Self::C2, Self::D2, Self::E2, Self::F2, Self::G2, Self::H2,
            Self::A3, Self::B3, Self::C3, Self::D3, Self::E3, Self::F3, Self::G3, Self::H3,
            Self::A4, Self::B4, Self::C4, Self::D4, Self::E4, Self::F4, Self::G4, Self::H4,
            Self::A5, Self::B5, Self::C5, Self::D5, Self::E5, Self::F5, Self::G5, Self::H5,
            Self::A6, Self::B6, Self::C6, Self::D6, Self::E6, Self::F6, Self::G6, Self::H6,
            Self::A7, Self::B7, Self::C7, Self::D7, Self::E7, Self::F7, Self::G7, Self::H7,
            Self::A8, Self::B8, Self::C8, Self::D8, Self::E8, Self::F8, Self::G8, Self::H8,
        ]
    }

    pub const fn new(index: u8) -> Option<Self> {
        if index < 64 {
            Some(unsafe { Self::new_unchecked(index) })
        } else {
            None
        }
    }

    /// **Safety:** The caller must ensure that `index < 64`.
    #[inline]
    pub const unsafe fn new_unchecked(index: u8) -> Self {
        debug_assert!(index < 64);
        unsafe { core::mem::transmute(index) }
    }

    #[inline]
    pub const fn from_coords(file: File, rank: Rank) -> Self {
        unsafe { Self::new_unchecked(((rank as u8) << 3) | file as u8) }
    }

    #[inline]
    pub const fn file(self) -> File {
        unsafe { File::new_unchecked((self as u8) & 7) }
    }

    #[inline]
    pub const fn rank(self) -> Rank {
        unsafe { Rank::new_unchecked((self as u8) >> 3) }
    }

    #[inline]
    pub const fn bitboard(self) -> Bitboard {
        Bitboard(1 << self as u8)
    }

    #[inline]
    pub const fn mirror(self) -> Self {
        let sq = self as u8;
        unsafe { Self::new_unchecked(sq & 0b000111 | (!sq & 0b111000)) }
    }

    #[inline]
    pub(crate) const fn trans(self, direction: Direction) -> Option<Self> {
        if self.check_trans(direction) {
            Some(unsafe { self.trans_unchecked(direction) })
        } else {
            None
        }
    }

    /// SAFETY: the translation must not move the square outside the board
    #[inline]
    pub(crate) const unsafe fn trans_unchecked(self, direction: Direction) -> Self {
        debug_assert!(self.check_trans(direction));
        let i = self as u8;
        unsafe {
            Self::new_unchecked(match direction {
                Direction::East => i.unchecked_add(1),
                Direction::NorthEast => i.unchecked_add(9),
                Direction::North => i.unchecked_add(8),
                Direction::NorthWest => i.unchecked_add(7),
                Direction::SouthEast => i.unchecked_sub(7),
                Direction::South => i.unchecked_sub(8),
                Direction::SouthWest => i.unchecked_sub(9),
                Direction::West => i.unchecked_sub(1),
            })
        }
    }

    /// Returns `false` if the translation would move the square outside the board
    #[inline]
    const fn check_trans(self, direction: Direction) -> bool {
        let file = self.file() as u8;
        let rank = self.rank() as u8;
        match direction {
            Direction::East => file < 7,
            Direction::NorthEast => file < 7 && rank < 7,
            Direction::North => rank < 7,
            Direction::NorthWest => file > 0 && rank < 7,
            Direction::SouthEast => file < 7 && rank > 0,
            Direction::South => rank > 0,
            Direction::SouthWest => file > 0 && rank > 0,
            Direction::West => file > 0,
        }
    }
}

impl core::fmt::Display for Square {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        use core::fmt::Write;
        f.write_char(self.file().to_char())?;
        f.write_char(self.rank().to_char())?;
        Ok(())
    }
}

/// An error while parsing a [`Square`].
#[derive(Debug)]
pub struct ParseSquareError;
impl core::fmt::Display for ParseSquareError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("invalid square syntax")
    }
}
impl core::error::Error for ParseSquareError {}

impl core::str::FromStr for Square {
    type Err = ParseSquareError;
    #[inline]
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        (|| match s.as_bytes() {
            [f, r] => Some(Self::from_coords(
                File::from_ascii(*f)?,
                Rank::from_ascii(*r)?,
            )),
            _ => None,
        })()
        .ok_or(ParseSquareError)
    }
}

#[rustfmt::skip]
#[allow(unused)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub(crate) enum OptionSquare {
    A1, B1, C1, D1, E1, F1, G1, H1,
    A2, B2, C2, D2, E2, F2, G2, H2,
    A3, B3, C3, D3, E3, F3, G3, H3,
    A4, B4, C4, D4, E4, F4, G4, H4,
    A5, B5, C5, D5, E5, F5, G5, H5,
    A6, B6, C6, D6, E6, F6, G6, H6,
    A7, B7, C7, D7, E7, F7, G7, H7,
    A8, B8, C8, D8, E8, F8, G8, H8,
    None,
}

impl OptionSquare {
    #[inline]
    pub(crate) fn new(square: Option<Square>) -> OptionSquare {
        match square {
            Some(square) => Self::from_square(square),
            None => Self::None,
        }
    }

    #[inline]
    pub(crate) fn try_into_square(self) -> Option<Square> {
        unsafe {
            match self {
                Self::None => None,
                _ => Some(Square::new_unchecked(self as u8)),
            }
        }
    }

    #[inline]
    pub(crate) fn from_square(square: Square) -> Self {
        unsafe { core::mem::transmute(square) }
    }

    #[inline]
    pub(crate) fn bitboard(&self) -> Bitboard {
        Bitboard(1 << (*self as u8))
    }
}

/// A type of piece.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Role {
    Pawn = 1,
    Knight,
    Bishop,
    Rook,
    Queen,
    King,
}

impl Role {
    #[inline]
    pub const fn all() -> [Self; 6] {
        [
            Self::Pawn,
            Self::Knight,
            Self::Bishop,
            Self::Rook,
            Self::Queen,
            Self::King,
        ]
    }

    #[inline]
    pub(crate) fn to_char_uppercase(self) -> char {
        match self {
            Self::Pawn => 'P',
            Self::Knight => 'N',
            Self::Bishop => 'B',
            Self::Rook => 'R',
            Self::Queen => 'Q',
            Self::King => 'K',
        }
    }

    #[inline]
    pub(crate) fn to_char_lowercase(self) -> char {
        match self {
            Self::Pawn => 'p',
            Self::Knight => 'n',
            Self::Bishop => 'b',
            Self::Rook => 'r',
            Self::Queen => 'q',
            Self::King => 'k',
        }
    }

    #[inline]
    pub(crate) fn from_ascii(x: u8) -> Option<Self> {
        Some(match x {
            b'p' | b'P' => Self::Pawn,
            b'n' | b'N' => Self::Knight,
            b'b' | b'B' => Self::Bishop,
            b'r' | b'R' => Self::Rook,
            b'q' | b'Q' => Self::Queen,
            b'k' | b'K' => Self::King,
            _ => return None,
        })
    }

    #[inline]
    pub(crate) unsafe fn transmute(i: u8) -> Self {
        debug_assert!(i >= 1 && i < 7);
        unsafe { core::mem::transmute(i) }
    }
}

/// Container with values for each [`Role`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ByRole<T>(pub(crate) [T; 6]);
#[allow(unused)]
impl<T> ByRole<T> {
    #[inline]
    pub const fn new(values: [T; 6]) -> Self {
        Self(values)
    }

    #[inline]
    pub const fn get(&self, k: Role) -> &T {
        let i = unsafe { (k as usize).unchecked_sub(1) };
        unsafe { core::hint::assert_unchecked(i < 6) };
        &self.0[i]
    }

    #[inline]
    pub const fn get_mut(&mut self, k: Role) -> &mut T {
        let i = unsafe { (k as usize).unchecked_sub(1) };
        unsafe { core::hint::assert_unchecked(i < 6) };
        &mut self.0[i]
    }

    #[inline]
    pub fn with<F>(f: F) -> Self
    where
        F: FnMut(Role) -> T,
    {
        Self(Role::all().map(f))
    }
}

impl<T> ByRole<T>
where
    T: Copy,
{
    #[inline]
    pub(crate) fn pawn(&self) -> T {
        *self.get(Role::Pawn)
    }
    #[inline]
    pub(crate) fn knight(&self) -> T {
        *self.get(Role::Knight)
    }
    #[inline]
    pub(crate) fn bishop(&self) -> T {
        *self.get(Role::Bishop)
    }
    #[inline]
    pub(crate) fn rook(&self) -> T {
        *self.get(Role::Rook)
    }
    #[inline]
    pub(crate) fn queen(&self) -> T {
        *self.get(Role::Queen)
    }
    #[inline]
    pub(crate) fn king(&self) -> T {
        *self.get(Role::King)
    }
}

/// A chess piece (i.e. its [`Role`] and [`Color`]).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Piece {
    pub role: Role,
    pub color: Color,
}

#[derive(Clone, Copy)]
#[repr(u8)]
pub(crate) enum Direction {
    East,
    NorthEast,
    North,
    NorthWest,
    SouthEast,
    South,
    SouthWest,
    West,
}

container!(pub(crate), Direction, ByDirection, 8);

impl Direction {
    #[inline]
    pub fn all() -> [Self; 8] {
        [
            Self::East,
            Self::NorthEast,
            Self::North,
            Self::NorthWest,
            Self::SouthEast,
            Self::South,
            Self::SouthWest,
            Self::West,
        ]
    }

    #[inline]
    pub(crate) const fn from_index(index: u8) -> Option<Self> {
        if index < 8 {
            Some(unsafe { Self::transmute(index) })
        } else {
            None
        }
    }

    #[inline]
    const unsafe fn transmute(value: u8) -> Self {
        debug_assert!(value < 8);
        unsafe { core::mem::transmute(value) }
    }
}

impl core::ops::Not for Direction {
    type Output = Self;
    #[inline]
    fn not(self) -> Self::Output {
        unsafe { Self::transmute(self as u8 ^ 0b111) }
    }
}

/// A side of the board.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum CastlingSide {
    /// King's side
    Short,
    /// Queen's side
    Long,
}

container!(pub, CastlingSide, ByCastlingSide, 2);

impl CastlingSide {
    #[inline]
    pub fn all() -> [Self; 2] {
        [Self::Short, Self::Long]
    }

    #[inline]
    pub(crate) fn rook_origin_file(self) -> File {
        match self {
            Self::Short => File::H,
            Self::Long => File::A,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct CastlingRights(u8);

impl CastlingRights {
    #[inline]
    pub(crate) fn new() -> Self {
        Self(0)
    }

    #[inline]
    pub(crate) const fn full() -> Self {
        Self(15)
    }

    #[inline]
    pub(crate) fn get(&self, color: Color, side: CastlingSide) -> bool {
        (self.0 & Self::mask(color, side)) != 0
    }

    #[inline]
    pub(crate) fn set(&mut self, color: Color, side: CastlingSide) {
        self.0 |= Self::mask(color, side);
    }

    #[inline]
    pub(crate) fn unset(&mut self, color: Color, side: CastlingSide) {
        self.0 &= !Self::mask(color, side);
    }

    #[inline]
    pub(crate) fn mirror(&self) -> Self {
        Self(((self.0 & 3) << 2) | (self.0 >> 2))
    }

    #[inline]
    const fn mask(color: Color, side: CastlingSide) -> u8 {
        match (color, side) {
            (Color::White, CastlingSide::Short) => 1,
            (Color::White, CastlingSide::Long) => 2,
            (Color::Black, CastlingSide::Short) => 4,
            (Color::Black, CastlingSide::Long) => 8,
        }
    }
}
