use crate::bitboard::Bitboard;

#[derive(Debug)]
pub struct Board {
    pub pieces: [Bitboard; 12],

    pub occupancy: Bitboard,
    pub ply: u16,
}


pub enum PieceTypes {
    Pawn,
    PawnBlack,
    Knight,
    KnightBlack,
    Bishop,
    BishopBlack,
    Rook,
    RookBlack,
    Queen,
    QueenBlack,
    King,
    KingBlack,
}


const PIECE_CHARS: [&str; 12] = [
    "♟︎", "♙",
    "♞", "♘",
    "♝", "♗",
    "♜", "♖",
    "♛", "♕",
    "♚", "♔",
];


#[allow(unused)]
impl Board {
    #[allow(non_snake_case)]
    pub fn from_FEN(fen: String) -> Self {
        let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

        let mut rank = 7;
        let mut file = 0i32;

        for character in fen.chars() {
            let index = rank * 8 + file;
            let position = 1 << index.clamp(0, 63);

            if character.is_numeric() {
                let digit = match character.to_digit(10) {
                    None => todo!("What to do here?"),
                    Some(digit) => digit,
                };
                if digit > 0 && digit <= 8 {
                    file += digit as i32;
                }
            } else {
                match character {
                    'P' => pieces[PieceTypes::Pawn as usize] |= position,
                    'N' => pieces[PieceTypes::Knight as usize] |= position,
                    'B' => pieces[PieceTypes::Bishop as usize] |= position,
                    'R' => pieces[PieceTypes::Rook as usize] |= position,
                    'Q' => pieces[PieceTypes::Queen as usize] |= position,
                    'K' => pieces[PieceTypes::King as usize] |= position,
                    'p' => pieces[PieceTypes::PawnBlack as usize] |= position,
                    'n' => pieces[PieceTypes::KnightBlack as usize] |= position,
                    'b' => pieces[PieceTypes::BishopBlack as usize] |= position,
                    'r' => pieces[PieceTypes::RookBlack as usize] |= position,
                    'q' => pieces[PieceTypes::QueenBlack as usize] |= position,
                    'k' => pieces[PieceTypes::KingBlack as usize] |= position,
                    '/' => {
                        rank -= 1;
                        file = -1; // So it becomes 0
                    },
                    ' ' => { break }, // TODO: break for now, parse everything else later
                    '-' => {}, // TODO
                    'w' => {}, // TODO
                    _ => todo!("Unexpected character!"),
                }
                file += 1;
            }
        }

        let mut board = Self { pieces, occupancy: 0, ply: 0 };
        board.update_occupancy();
        board
    }

    fn update_occupancy(&mut self) {
        self.occupancy = 0;
        // TODO: reduce
        for piece in self.pieces {
            self.occupancy |= piece;
        }
    }

    fn empty(&self) -> Bitboard {
        !self.occupancy
    }

    fn color_occupancy(&self, color: Color) -> Bitboard {
        let mut occupancy = 0;
        for (piece_type, piece) in self.pieces.iter().enumerate() {
            if piece_type % 2 == color as usize {
                occupancy |= piece;
            }
        }
        occupancy
    }

    pub fn print(&self) {
        println!();
        for rank in (0..8).rev() {
            print!("{}|", rank + 1);
            for file in 0..8 {
                let index = rank * 8 + file;
                let position: Bitboard = 1 << index;
                let mut found = false;
                for (piec_type, piece_bitboard) in self.pieces.iter().enumerate() {
                    if (piece_bitboard & position) > 0 {
                        found = true;
                        print!("{} ", PIECE_CHARS[piec_type]);
                    }
                }
                if !found {
                    print!(". ");
                }
            }
            println!();
        }
        println!("  a b c d e f g h");
    }
}


#[derive(Clone, Copy)]
pub enum Color {
    White,
    Black,
}

/// Aliases to board square indexes
#[allow(dead_code)]
#[derive(Debug)]
pub enum Square {
  A1, B1, C1, D1, E1, F1, G1, H1,
  A2, B2, C2, D2, E2, F2, G2, H2,
  A3, B3, C3, D3, E3, F3, G3, H3,
  A4, B4, C4, D4, E4, F4, G4, H4,
  A5, B5, C5, D5, E5, F5, G5, H5,
  A6, B6, C6, D6, E6, F6, G6, H6,
  A7, B7, C7, D7, E7, F7, G7, H7,
  A8, B8, C8, D8, E8, F8, G8, H8,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::bitboard::{pop_count, bitscan, print};

    #[test]
    fn test_square_enum() {
        assert_eq!(Square::A1 as u8, 0);
        assert_eq!(Square::F1 as u8, 5);
        assert_eq!(Square::H8 as u8, 63);
    }

    #[test]
    fn test_from_fen() {
        let fen = String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
        let board = Board::from_FEN(fen);

        board.print();
        print(board.empty(), "Empty squares");

        assert_eq!(pop_count(board.pieces[PieceTypes::Pawn as usize]), 8);
        assert_eq!(pop_count(board.pieces[PieceTypes::Knight as usize]), 2);
        assert_eq!(pop_count(board.pieces[PieceTypes::Bishop as usize]), 2);
        assert_eq!(pop_count(board.pieces[PieceTypes::Rook as usize]), 2);
        assert_eq!(pop_count(board.pieces[PieceTypes::Queen as usize]), 1);
        assert_eq!(pop_count(board.pieces[PieceTypes::King as usize]), 1);

        assert_eq!(pop_count(board.pieces[PieceTypes::PawnBlack as usize]), 8);
        assert_eq!(pop_count(board.pieces[PieceTypes::KnightBlack as usize]), 2);
        assert_eq!(pop_count(board.pieces[PieceTypes::BishopBlack as usize]), 2);
        assert_eq!(pop_count(board.pieces[PieceTypes::RookBlack as usize]), 2);
        assert_eq!(pop_count(board.pieces[PieceTypes::QueenBlack as usize]), 1);
        assert_eq!(pop_count(board.pieces[PieceTypes::KingBlack as usize]), 1);

        assert_eq!(bitscan(board.pieces[PieceTypes::King as usize]), Square::E1 as u8);
        assert_eq!(bitscan(board.pieces[PieceTypes::QueenBlack as usize]), Square::D8 as u8);

        assert_eq!(pop_count(board.occupancy), 32);
        assert_eq!(pop_count(board.empty()), 32);
        assert_eq!(pop_count(board.color_occupancy(Color::White)), 16);
        assert_eq!(pop_count(board.color_occupancy(Color::Black)), 16);
    }
}