//! Represent the different kinds of move

use regex::Regex;
use std::fmt;

use super::board::Board;
use super::board::*;

pub trait Move: fmt::Display {
    // Returns a new instance of the given board, after making the given move.
    // Checks for some errors, so returns 'Result'.
    fn make_move(&self, board: &Board) -> Result<Board, &'static str>;
}

struct KingSideCastles {}

impl fmt::Display for KingSideCastles {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "O-O")
    }
}

impl Move for KingSideCastles {
    fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
        if board.white_to_move {
            // white-castles
            if board.get_coord(4, 0).name == Piece::WK
                && board.get_coord(5, 0).name == Piece::NO
                && board.get_coord(6, 0).name == Piece::NO
                && board.get_coord(7, 0).name == Piece::WR
                && board.white_king_side_castling
            {
                let mut revised_board = board.clone();
                revised_board.set_coord(4, 0, &Piece::NO);
                revised_board.set_coord(5, 0, &Piece::WR);
                revised_board.set_coord(6, 0, &Piece::WK);
                revised_board.set_coord(7, 0, &Piece::NO);

                revised_board.white_to_move = false;
                revised_board.enpassant_target = None;
                revised_board.halfmove_clock += 1;
                revised_board.white_king_side_castling = false;
                revised_board.white_queen_side_castling = false;

                Ok(revised_board)
            } else {
                Err("Invalid white O-O")
            }
        } else {
            // black-castles
            if board.get_coord(4, 7).name == Piece::BK
                && board.get_coord(5, 7).name == Piece::NO
                && board.get_coord(6, 7).name == Piece::NO
                && board.get_coord(7, 7).name == Piece::BR
                && board.black_king_side_castling
            {
                let mut revised_board = board.clone();
                revised_board.set_coord(4, 7, &Piece::NO);
                revised_board.set_coord(5, 7, &Piece::BR);
                revised_board.set_coord(6, 7, &Piece::BK);
                revised_board.set_coord(7, 7, &Piece::NO);

                revised_board.white_to_move = true;
                revised_board.enpassant_target = None;
                revised_board.halfmove_clock += 1;
                revised_board.fullmove_number += 1;
                revised_board.black_king_side_castling = false;
                revised_board.black_queen_side_castling = false;

                Ok(revised_board)
            } else {
                Err("Invalid black O-O")
            }
        }
    }
}

struct QueenSideCastles {}

impl fmt::Display for QueenSideCastles {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "O-O-O")
    }
}

impl Move for QueenSideCastles {
    fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
        if board.white_to_move {
            // white-castles
            if board.get_coord(4, 0).name == Piece::WK
                && board.get_coord(3, 0).name == Piece::NO
                && board.get_coord(2, 0).name == Piece::NO
                && board.get_coord(1, 0).name == Piece::NO
                && board.get_coord(0, 0).name == Piece::WR
                && board.white_queen_side_castling
            {
                let mut revised_board = board.clone();
                revised_board.set_coord(4, 0, &Piece::NO);
                revised_board.set_coord(3, 0, &Piece::WR);
                revised_board.set_coord(2, 0, &Piece::WK);
                revised_board.set_coord(0, 0, &Piece::NO);

                revised_board.white_to_move = false;
                revised_board.enpassant_target = None;
                revised_board.halfmove_clock += 1;
                revised_board.white_king_side_castling = false;
                revised_board.white_queen_side_castling = false;

                Ok(revised_board)
            } else {
                Err("Invalid white O-O-O")
            }
        } else {
            // black-castles
            if board.get_coord(4, 7).name == Piece::BK
                && board.get_coord(3, 7).name == Piece::NO
                && board.get_coord(2, 7).name == Piece::NO
                && board.get_coord(1, 7).name == Piece::NO
                && board.get_coord(0, 7).name == Piece::BR
                && board.black_queen_side_castling
            {
                let mut revised_board = board.clone();
                revised_board.set_coord(4, 7, &Piece::NO);
                revised_board.set_coord(3, 7, &Piece::BR);
                revised_board.set_coord(2, 7, &Piece::BK);
                revised_board.set_coord(0, 7, &Piece::NO);

                revised_board.white_to_move = true;
                revised_board.enpassant_target = None;
                revised_board.halfmove_clock += 1;
                revised_board.fullmove_number += 1;
                revised_board.black_king_side_castling = false;
                revised_board.black_queen_side_castling = false;

                Ok(revised_board)
            } else {
                Err("Invalid black O-O-O")
            }
        }
    }
}

