//
// jja: swiss army knife for chess file formats
// src/chess.rs: Chess logic
//
// Copyright (c) 2023 Ali Polatel <alip@chesswob.org>
// Based in part upon ChessX's src/database/*.cpp which is:
//     Copyright (C) 2015, 2017 by Jens Nissen jens-chessx@gmx.net
// Based in part upon chess-tactics-cli in get_board_lines(), piece_ascii() functions which is:
//     Copyright Marcus Bufett, distributed under the MIT license
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{collections::BTreeMap, ffi::OsStr, fmt::Write, num::NonZeroU32};

use console::style;
use prettytable::{Cell, Row, Table};
use shakmaty::{
    fen::Epd, san::San, uci::Uci, Bitboard, Board, CastlingMode, CastlingSide, Chess, Color,
    EnPassantMode, File, FromSetup, Piece, Position, PositionError, Rank, Role, Setup, Square,
};
use termtree::Tree;

use crate::{built_info, tr};

/// A constant representing the initial FEN string for a standard chess game.
pub const ROOT: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -";

/// Represents a chess move as a UCI (Universal Chess Interface) string.
#[derive(Clone, Debug)]
pub struct ChessMove(pub Uci);

/// Implements a `ValueParserFactory` for the `ChessMove` type.
impl clap::builder::ValueParserFactory for ChessMove {
    type Parser = ChessMoveValueParser;
    fn value_parser() -> Self::Parser {
        ChessMoveValueParser
    }
}

/// Provides a value parser to parse a UCI chess move from command line arguments.
#[derive(Clone, Debug)]
pub struct ChessMoveValueParser;

/// Implements the `TypedValueParser` trait for `ChessMoveValueParser`.
impl clap::builder::TypedValueParser for ChessMoveValueParser {
    type Value = ChessMove;

    ///
    /// # Arguments
    ///
    /// * `cmd` - A reference to the `clap::Command`.
    /// * `_arg` - An optional reference to the `clap::Arg`.
    /// * `value` - A reference to the `OsStr` value containing the UCI move to parse.
    ///
    /// # Returns
    ///
    /// * `Result<Self::Value, clap::Error>` - Returns a `ChessMove` if the parsing is successful,
    /// otherwise returns a `clap::Error`.
    fn parse_ref(
        &self,
        cmd: &clap::Command,
        _arg: Option<&clap::Arg>,
        value: &OsStr,
    ) -> Result<Self::Value, clap::Error> {
        let value_str = value.to_str().ok_or_else(|| {
            let mut cmd = cmd.clone();
            cmd.error(
                clap::error::ErrorKind::InvalidValue,
                tr!("Unable to parse UCI move"),
            )
        })?;

        let uci = Uci::from_ascii(value_str.as_bytes()).map_err(|_| {
            let mut cmd = cmd.clone();
            cmd.error(
                clap::error::ErrorKind::InvalidValue,
                tr!("Invalid UCI move"),
            )
        })?;

        Ok(ChessMove(uci))
    }
}

