use crate::{bitboard::Bitboard, attacks::Attacks, moves::Move, clock::Clock};
use super::{Board, Piece, zobrist::Zobrist};

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

/// Input/Output operations with Board
pub trait IO {
    fn print(&self);

    #[allow(non_snake_case)]
    fn from_FEN(fen: String) -> Self;
    #[allow(non_snake_case)]
    fn to_FEN(&self) -> String;

    fn read_move(&self) -> Result<Move, String>;
}

impl IO for Board {
    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 (piece_type, piece_bitboard) in self.piece_sets.iter().enumerate() {
                    if (piece_bitboard & position) > 0 {
                        found = true;
                        print!("{} ", PIECE_CHARS[piece_type]);
                    }
                }
                if !found {
                    print!(". ");
                }
            }
            println!();
        }
        println!("  a b c d e f g h");
    }

    #[allow(non_snake_case)]
    fn from_FEN(fen: String) -> Self {
        let mut piece_sets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

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

        let mut chars = fen.chars();

        for character in chars.by_ref() {
            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' => piece_sets[Piece::Pawn as usize] |= position,
                    'N' => piece_sets[Piece::Knight as usize] |= position,
                    'B' => piece_sets[Piece::Bishop as usize] |= position,
                    'R' => piece_sets[Piece::Rook as usize] |= position,
                    'Q' => piece_sets[Piece::Queen as usize] |= position,
                    'K' => piece_sets[Piece::King as usize] |= position,
                    'p' => piece_sets[Piece::PawnBlack as usize] |= position,
                    'n' => piece_sets[Piece::KnightBlack as usize] |= position,
                    'b' => piece_sets[Piece::BishopBlack as usize] |= position,
                    'r' => piece_sets[Piece::RookBlack as usize] |= position,
                    'q' => piece_sets[Piece::QueenBlack as usize] |= position,
                    'k' => piece_sets[Piece::KingBlack as usize] |= position,
                    '/' => {
                        rank -= 1;
                        file = -1; // So it becomes 0
                    },
                    ' ' => { break },
                    _ => todo!("Unexpected character!"),
                }
                file += 1;
            }
        }

        let ply = match chars.next() {
            Some(char) => match char {
                'w' => 0,
                'b' => 1,
                 _ => panic!("Expected side to move"),
            }
            None => panic!("Expected side to move"),
        };

        let mut board = Self {
            piece_sets,
            occupancy: 0,
            ply,
            attacks: Attacks::new(),
            castling_rights: [[true; 2]; 2], // TODO: actualy parse from FEN
            ep_target: None, // TODO: parse from FEN
            clock: Clock::default(),
            positions: Vec::new(),
            hash: 0,
            zobrist_seed: Board::seed(),
        };
        board.update_occupancy();
        board.compute_hash();
        board
    }

    #[allow(non_snake_case)]
    fn to_FEN(&self) -> String {
        todo!()
    }

    fn read_move(&self) -> Result<Move, String> {
        todo!();
        // print!("\nEnter a move: ");
        // stdout().flush().unwrap();
        // let mut s = String::new();
        // stdin().read_line(&mut s).unwrap();
        // let chars = &mut s.chars();
        //
        // let source = match Square::from_notation(chars) {
        //     Ok(s) => s,
        //     Err(e) => return Err(e),
        // };
        // let target = match Square::from_notation(chars) {
        //     Ok(s) => s,
        //     Err(e) => return Err(e),
        // };
        //
        // let moves = self.generate_pseudolegal_moves();

        // let mov = match moves.iter().find(|m| m.source == source && m.target == target) {
        //     Some(m) => *m,
        //     None => return Err(String::from("Move is not valid")),
        // };

        // Ok(mov)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{bitboard::BitboardFns, square::Square, board::Color};

    #[test]
    fn new_from_default_fen() {
        let board = Board::new();

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

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

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

        assert_eq!(board.piece_sets[Piece::King as usize].bitscan(), Square::E1);
        assert_eq!(board.piece_sets[Piece::QueenBlack as usize].bitscan(), Square::D8);

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