struct PieceMove {
    move_string: String,
    piece: Piece,
    identifier: String, // regex constrains this to be 'a'-'h' or '1'-'8'
    destination: Square,
    is_capture: bool,
}

impl fmt::Display for PieceMove {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.move_string)
    }
}

impl Move for PieceMove {
    fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
        let piece = piece_for_colour (&self.piece, board.white_to_move);
        // for given piece type, locate those pieces on board which can reach destination
        let mut origin: Vec<Square> = board
            .locations_within(&piece, &self.identifier)
            .iter()
            .filter(|loc| board.can_reach(&piece, &loc, &self.destination))
            .cloned()
            .collect();
        // filter out ambiguities raised by king being left in check
        if origin.len() > 1 {
            origin = origin
                .iter()
                .filter(|loc| !board.king_left_in_check(&piece, &loc, &self.destination))
                .cloned()
                .collect();
        }
        // there should only be one unique piece at this point
        // Return an error if not
        if origin.len() != 1 || board.get_square(&origin[0]).name != piece {
            Err("Invalid piece move")
        } else {
            // all ok, so setup revised board with the move completed
            let mut revised_board = board.clone();
            revised_board.set_square(&origin[0], &Piece::NO);
            revised_board.set_square(&self.destination, &piece);
            revised_board.white_to_move = !board.white_to_move;
            revised_board.enpassant_target = None;
            if self.is_capture {
                revised_board.halfmove_clock = 0;
            } else {
                revised_board.halfmove_clock += 1;
            }
            if !board.white_to_move {
                revised_board.fullmove_number += 1
            }

            Ok(revised_board)
        }
    }
}

struct SimplePawnMove {
    move_string: String,
    destination: Square,
}

impl fmt::Display for SimplePawnMove {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.move_string)
    }
}

impl Move for SimplePawnMove {
    fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
        let mut revised_board = board.clone();

        if board.white_to_move {
            // white-move
            revised_board.white_to_move = false;
            revised_board.halfmove_clock = 0;

            if self.single_step(&board) {
                revised_board.set_square(&self.destination, &Piece::WP);
                let square = self.previous_square(board.white_to_move);
                revised_board.set_square(&square, &Piece::NO);
                revised_board.enpassant_target = None;

                Ok(revised_board)
            } else if self.initial_step(&board) {
                revised_board.set_square(&self.destination, &Piece::WP);
                revised_board.set_square(&self.initial_square(board.white_to_move), &Piece::NO);
                let square = self.previous_square(board.white_to_move);
                revised_board.enpassant_target = Some(square);

                Ok(revised_board)
            } else {
                Err("Invalid simple pawn move")
            }
        } else {
            // black-move
            revised_board.white_to_move = true;
            revised_board.halfmove_clock = 0;
            revised_board.fullmove_number += 1;

            if self.single_step(&board) {
                revised_board.set_square(&self.destination, &Piece::BP);
                let square = self.previous_square(board.white_to_move);
                revised_board.set_square(&square, &Piece::NO);
                revised_board.enpassant_target = None;

                Ok(revised_board)
            } else if self.initial_step(&board) {
                revised_board.set_square(&self.destination, &Piece::BP);
                revised_board.set_square(&self.initial_square(board.white_to_move), &Piece::NO);
                let square = self.previous_square(board.white_to_move);
                revised_board.enpassant_target = Some(square);

                Ok(revised_board)
            } else {
                Err("Invalid simple pawn move")
            }
        }
    }
}