/// Serialize a `Chess` object into an array of 5 `u64` integers.
///
/// Each `u64` integer stores the information of 16 squares on the chess board.
/// For each square, 4 bits are allocated. The value of these 4 bits represent the
/// type and color of the piece (if any) on the square:
///
/// - 0 means no piece
/// - 1 for a black pawn
/// - 2 for a white pawn
/// - 3 for a black knight
/// - 4 for a white knight
/// - 5 for a black bishop
/// - 6 for a white bishop
/// - 7 for a black rook
/// - 8 for a white rook
/// - 9 for a black queen
/// - 10 for a white queen
/// - 11 for a black king
/// - 12 for a white king
///
/// The last `u64` in the array also stores the turn (bit 0), the castling rights
/// (bits 1 to 4, in the order of white kingside, white queenside, black kingside,
/// black queenside, with 1 meaning the right still exists), and the en passant
/// target square file index (bits 5 to 8, 0 means no en passant square, 1-8 are for
/// files 'a' to 'h' respectively).
///
/// # Arguments
///
/// * `chess` - A `shakmaty::Chess` object representing the current state of a chess game.
///
/// # Returns
/// * An array of five `u64` numbers, where:
///
///   * The first four numbers represent the 64 squares of the chessboard.
///   * The fifth number represents the turn, castling rights and the en passant target square.
pub fn serialize_chess(chess: &Chess) -> [u64; 5] {
    let mut arr = [0u64; 5];

    // A lookup table to map a (Color, Role) to its value
    static PIECE_VALUES: [[u64; 7]; 2] = [
        [0, 1, 3, 5, 7, 9, 11],  // Black
        [0, 2, 4, 6, 8, 10, 12], // White
    ];

    let board = chess.board();
    for square in board.occupied() {
        // SAFETY: Square is in occupied bitboard, piece_at must return Some.
        let piece = unsafe { board.piece_at(square).unwrap_unchecked() };
        let piece_val = PIECE_VALUES[piece.color as usize][piece.role as usize];
        let arr_index = u8::from(square) / 16;
        let bit_offset = (u8::from(square) % 16) * 4;
        arr[arr_index as usize] |= piece_val << bit_offset;
    }

    /*
    eprintln!(
        "turn: {:?}, castles: {:?}, serialize: {:?}",
        chess.turn(),
        u64::from(chess.castles().castling_rights()),
        chess.ep_square(EnPassantMode::Legal)
    );
    */

    // Serialize the turn, castling rights, and en passant square
    let turn_bit = u64::from(chess.turn() == Color::Black);

    let castles = chess.castles();
    let castling_bits = if castles.has(Color::White, CastlingSide::KingSide) {
        1 << 1
    } else {
        0
    } | if castles.has(Color::White, CastlingSide::QueenSide) {
        1 << 2
    } else {
        0
    } | if castles.has(Color::Black, CastlingSide::KingSide) {
        1 << 3
    } else {
        0
    } | if castles.has(Color::Black, CastlingSide::QueenSide) {
        1 << 4
    } else {
        0
    };

    let ep_square = chess.ep_square(EnPassantMode::Legal);
    let ep_bits = ep_square.map_or(0, |sq| (sq.file().char() as u64 - 'a' as u64 + 1) << 5);

    arr[4] = turn_bit | castling_bits | ep_bits;

    arr
}

