Documentation
use std::{fmt::Display, str::FromStr};

use crate::{piece::PieceType, Bitboard, Color, Square};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
pub struct Move {
    pub from: Square,
    pub to: Square,
    pub piece: PieceType,
    pub capture: Option<PieceType>,
    pub move_type: MoveType,
}

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
pub enum CastlingSide {
    KingSide,
    QueenSide,
    Both,
}

impl Move {
    pub fn new(
        from: Square,
        to: Square,
        piece: PieceType,
        capture: Option<PieceType>,
        move_type: MoveType,
    ) -> Self {
        Self {
            from,
            to,
            piece,
            capture,
            move_type,
        }
    }
    pub fn update_position(
        &self,
        white_pieces: &mut [Bitboard; 6],
        black_pieces: &mut [Bitboard; 6],
        side_to_move: Color,
    ) {
        let from_bb = Bitboard(1_u64 << self.from.0);
        let to_bb = Bitboard(1_u64 << self.to.0);
        let from_to_bb = from_bb ^ to_bb;

        match self.move_type {
            MoveType::Quiet => {
                match side_to_move {
                    Color::White => white_pieces[self.piece as usize] ^= from_to_bb,
                    Color::Black => black_pieces[self.piece as usize] ^= from_to_bb,
                }

                if let Some(piece) = self.capture {
                    match side_to_move.opposite() {
                        Color::White => white_pieces[piece as usize] ^= to_bb,
                        Color::Black => black_pieces[piece as usize] ^= to_bb,
                    }
                }
            }
            MoveType::Promotion { promotion_to } => {
                assert!(promotion_to != PieceType::King);
                match side_to_move {
                    Color::White => {
                        white_pieces[self.piece as usize] ^= from_bb;
                        white_pieces[promotion_to as usize] ^= to_bb;
                    }
                    Color::Black => {
                        black_pieces[self.piece as usize] ^= from_bb;
                        black_pieces[promotion_to as usize] ^= to_bb;
                    }
                }

                if let Some(piece) = self.capture {
                    match side_to_move.opposite() {
                        Color::White => white_pieces[piece as usize] ^= to_bb,
                        Color::Black => black_pieces[piece as usize] ^= to_bb,
                    }
                }
            }
            MoveType::EnPassant { captures_on } => {
                match side_to_move {
                    Color::White => white_pieces[self.piece as usize] ^= from_to_bb,
                    Color::Black => black_pieces[self.piece as usize] ^= from_to_bb,
                }

                match side_to_move.opposite() {
                    Color::White => {
                        white_pieces[self.piece as usize] ^= Bitboard::from_square(captures_on)
                    }
                    Color::Black => {
                        black_pieces[self.piece as usize] ^= Bitboard::from_square(captures_on)
                    }
                }
            }
            MoveType::Castling { side } => match side_to_move {
                Color::White => {
                    white_pieces[self.piece as usize] ^= from_to_bb;
                    match side {
                        CastlingSide::KingSide => {
                            let rook = Bitboard::from_square(Square::from_str("h1").unwrap())
                                | Bitboard::from_square(Square::from_str("f1").unwrap());
                            white_pieces[PieceType::Rook as usize] ^= rook;
                        }
                        CastlingSide::QueenSide => {
                            let rook = Bitboard::from_square(Square::from_str("a1").unwrap())
                                | Bitboard::from_square(Square::from_str("d1").unwrap());
                            white_pieces[PieceType::Rook as usize] ^= rook;
                        }
                        CastlingSide::Both => unreachable!(),
                    }
                }
                Color::Black => {
                    black_pieces[self.piece as usize] ^= from_to_bb;
                    match side {
                        CastlingSide::KingSide => {
                            let rook = Bitboard::from_square(Square::from_str("h8").unwrap())
                                | Bitboard::from_square(Square::from_str("f8").unwrap());
                            black_pieces[PieceType::Rook as usize] ^= rook;
                        }
                        CastlingSide::QueenSide => {
                            let rook = Bitboard::from_square(Square::from_str("a8").unwrap())
                                | Bitboard::from_square(Square::from_str("d8").unwrap());
                            black_pieces[PieceType::Rook as usize] ^= rook;
                        }
                        CastlingSide::Both => unreachable!(),
                    }
                }
            },
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
pub enum MoveType {
    Quiet,
    EnPassant { captures_on: Square },
    Castling { side: CastlingSide },
    Promotion { promotion_to: PieceType },
}

impl Display for Move {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let MoveType::Castling { side } = self.move_type {
            match side {
                CastlingSide::KingSide => {
                    write!(f, "O-O")?;
                }
                CastlingSide::QueenSide => {
                    write!(f, "O-O-O")?;
                }
                CastlingSide::Both => unreachable!(),
            }
        } else {
            let piece = if self.piece == PieceType::Pawn {
                "".to_string()
            } else {
                format!("{}", self.piece)
            };
            let capture = if self.capture.is_some() {
                if self.piece == PieceType::Pawn {
                    format!("{}x", self.from.file())
                } else {
                    "x".to_string()
                }
            } else {
                "".to_string()
            };
            let promotion = if let MoveType::Promotion { promotion_to } = self.move_type {
                format!("={}", promotion_to)
            } else {
                "".to_string()
            };

            write!(f, "{}{}{}{}", piece, capture, self.to, promotion)?;
        }

        Ok(())
    }
}