impl SimplePawnMove {
    fn single_step(&self, board: &Board) -> bool {
        let pawn = if board.white_to_move { Piece::WP } else { Piece::BP };

        let dest_square = board.get_square(&self.destination);
        let square = self.previous_square(board.white_to_move);
        let dest_piece = board.get_square(&square);
        dest_square.name == Piece::NO && dest_piece.name == pawn
    }

    fn initial_step(&self, board: &Board) -> bool {
        let (pawn, rank) = if board.white_to_move {
            (Piece::WP, 3)
        } else {
            (Piece::BP, 4)
        };

        let dest_square = board.get_square(&self.destination);
        let dest_piece = board.get_square(&self.initial_square(board.white_to_move));
        dest_square.name == Piece::NO && self.destination.row == rank && dest_piece.name == pawn
    }

    fn initial_square(&self, white_to_move: bool) -> Square {
        Square {
            col: self.destination.col,
            row: if white_to_move { 1 } else { 6 },
        }
    }

    fn previous_square(&self, white_to_move: bool) -> Square {
        let offset: i32 = if white_to_move { -1 } else { 1 };

        Square {
            col: self.destination.col,
            row: ((self.destination.row as i32) + offset) as usize,
        }
    }
}

struct PawnCapture {
    move_string: String,
    source_col: String, // regex constrains this to be 'a'-'h'
    destination: Square,
}

impl fmt::Display for PawnCapture {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.move_string)
    }
}

impl Move for PawnCapture {
    fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
        let origin = self.find_origin(board.white_to_move);

        let mut revised_board = board.clone();
        if let Some(ep) = &board.enpassant_target {
            if self.destination == *ep {
                revised_board.set_coord(self.destination.col, origin.row, &Piece::NO);
            }
        };
        revised_board.set_square(&origin, &Piece::NO);
        revised_board.set_square(&self.destination, &board.get_square(&origin).name);
        revised_board.enpassant_target = None;
        revised_board.halfmove_clock = 0;
        revised_board.white_to_move = !board.white_to_move;
        if revised_board.white_to_move {
            revised_board.fullmove_number += 1;
        }

        Ok(revised_board)
    }
}

impl PawnCapture {
    // for a pawn capture, find the originating row and create origin square
    fn find_origin(&self, white_to_move: bool) -> Square {
        let offset: i32 = if white_to_move { -1 } else { 1 };
        let col = match self.source_col.as_str() {
            "a" => 0,
            "b" => 1,
            "c" => 2,
            "d" => 3,
            "e" => 4,
            "f" => 5,
            "g" => 6,
            "h" => 7,
            _ => panic!("Internal error: invalid square definition"),
        };
        Square {
            col,
            row: ((self.destination.row as i32) + offset) as usize,
        }
    }
}

struct PromotionPawnMove {
    pawn_move: SimplePawnMove,
    piece: Piece,
}

impl fmt::Display for PromotionPawnMove {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.pawn_move)
    }
}

impl Move for PromotionPawnMove {
    fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
        let mut revised_board = self.pawn_move.make_move(&board)?;
        revised_board.set_square(&self.pawn_move.destination, &self.piece);

        Ok(revised_board)
    }
}

struct PromotionPawnCapture {
    pawn_capture: PawnCapture,
    piece: Piece,
}

impl fmt::Display for PromotionPawnCapture {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.pawn_capture)
    }
}

impl Move for PromotionPawnCapture {
    fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
        let mut revised_board = self.pawn_capture.make_move(&board)?;
        revised_board.set_square(&self.pawn_capture.destination, &self.piece);

        Ok(revised_board)
    }
}

// used to hold regexes
pub struct Matches {
    king_side_castles: Regex,
    queen_side_castles: Regex,
    piece_move: Regex,
    simple_pawn_move: Regex,
    pawn_capture: Regex,
    promotion_pawn_move: Regex,
    promotion_pawn_capture: Regex,
    pub comment: Regex,
    pub nag: Regex
}

