use smallvec::SmallVec;
use crate::{moves::{Move, MoveKind}, board::{color::Color, piece::Piece, CastlingSide}, bitboard::BitboardFns, square::Square};

use super::Board;

pub type MoveList = SmallVec<[Move; 128]>;

impl Board {
    pub fn generate_pseudolegal_moves(&self) -> MoveList {
        let mut result = MoveList::new();
        result.append(&mut self.generate_moves_core(true));
        result.append(&mut self.generate_moves_core(false));
        result
    }

    pub fn generate_moves_core(&self, tactical_only: bool) -> MoveList {
        let color = self.color();
        let player_pieces = self.pieces_by_color(color);
        let mut moves = MoveList::new();
        let empty = self.empty();

        let targets = if tactical_only {
            self.color_occupancy(color.flip()) ^ match color {
                // Exclude opponent king because we can't capture it
                Color::White => self.piece_sets[Piece::KingBlack as usize],
                Color::Black => self.piece_sets[Piece::King as usize],
            }
        } else {
            empty
        };

        let kind = if tactical_only {
            MoveKind::Capture
        } else {
            MoveKind::Quiet
        };

        for (piece_id, piece) in player_pieces.iter().enumerate() {
            for source in piece.serialize() {
                let piece_type = Piece::from(piece_id);
                let move_targets = match piece_type {
                    Piece::Bishop => self.attacks.bishop(self.occupancy, source),
                    Piece::Rook => self.attacks.rook(self.occupancy, source),
                    Piece::Queen => self.attacks.queen(self.occupancy, source),
                    Piece::Knight => self.attacks.knight[source as usize],
                    Piece::King => self.attacks.king[source as usize],
                    Piece::Pawn => if tactical_only {
                        self.attacks.pawn[color as usize][source as usize]
                    } else {
                        self.attacks.pawn_pushes[color as usize][source as usize]
                    },
                    _ => panic!("Incorrect piece type"),
                };

                // Generalized attack/move pattern for all pieces
                for target in (move_targets & targets).serialize() {
                    moves.push(Move { source, target, kind });
                }

                // Promotions
                if piece_type == Piece::Pawn && tactical_only {
                    // All promotions are tactical, but target square can be empty
                    let iter_captures = (move_targets & targets).serialize();
                    let iter_pushes = (self.attacks.pawn_pushes[color as usize][source as usize] & empty).serialize();

                    for target in iter_pushes.chain(iter_captures) {
                        if target.rank() == 7 {
                            for promo_type in [Piece::Bishop, Piece::Knight, Piece::Rook, Piece::Queen] {
                                moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
                            }
                        } else if target.rank() == 0 {
                            for promo_type in [Piece::BishopBlack, Piece::KnightBlack, Piece::RookBlack, Piece::QueenBlack] {
                                moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
                            }
                        }
                    }
                }

                // En Passant
                if piece_type == Piece::Pawn && tactical_only {
                    for target in (move_targets & self.ep_bitboard()).serialize() {
                        moves.push(Move { source, target, kind: MoveKind::EnPassant });
                    }
                }
            }
        }

        if !tactical_only {
            // Double Pushes
            // Make sure no blocking piece is standing in front of the pawns
            // that are potential double-push sources
            let able_to_double_push_mask = match color {
                Color::White => empty >> 8,
                Color::Black => empty << 8,
            };
            for source in (player_pieces[Piece::Pawn as usize] & able_to_double_push_mask).serialize() {
                for target in (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).serialize() {
                    moves.push(Move { source, target, kind: MoveKind::DoublePush });
                };
            }

            // Castling
            let king_home_position = match color {
                Color::White => Square::E1,
                Color::Black => Square::E8,
            };
            if player_pieces[Piece::King as usize].bitscan() == king_home_position {
                for rook_square in player_pieces[Piece::Rook as usize]
                    .serialize()
                    .filter(|rook_square| rook_square.rank() == king_home_position.rank())
                {
                    match rook_square.file() {
                        0 => {
                            let all_empty = [
                                king_home_position.west_one(),
                                king_home_position.west_one().west_one(),
                                king_home_position.west_one().west_one().west_one(),

                            ].iter().all(|square| empty & square.to_bitboard() > 0);

                            let any_checks = [
                                king_home_position,
                                king_home_position.west_one(),
                                king_home_position.west_one().west_one(),
                            ].iter().any(|square| self.is_square_attacked(*square, color.flip()));

                            if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::Queen as usize] {
                                moves.push(Move {
                                    source: king_home_position,
                                    target: king_home_position.west_one().west_one(),
                                    kind: MoveKind::Castle,
                                })
                            }
                        },
                        7 => {
                            let all_empty = [
                                king_home_position.east_one(),
                                king_home_position.east_one().east_one(),

                            ].iter().all(|square| empty & square.to_bitboard() > 0);

                            let any_checks = [
                                king_home_position,
                                king_home_position.east_one(),
                                king_home_position.east_one().east_one(),
                            ].iter().any(|square| self.is_square_attacked(*square, color.flip()));

                            if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::King as usize] {
                                moves.push(Move {
                                    source: king_home_position,
                                    target: king_home_position.east_one().east_one(),
                                    kind: MoveKind::Castle,
                                })
                            }
                        },
                        _ => {},
                    }
                }
            }
        }

        moves
    }
}