/// Deserialize a `Chess` object from an array of 5 `u64` integers.
///
/// Each `u64` integer represents the information of 16 squares on the chess board.
/// For each square, 4 bits are allocated. The value of these 4 bits represent the
/// type and color of the piece (if any) on the square, according to the same scheme
/// used in `serialize_chess`.
///
/// The last `u64` in the array also contains the turn (bit 0), the castling rights
/// (bits 1 to 4, in the order of white kingside, white queenside, black kingside,
/// black queenside, with 1 meaning the right still exists), and the en passant
/// target square file index (bits 5 to 8, 0 means no en passant square, 1-8 are for
/// files 'a' to 'h' respectively).
///
/// # Panics
///
/// This function panics upon encountering an undefined piece index in the array.
///
/// # Errors
///
/// If the input array cannot be properly converted into a `Chess` object,
/// for example, if there are invalid positions like two kings on one square,
/// the function will return an `Err(PositionError<Chess>)`.
///
/// # Arguments
///
/// * `arr` - An array of five `u64` numbers where:
///
///   * The first four numbers represent the 64 squares of the chessboard.
///   * The fifth number represents the castling rights and the en passant target square.
///
/// # Returns
/// * A `shakmaty::Chess` object representing the current state of a chess game.
pub fn deserialize_chess(arr: [u64; 5]) -> Result<Chess, PositionError<Chess>> {
    let mut board = Board::empty();

    for rank in 0..8 {
        for file in 0..8 {
            let square_index = rank * 8 + file;
            // SAFETY: Loop bounds guarantee square index is within bounds 0..=63.
            let square = unsafe { Square::new_unchecked(square_index) };
            let arr_index = square_index / 16;
            let bit_offset = (square_index % 16) * 4;
            let piece_val = ((arr[arr_index as usize] >> bit_offset) & 0xF) as u8;
            let piece = match piece_val {
                0 => {
                    continue;
                }
                1 => Piece {
                    color: Color::Black,
                    role: Role::Pawn,
                },
                2 => Piece {
                    color: Color::White,
                    role: Role::Pawn,
                },
                3 => Piece {
                    color: Color::Black,
                    role: Role::Knight,
                },
                4 => Piece {
                    color: Color::White,
                    role: Role::Knight,
                },
                5 => Piece {
                    color: Color::Black,
                    role: Role::Bishop,
                },
                6 => Piece {
                    color: Color::White,
                    role: Role::Bishop,
                },
                7 => Piece {
                    color: Color::Black,
                    role: Role::Rook,
                },
                8 => Piece {
                    color: Color::White,
                    role: Role::Rook,
                },
                9 => Piece {
                    color: Color::Black,
                    role: Role::Queen,
                },
                10 => Piece {
                    color: Color::White,
                    role: Role::Queen,
                },
                11 => Piece {
                    color: Color::Black,
                    role: Role::King,
                },
                12 => Piece {
                    color: Color::White,
                    role: Role::King,
                },
                n => {
                    panic!(
                        "{}",
                        tr!(
                            "Invalid piece index {} while deserializing {}. Please report a bug!",
                            n,
                            format!("{:?}", arr)
                        )
                    );
                }
            };
            board.set_piece_at(square, piece);
        }
    }

    // Deserialize the turn, castling rights, and en passant square
    let turn_bit = arr[4] & 1;
    let turn = if turn_bit == 1 {
        Color::Black
    } else {
        Color::White
    };

    let castling_bits = (arr[4] >> 1) & 0xF;
    let wrooks: Bitboard = board
        .by_piece(Piece {
            color: Color::White,
            role: Role::Rook,
        })
        .into_iter()
        .filter(|&square| square.rank() == Rank::First)
        .collect();
    let brooks: Bitboard = board
        .by_piece(Piece {
            color: Color::Black,
            role: Role::Rook,
        })
        .into_iter()
        .filter(|&square| square.rank() == Rank::Eighth)
        .collect();
    let mut castling_rights = Bitboard::EMPTY;
    if (castling_bits & 1) != 0 {
        /* White kingside castling */
        castling_rights |= wrooks
            .into_iter()
            .max_by_key(|&square| square.file())
            .expect("missing white kingside rook");
    }
    if (castling_bits & 2) != 0 {
        /* White queenside castling */
        castling_rights |= wrooks
            .into_iter()
            .min_by_key(|&square| square.file())
            .expect("missing white queenside rook");
    }
    if (castling_bits & 4) != 0 {
        /* Black kingside castling */
        castling_rights |= brooks
            .into_iter()
            .max_by_key(|&square| square.file())
            .expect("missing black kingside rook");
    }
    if (castling_bits & 8) != 0 {
        /* Black queenside castling */
        castling_rights |= brooks
            .into_iter()
            .min_by_key(|&square| square.file())
            .expect("missing black queenside rook");
    }

    let ep_file = ((arr[4] >> 5) & 0xF) as u32;
    let ep_rank = match turn {
        Color::Black => Rank::Third,
        Color::White => Rank::Sixth,
    };
    let ep_square = if ep_file > 0 && ep_file <= 8 {
        Some(Square::from_coords(File::new(ep_file - 1), ep_rank))
    } else {
        None
    };

    let setup = Setup {
        board,
        promoted: Bitboard::EMPTY, // No promoted pieces in standard chess
        pockets: None,             // No pockets in standard chess
        turn,
        castling_rights,
        ep_square,
        remaining_checks: None, // No remaining checks in standard chess
        halfmoves: 0,           // Reset the halfmove clock
        fullmoves: NonZeroU32::new(1).unwrap(), // Assume we're at the first move
    };

    Chess::from_setup(setup, CastlingMode::Chess960)
}