impl Matches {
    pub fn new() -> Matches {
        Matches {
            king_side_castles: Regex::new(r"^O-O\+?\z").unwrap(),
            queen_side_castles: Regex::new(r"^O-O-O\+?\z").unwrap(),
            piece_move: Regex::new(r"^([KQRBN])([a-h]?|[1-8]?)x?([a-h][1-8])\+?\z").unwrap(),
            simple_pawn_move: Regex::new(r"^([a-h][2-7])\+?\z").unwrap(),
            pawn_capture: Regex::new(r"^([a-h])x([a-h][2-7])\+?\z").unwrap(),
            promotion_pawn_move: Regex::new(r"^([a-h][18])=([QqRrBbNn])\+?\z").unwrap(),
            promotion_pawn_capture: Regex::new(r"^([a-h])x([a-h][18])=([QqRrBbNn])\+?\z").unwrap(),
            comment: Regex::new(r"\{(.)*\}").unwrap(),
            nag: Regex::new(r"$\d+").unwrap(),
        }
    }

    pub fn is_valid(&self, move_string: &str) -> bool {
        self.king_side_castles.is_match(move_string)
            || self.queen_side_castles.is_match(move_string)
            || self.piece_move.is_match(move_string)
            || self.simple_pawn_move.is_match(move_string)
            || self.pawn_capture.is_match(move_string)
            || self.promotion_pawn_move.is_match(move_string)
            || self.promotion_pawn_capture.is_match(move_string)
    }