#[cfg(test)]
mod tests {
    use crate::board::{Board, io::IO};

    use super::*;

    #[test]
    fn generate_pseudolegal_moves_starting_position() {
        let board = Board::new();
        let moves = board.generate_pseudolegal_moves();
        let black_moves = board.generate_pseudolegal_moves();

        assert_eq!(moves.len(), 20);
        assert_eq!(black_moves.len(), 20);

        for mov in moves {
            mov.print();
        }
    }


    #[test]
    fn moved_king_castle() {
        let fen = String::from("4k2r/ppp1n3/8/4R1Pp/5P2/q1P5/P1P1BP2/1K1R4 b - - 2 22");
        let mut board = Board::from_FEN(fen);

        // Shuffle kings around, returning to the same position
        board.make_move(Move { source: Square::E8, target: Square::F8, kind: MoveKind::Quiet });
        board.make_move(Move { source: Square::B1, target: Square::A1, kind: MoveKind::Quiet });
        board.make_move(Move { source: Square::F8, target: Square::E8, kind: MoveKind::Quiet });
        board.make_move(Move { source: Square::A1, target: Square::B1, kind: MoveKind::Quiet });
        board.print();

        let moves = board.generate_pseudolegal_moves();

        let castle = moves.iter().find(|m| **m == Move { source: Square::E8, target: Square::G8, kind: MoveKind::Castle });
        println!("{:?}", board.castling_rights);
        assert!(castle.is_none(), "Castle should not be allowed after king has moved");
    }

    #[test]
    fn tactical_move_generation() {
        let fen = String::from("4k2r/ppp1n3/8/4R1Pp/5P2/q1P5/P1P1BP2/1K1R4 b - - 2 22");
        let board = Board::from_FEN(fen);

        let all_moves = board.generate_pseudolegal_moves();

        let tactical_only = board.generate_moves_core(true);
        let quiet_only = board.generate_moves_core(false);

        let true_tactical = all_moves.iter().filter(|m| m.is_tactical()).copied().collect::<MoveList>();
        let true_quiet = all_moves.iter().filter(|m| !m.is_tactical()).copied().collect::<MoveList>();

        // Make sure tactical_only and true_tactical are identical sets
        for mov in tactical_only.iter() {
            assert!(true_tactical.iter().any(|m| *m == *mov));
            assert!(mov.is_tactical());
        }
        for mov in true_tactical {
            assert!(tactical_only.iter().any(|m| *m == mov));
        }

        // Make sure quiet_only and true_quiet are identical sets
        for mov in quiet_only.iter() {
            assert!(!mov.is_tactical());
            assert!(true_quiet.iter().any(|m| *m == *mov));
        }
        for mov in true_quiet {
            assert!(quiet_only.iter().any(|m| *m == mov));
        }
    }
}