/// Generates a table containing opening lines from a given tree of opening moves.
///
/// The `tree` parameter is a `Tree<String>` representing the opening moves, where
/// each node in the tree corresponds to a single move.
///
/// The `max_ply` parameter determines the maximum number of plies (half-moves) to include
/// in the opening lines.
///
/// # Returns
///
/// * Returns a `Table`.
pub fn lines_from_tree(tree: &Tree<String>, max_ply: u16) -> Table {
    let mut table = Table::new();

    // Set the table headers
    table.set_titles(Row::new(vec![Cell::new(&tree.root)]));

    let is_white_to_move = tree.root.contains(" w ");

    // Traverse the tree and generate the table rows
    fn traverse_tree(
        tree: &Tree<String>,
        table: &mut Table,
        path: &mut Vec<String>,
        max_ply: u16,
        is_white_to_move: bool,
    ) {
        if path.len() == max_ply as usize * 2 || tree.leaves.is_empty() {
            if !path.is_empty() {
                let mut opening = String::with_capacity(256);
                let offset = if is_white_to_move { 0 } else { 1 };
                if !is_white_to_move {
                    opening.push_str("1. ... ");
                }
                for (i, move_str) in path.iter().enumerate() {
                    if (i + offset) % 2 == 0 {
                        write!(&mut opening, "{}. ", (i + offset) / 2 + 1).unwrap();
                    }
                    write!(&mut opening, "{} ", move_str).unwrap();
                }

                table.add_row(Row::new(vec![Cell::new(opening.trim())]));
            }
        } else {
            for child in &tree.leaves {
                path.push(child.root.clone());
                traverse_tree(child, table, path, max_ply, is_white_to_move);
                path.pop();
            }
        }
    }

    traverse_tree(tree, &mut table, &mut vec![], max_ply, is_white_to_move);

    table
}

/// Generates a PGN file from the given opening tree.
///
/// The `tree` argument is the tree containing opening variations.
/// The `output` argument is a mutable reference to a boxed `Write` trait object where the generated PGN will be written.
/// The `event`, `site`, `date`, `white`, `black`, and `result` arguments are used to fill in the corresponding PGN tags for each game.
/// The `max_ply` argument limits the depth of variations to be considered.
///
/// If `tree.root` is equal to `chess::ROOT`, which is the starting position, the FEN header will not be printed.
/// In other cases, a `[SetUp "1"]` tag will be added along with the FEN and other tags.
#[allow(clippy::too_many_arguments)]
#[deprecated(
    since = "0.7.0",
    note = "Please use the `write_pgn` function of the respective opening book implementation instead"
)]
pub fn pgn_from_tree(
    tree: &Tree<String>,
    output: &mut Box<dyn std::io::Write + Send>,
    event: &str,
    site: &str,
    date: &str,
    white: &str,
    black: &str,
    result: &str,
    max_ply: u16,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut round = 1;
    let root = tree.root.as_str();
    let black_to_move = root.contains(" b ");

    #[allow(clippy::too_many_arguments)]
    fn traverse_tree(
        root: &str,
        tree: &Tree<String>,
        output: &mut Box<dyn std::io::Write + Send>,
        path: &mut Vec<String>,
        max_ply: u16,
        round: &mut usize,
        event: &str,
        site: &str,
        date: &str,
        white: &str,
        black: &str,
        result: &str,
        black_to_move: bool,
    ) {
        if path.len() == max_ply as usize * 2 || tree.leaves.is_empty() {
            if !path.is_empty() {
                let opening = path
                    .iter()
                    .enumerate()
                    .map(|(i, move_text)| {
                        let move_number = (i + (if black_to_move { 1 } else { 0 })) / 2 + 1;
                        if i == 0 || i % 2 == if black_to_move { 1 } else { 0 } {
                            format!(
                                "{}.{} {}",
                                move_number,
                                if black_to_move && i == 0 { " ..." } else { "" },
                                move_text
                            )
                        } else {
                            move_text.to_string()
                        }
                    })
                    .collect::<Vec<String>>()
                    .join(" ");

                writeln!(
                    output,
                    "[Event \"{}\"]\n\
                     [Site \"{}\"]\n\
                     [Date \"{}\"]\n\
                     [Round \"{}\"]\n\
                     [White \"{}\"]\n\
                     [Black \"{}\"]\n\
                     [Result \"{}\"]\n\
                     [Annotator \"{} v{}\"]",
                    event,
                    site,
                    date,
                    round,
                    white,
                    black,
                    result,
                    built_info::PKG_NAME,
                    built_info::PKG_VERSION,
                )
                .unwrap();

                if root != ROOT {
                    writeln!(output, "[FEN \"{} 0 1\"]", root).unwrap();
                    writeln!(output, "[SetUp \"1\"]").unwrap();
                }

                write!(output, "\n{} {}\n\n", opening, result).unwrap();
                *round += 1;
            }
        } else {
            for child in &tree.leaves {
                path.push(child.root.clone());
                traverse_tree(
                    root,
                    child,
                    output,
                    path,
                    max_ply,
                    round,
                    event,
                    site,
                    date,
                    white,
                    black,
                    result,
                    black_to_move,
                );
                path.pop();
            }
        }
    }

    traverse_tree(
        root,
        tree,
        output,
        &mut vec![],
        max_ply,
        &mut round,
        event,
        site,
        date,
        white,
        black,
        result,
        black_to_move,
    );

    Ok(())
}