    /// Returns a move of the appropriate move type for the given move.
    pub fn new_move(&self, move_string: &str) -> Result<Box<dyn Move>, &'static str> {
        if self.king_side_castles.is_match(move_string) {
            Ok(Box::new(KingSideCastles {}))
        } else if self.queen_side_castles.is_match(move_string) {
            Ok(Box::new(QueenSideCastles {}))
        } else if self.piece_move.is_match(move_string) {
            let captures = self.piece_move.captures(move_string).unwrap();
            Ok(Box::new(PieceMove {
                move_string: String::from(move_string),
                piece: piece_from(&captures[1].to_string()),
                identifier: captures[2].to_string().to_lowercase(),
                destination: Square::from(&captures[3]).unwrap(),
                is_capture: move_string.contains("x"),
            }))
        } else if self.simple_pawn_move.is_match(move_string) {
            let captures = self.simple_pawn_move.captures(move_string).unwrap();
            Ok(Box::new(SimplePawnMove {
                move_string: String::from(move_string),
                destination: Square::from(&captures[1]).unwrap(),
            }))
        } else if self.pawn_capture.is_match(move_string) {
            let captures = self.pawn_capture.captures(move_string).unwrap();
            Ok(Box::new(PawnCapture {
                move_string: String::from(move_string),
                source_col: captures[1].to_string().to_lowercase(),
                destination: Square::from(&captures[2]).unwrap(),
            }))
        } else if self.promotion_pawn_move.is_match(move_string) {
            let captures = self.promotion_pawn_move.captures(move_string).unwrap();
            let destination = Square::from(&captures[1])?;
            let destination_row = destination.row;
            Ok(Box::new(PromotionPawnMove {
                pawn_move: SimplePawnMove {
                    move_string: String::from(move_string),
                    destination
                },
                piece: if destination_row == 7 { // when white promotes, piece is uppercase
                    piece_from(&captures[2].to_string().to_uppercase())
                } else { // when black promotes, piece is lowercase
                    piece_from(&captures[2].to_string().to_lowercase())
                }
            }))
        } else if self.promotion_pawn_capture.is_match(move_string) {
            let captures = self.promotion_pawn_capture.captures(move_string).unwrap();
            let destination = Square::from(&captures[2])?;
            let destination_row = destination.row;
            Ok(Box::new(PromotionPawnCapture {
                pawn_capture: PawnCapture {
                    move_string: String::from(move_string),
                    source_col: captures[1].to_string().to_lowercase(),
                    destination
                },
                piece: if destination_row == 7 { // when white promotes, piece is uppercase
                    piece_from(&captures[3].to_string().to_uppercase())
                } else { // when black promotes, piece is lowercase
                    piece_from(&captures[3].to_string().to_lowercase())
                }
            }))
        } else {
            Err("Unknown move type")
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const DEFNS: &[(&str, &str, &str)] = &[
        // castling moves
        (
            "8/8/8/8/8/8/8/4K2R w K - 0 0",
            "O-O",
            "8/8/8/8/8/8/8/5RK1 b - - 1 0",
        ),
        (
            "4k2r/8/8/8/8/8/8/8 b k - 0 0",
            "O-O",
            "5rk1/8/8/8/8/8/8/8 w - - 1 1",
        ),
        (
            "8/8/8/8/8/8/8/R3K2R w KQ - 0 0",
            "O-O-O",
            "8/8/8/8/8/8/8/2KR3R b - - 1 0",
        ),
        (
            "r3k2r/8/8/8/8/8/8/8 b kq - 0 0",
            "O-O-O",
            "2kr3r/8/8/8/8/8/8/8 w - - 1 1",
        ),
        // simple pawn moves
        (
            "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
            "e4",
            "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
        ),
        (
            "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
            "a3",
            "rnbqkbnr/pppppppp/8/8/8/P7/1PPPPPPP/RNBQKBNR b KQkq - 0 1",
        ),
        (
            "8/8/4P3/8/8/8/8/8 w - - 10 23",
            "e7",
            "8/4P3/8/8/8/8/8/8 b - - 0 23",
        ),
        (
            "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
            "d5",
            "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2",
        ),
        (
            "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
            "g6",
            "rnbqkbnr/pppppp1p/6p1/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2",
        ),
        (
            "8/8/8/8/8/2p5/8/8 b - - 10 23",
            "c2",
            "8/8/8/8/8/8/2p5/8 w - - 0 24",
        ),
        // pawn promotions
        (
            "8/4P3/8/8/8/8/8/8 w - - 10 23",
            "e8=Q",
            "4Q3/8/8/8/8/8/8/8 b - - 0 23",
        ),
        (
            "8/4P3/8/8/8/8/8/8 w - - 10 23",
            "e8=N",
            "4N3/8/8/8/8/8/8/8 b - - 0 23",
        ),
        (
            "8/8/8/8/8/8/2p5/8 b - - 10 23",
            "c1=B",
            "8/8/8/8/8/8/8/2b5 w - - 0 24",
        ),
        (
            "7k/4P3/8/8/8/8/8/8 w - - 10 23",
            "e8=Q+",
            "4Q2k/8/8/8/8/8/8/8 b - - 0 23",
        ),
        // piece moves
        (
            "8/8/8/8/4Q3/8/8/8 w - - 10 23",
            "Qe8",
            "4Q3/8/8/8/8/8/8/8 b - - 11 23",
        ),
        (
            "4q3/8/8/8/4Q3/8/8/8 w - - 10 23",
            "Qxe8",
            "4Q3/8/8/8/8/8/8/8 b - - 0 23",
        ),
        (
            "8/8/8/8/4r3/8/8/8 b - - 10 23",
            "Rh4",
            "8/8/8/8/7r/8/8/8 w - - 11 24",
        ),
        (
            "8/8/8/8/Pr3/8/8/8 b - - 10 23",
            "Rxa4",
            "8/8/8/8/r7/8/8/8 w - - 0 24",
        ),
        (
            "8/8/5p2/8/4N3/8/8/8 w - - 10 23",
            "Nxf6",
            "8/8/5N2/8/8/8/8/8 b - - 0 23",
        ),
        (
            "8/8/5p2/8/4k3/8/8/8 b - - 10 23",
            "Kd5",
            "8/8/5p2/3k4/8/8/8/8 w - - 11 24",
        ),
        (
            "8/8/8/8/1N3N2/8/8/8 w - - 10 23",
            "Nbd5",
            "8/8/8/3N4/5N2/8/8/8 b - - 11 23",
        ),
        (
            "8/8/8/8/1N3N2/8/8/8 w - - 10 23",
            "Nfd5",
            "8/8/8/3N4/1N6/8/8/8 b - - 11 23",
        ),
        (
            "8/8/8/8/1R6/1p6/8/1R6 w - - 10 23",
            "R1xb3",
            "8/8/8/8/1R6/1R6/8/8 b - - 0 23",
        ),
        (
            "8/8/8/8/1R6/1p6/8/1R6 w - - 10 23",
            "R4xb3",
            "8/8/8/8/8/1R6/8/1R6 b - - 0 23",
        ),
        (
            "r1bqk2r/1pppbppp/p1n2n2/4p3/B3P3/5N2/PPPP1PPP/RNBQ1RK1 w - - 2 6",
            "Re1",
            "r1bqk2r/1pppbppp/p1n2n2/4p3/B3P3/5N2/PPPP1PPP/RNBQR1K1 b - - 3 6",
        ),
        (
            "Q7/8/2p5/8/8/8/8/7Q w - - 10 23",
            "Qf3",
            "Q7/8/2p5/8/8/5Q2/8/8 b - - 11 23",
        ),
        (
            "r2q1rk1/1b1nbppp/2pp1n2/1p2p3/3PP3/1BN2N1P/PP3PP1/R1BQR1K1 w - - 0 14",
            "Bg5",
            "r2q1rk1/1b1nbppp/2pp1n2/1p2p1B1/3PP3/1BN2N1P/PP3PP1/R2QR1K1 b - - 1 14",
        ),
        (
            "r2q1rk1/1b1nbpp1/3p3p/2p1P3/1p2n2B/1B3N1P/PP3PP1/RN1QR1K1 w - - 0 18",
            "Bxe7",
            "r2q1rk1/1b1nBpp1/3p3p/2p1P3/1p2n3/1B3N1P/PP3PP1/RN1QR1K1 b - - 0 18",
        ),
        (
            "8/8/8/8/1b6/2N5/8/4K1N1 w - - 0 10",
            "Ne2",
            "8/8/8/8/1b6/2N5/4N3/4K3 b - - 1 10",
        ),
        // Pawn captures
        (
            "8/8/8/3p4/4P3/8/8/8 w - - 10 23",
            "exd5",
            "8/8/8/3P4/8/8/8/8 b - - 0 23",
        ),
        (
            "8/8/8/3p4/4P3/8/8/8 b - - 10 23",
            "dxe4",
            "8/8/8/8/4p3/8/8/8 w - - 0 24",
        ),
        (
            "8/8/8/8/3pP3/8/8/8 b - e3 10 23",
            "dxe3",
            "8/8/8/8/8/4p3/8/8 w - - 0 24",
        ),
        (
            "8/8/8/3pP3/8/8/8/8 w - d6 10 23",
            "exd6",
            "8/8/3P4/8/8/8/8/8 b - - 0 23",
        ),
        (
            "3n4/4P3/8/8/8/8/8/8 w - - 10 23",
            "exd8=B",
            "3B4/8/8/8/8/8/8/8 b - - 0 23",
        ),
    ];

    #[test]
    fn test_manages_moves() {
        let matcher = Matches::new();
        for (source, mv, target) in DEFNS.iter() {
            let board = Board::from_fen(source).unwrap();
            match matcher.new_move(mv).unwrap().make_move(&board) {
                Ok(end_board) => assert_eq!(Board::from_fen(target).unwrap(), end_board),
                Err(e) => {
                    println!("Error {}", e);
                    assert!(false);
                }
            }
        }
    }
}
