Documentation
use std::ops::{BitOr, Not};

use strum::{EnumCount, EnumIter};

use self::Colour as C;
use self::Piece as P;
use self::UncolouredPiece as U;
use crate::error::Error;

#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, EnumCount)]
pub enum Piece {
    BlackKing,
    BlackQueen,
    BlackRook,
    BlackBishop,
    BlackKnight,
    BlackPawn,
    WhiteKing,
    WhiteQueen,
    WhiteRook,
    WhiteBishop,
    WhiteKnight,
    WhitePawn,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Colour {
    Black,
    White,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UncolouredPiece {
    King,
    Queen,
    Rook,
    Bishop,
    Knight,
    Pawn,
}

impl Piece {
    pub fn colour(self) -> Colour {
        match self {
            P::BlackKing
            | P::BlackQueen
            | P::BlackRook
            | P::BlackBishop
            | P::BlackKnight
            | P::BlackPawn => Colour::Black,
            P::WhiteKing
            | P::WhiteQueen
            | P::WhiteRook
            | P::WhiteBishop
            | P::WhiteKnight
            | P::WhitePawn => Colour::White,
        }
    }

    pub fn piece(self) -> UncolouredPiece {
        match self {
            P::BlackKing | P::WhiteKing => U::King,
            P::BlackQueen | P::WhiteQueen => U::Queen,
            P::BlackRook | P::WhiteRook => U::Rook,
            P::BlackBishop | P::WhiteBishop => U::Bishop,
            P::BlackKnight | P::WhiteKnight => U::Knight,
            P::BlackPawn | P::WhitePawn => U::Pawn,
        }
    }

    pub fn as_index(self) -> usize {
        self as usize
    }
}

impl TryFrom<char> for Piece {
    type Error = Error;

    fn try_from(value: char) -> Result<Self, Self::Error> {
        match value {
            'k' => Ok(P::BlackKing),
            'q' => Ok(P::BlackQueen),
            'r' => Ok(P::BlackRook),
            'b' => Ok(P::BlackBishop),
            'n' => Ok(P::BlackKnight),
            'p' => Ok(P::BlackPawn),
            'K' => Ok(P::WhiteKing),
            'Q' => Ok(P::WhiteQueen),
            'R' => Ok(P::WhiteRook),
            'B' => Ok(P::WhiteBishop),
            'N' => Ok(P::WhiteKnight),
            'P' => Ok(P::WhitePawn),
            _ => Err(Error::InvalidPiece(value)),
        }
    }
}

impl From<Piece> for char {
    fn from(value: Piece) -> Self {
        match value {
            P::BlackKing => 'k',
            P::BlackQueen => 'q',
            P::BlackRook => 'r',
            P::BlackBishop => 'b',
            P::BlackKnight => 'n',
            P::BlackPawn => 'p',
            P::WhiteKing => 'K',
            P::WhiteQueen => 'Q',
            P::WhiteRook => 'R',
            P::WhiteBishop => 'B',
            P::WhiteKnight => 'N',
            P::WhitePawn => 'P',
        }
    }
}

impl BitOr<UncolouredPiece> for Colour {
    type Output = Piece;

    fn bitor(self, rhs: UncolouredPiece) -> Self::Output {
        match (self, rhs) {
            (C::Black, U::King) => P::BlackKing,
            (C::Black, U::Queen) => P::BlackQueen,
            (C::Black, U::Rook) => P::BlackRook,
            (C::Black, U::Bishop) => P::BlackBishop,
            (C::Black, U::Knight) => P::BlackKnight,
            (C::Black, U::Pawn) => P::BlackPawn,
            (C::White, U::King) => P::WhiteKing,
            (C::White, U::Queen) => P::WhiteQueen,
            (C::White, U::Rook) => P::WhiteRook,
            (C::White, U::Bishop) => P::WhiteBishop,
            (C::White, U::Knight) => P::WhiteKnight,
            (C::White, U::Pawn) => P::WhitePawn,
        }
    }
}

impl Not for Colour {
    type Output = Self;

    fn not(self) -> Self::Output {
        match self {
            Self::Black => Self::White,
            Self::White => Self::Black,
        }
    }
}