/// Generates a comment string for editing a book entry based on the given chess position.
///
/// # Arguments
///
/// * `position` - A `Chess` position for which to generate the comment.
/// * `key` - The Zobrist hash of the position (may be a Polyglot zobrist hash, or Stockfish).
///
/// # Returns
///
/// A `String` containing the generated comment.
pub fn edit_comment(position: Chess, key: u64) -> String {
    let epd = format!(
        "{}",
        Epd::from_position(position.clone(), EnPassantMode::PseudoLegal)
    );
    let mut moves = BTreeMap::new();
    for move_ in position.legal_moves() {
        let san = San::from_move(&position, &move_);
        let uci = move_.to_uci(CastlingMode::Standard);

        moves.insert(format!("{}", san), format!("{}", uci));
    }

    let key_prefix = tr!("# key: ");
    let epd_prefix = tr!("# epd: ");
    let mut san_line = tr!("# san: ").to_string();
    let mut uci_line = tr!("# uci: ").to_string();

    for (idx, (san, uci)) in moves.iter().enumerate() {
        if idx != 0 {
            san_line.push_str(", ");
            uci_line.push_str(", ");
        }
        san_line.push_str(san);
        uci_line.push_str(uci);
    }

    format!(
        "{}{:#x}\n{}{}\n{}\n{}",
        key_prefix, key, epd_prefix, epd, san_line, uci_line
    )
}

/// Returns the square with the file mirrored along the center of the board.
///
/// # Arguments
///
/// * `s` - A `Square` to be mirrored.
///
/// # Returns
///
/// A `Square` with the mirrored file.
pub fn square_mirror_file(s: Square) -> Square {
    // SAFETY: argument type guarantees the mirror index is within range 0..=63
    unsafe { Square::new_unchecked(u32::from(s as u8 ^ 0x07)) }
}

/// Returns the file of the king for the side to move in the given position.
///
/// # Arguments
///
/// * `position` - A reference to a `dyn Position` object representing the chess position.
///
/// # Returns
///
/// A `File` representing the file of the king for the side to move.
pub fn king_file(position: &dyn Position) -> File {
    position.board().king_of(position.turn()).unwrap().file()
}

/// Checks if there is an en passant square in the given position.
///
/// # Arguments
///
/// * `position` - A reference to a `dyn Position` object representing the chess position.
///
/// # Returns
///
/// A `bool` indicating if there is an en passant square in the position.
pub fn has_en_passant(position: &dyn Position) -> bool {
    !position.en_passant_moves().is_empty()
}

/// Returns a vector of strings representing the board lines of the given chess position.
///
/// # Arguments
///
/// * position: &Chess - The chess position to generate the board lines for.
/// * color: bool - A boolean flag that indicates whether to use color for the board pieces.
pub fn get_board_lines(position: &Chess, color: bool) -> Vec<String> {
    let board: &Board = position.board();
    let mut board_lines = Vec::new();

    for row in 0..8 {
        let mut line = format!("  {}  ", 8 - row);
        for col in 0..8 {
            let idx = 64 - (row + 1) * 8 + col;
            let square = Square::new(idx);
            let piece = board.piece_at(square);
            let square_is_white = (row + col) % 2 == 0;
            let c = if square_is_white { 140 } else { 80 };
            let mut piece_char = piece
                .map(|p: Piece| {
                    let mut ch = piece_ascii(&p);
                    if color {
                        ch = if p.color == Color::White {
                            style(ch).blue().to_string()
                        } else {
                            style(ch).red().to_string()
                        };
                    }
                    ch
                })
                .unwrap_or("·".to_string());
            if color {
                piece_char = style(piece_char).on_color256(c).to_string();
            }

            let formatted_piece_char = format!("{} ", piece_char);
            line.push_str(&formatted_piece_char);
        }
        board_lines.push(line);
    }

    let bottom_line = format!(
        "     {}",
        (b'a'..=b'h')
            .map(char::from)
            .map(|c| c.to_string())
            .collect::<Vec<String>>()
            .join(" ")
    );
    board_lines.push(bottom_line);

    board_lines
}

fn piece_ascii(piece: &Piece) -> String {
    match (piece.role, piece.color) {
        (shakmaty::Role::Pawn, shakmaty::Color::Black) => "♟︎",
        (shakmaty::Role::Pawn, shakmaty::Color::White) => "♟︎",
        (shakmaty::Role::Knight, shakmaty::Color::Black) => "♞",
        (shakmaty::Role::Knight, shakmaty::Color::White) => "♞",
        (shakmaty::Role::Bishop, shakmaty::Color::Black) => "♝",
        (shakmaty::Role::Bishop, shakmaty::Color::White) => "♝",
        (shakmaty::Role::Rook, shakmaty::Color::Black) => "♜",
        (shakmaty::Role::Rook, shakmaty::Color::White) => "♜",
        (shakmaty::Role::Queen, shakmaty::Color::Black) => "♛",
        (shakmaty::Role::Queen, shakmaty::Color::White) => "♛",
        (shakmaty::Role::King, shakmaty::Color::Black) => "♚",
        (shakmaty::Role::King, shakmaty::Color::White) => "♚",
    }
    .to_string()
}

#[cfg(test)]
mod tests {
    use shakmaty::{fen::Epd, CastlingMode, Chess, File};

    use super::*;

    #[test]
    fn test_king_file_white() {
        // Create a position with the white king on the e-file
        let epd =
            Epd::from_ascii(b"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
        let pos: Chess = epd.into_position(CastlingMode::Standard).unwrap();

        // Check if king_file returns the correct file for the white king
        let white_king_file = king_file(&pos);
        assert_eq!(white_king_file, File::E);
    }

    #[test]
    fn test_king_file_black() {
        // Create a position with the black king on the e-file
        let epd =
            Epd::from_ascii(b"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
        let pos: Chess = epd.into_position(CastlingMode::Standard).unwrap();

        // Check if king_file returns the correct file for the black king
        let black_king_file = king_file(&pos);
        assert_eq!(black_king_file, File::E);
    }

    #[test]
    fn test_king_file_custom_position() {
        // Create a custom position with the white king on the c-file and the black king on the g-file
        let epd = Epd::from_ascii(
            b"r1bqr1k1/1pp2ppp/p1np1n2/2b1p1B1/2B1P3/2NP4/PPPQNPPP/2KR3R w - - 0 9",
        )
        .unwrap();
        let pos: Chess = epd.into_position(CastlingMode::Standard).unwrap();

        // Check if king_file returns the correct file for the white king
        let white_king_file = king_file(&pos);
        assert_eq!(white_king_file, File::C);

        // Check if king_file returns the correct file for the black king
        let pos = pos.swap_turn().unwrap();
        let black_king_file = king_file(&pos);
        assert_eq!(black_king_file, File::G);
    }

    #[test]
    fn test_has_en_passant_true() {
        // Create a position with an en passant opportunity
        let epd =
            Epd::from_ascii(b"rnbqkbnr/ppp3pp/4p3/3pPp2/3P4/8/PPP2PPP/RNBQKBNR w KQkq f6 0 4")
                .unwrap();
        let pos: Chess = epd.into_position(CastlingMode::Standard).unwrap();

        // Check if has_en_passant returns true
        assert_eq!(has_en_passant(&pos), true);
    }

    #[test]
    fn test_has_en_passant_false() {
        // Create a position without an en passant opportunity
        let epd =
            Epd::from_ascii(b"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
        let pos: Chess = epd.into_position(CastlingMode::Standard).unwrap();

        // Check if has_en_passant returns false
        assert_eq!(has_en_passant(&pos), false);
    }

    #[test]
    fn test_chess_serialize_root() {
        let epd =
            Epd::from_ascii(b"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
        let pos: Chess = epd.clone().into_position(CastlingMode::Standard).unwrap();
        let vec = serialize_chess(&pos);
        let pos_new = deserialize_chess(vec).unwrap();
        let epd_new = Epd::from_position(pos_new, EnPassantMode::Legal);
        assert_eq!(epd, epd_new);
    }

    #[test]
    fn test_chess_serialize_castling() {
        let epd =
            Epd::from_ascii(b"r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 0 1")
                .unwrap();
        let pos: Chess = epd.clone().into_position(CastlingMode::Standard).unwrap();
        let vec = serialize_chess(&pos);
        let pos_new = deserialize_chess(vec).unwrap();
        let epd_new = Epd::from_position(pos_new, EnPassantMode::Legal);
        assert_eq!(epd, epd_new);
    }

    #[test]
    fn test_chess_serialize_en_passant() {
        let epd =
            Epd::from_ascii(b"rnbqkbnr/ppp3pp/4p3/3pPp2/3P4/8/PPP2PPP/RNBQKBNR w KQkq f6 0 1")
                .unwrap();
        let pos: Chess = epd.clone().into_position(CastlingMode::Standard).unwrap();
        let vec = serialize_chess(&pos);
        let pos_new = deserialize_chess(vec).unwrap();
        let epd_new = Epd::from_position(pos_new, EnPassantMode::Legal);
        assert_eq!(epd, epd_new);
    }

    #[test]
    fn test_chess_serialize_ignore_chess960_castling() {
        let epd =
            Epd::from_ascii(b"nrbknbrq/pppppppp/8/8/8/8/PPPPPPPP/NRBKNBRQ w KQkq - 0 1").unwrap();
        let pos: Chess = epd.clone().into_position(CastlingMode::Chess960).unwrap();
        let vec = serialize_chess(&pos);
        let pos_new = deserialize_chess(vec).unwrap();
        let epd_new = Epd::from_position(pos_new, EnPassantMode::Legal);
        assert_eq!(epd, epd_new);
    }

    #[test]
    fn test_chess_serialize_castling_with_other_rook_on_non_back_rank() {
        let epd = Epd::from_ascii(b"r2qkb2/ppp2pp1/2n1p1b1/3P4/7r/1PB2B2/P1P1Q3/2KR3R w q - 0 1")
            .unwrap();
        let pos: Chess = epd.clone().into_position(CastlingMode::Chess960).unwrap();
        let vec = serialize_chess(&pos);
        let pos_new = deserialize_chess(vec).unwrap();
        let epd_new = Epd::from_position(pos_new, EnPassantMode::Legal);
        assert_eq!(epd, epd_new);
    }
}
