use core::fmt;

use crate::{FILE_COUNT, FenParseError, CastlingRights, Color, EvalPosition, Piece, Position, Square, ZobristKey};

pub struct FenString { string: String }

impl FenString {
    pub fn startpos() -> FenString {
        FenString::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -")
    }

    pub fn kiwipete() -> FenString {
        FenString::from("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -")
    }

    pub fn rook() -> FenString {
        FenString::from("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -")
    }

    pub fn tricky() -> FenString {
        FenString::from("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ -")
    }

    pub fn tricky2() -> FenString {
        FenString::from("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8")
    }

    pub fn parse(&self) -> Result<Position, FenParseError> {
        let mut position = Position::default();
        
        let mut fen_iter = self.string.split_whitespace();
        let pieces_str = fen_iter.next().ok_or(FenParseError::NoPieces)?;
        let side_str = fen_iter.next().ok_or(FenParseError::NoSide)?;
        let castling_rights_str = fen_iter.next().ok_or(FenParseError::NoCastlingRights)?;
        let en_passant_sq_str = fen_iter.next().ok_or(FenParseError::NoEnPassant)?;
        
        Self::set_pieces(&mut position, pieces_str)?;
        Self::set_side(&mut position, side_str)?;
        Self::set_castling_rights(&mut position, castling_rights_str)?;
        Self::set_en_passant_sq(&mut position, en_passant_sq_str)?;
        
        position.zobrist_key = ZobristKey::generate(&position);

        #[cfg(feature = "tapered_eval")]
        { position.game_phase_score = EvalPosition::get_game_phase_score(&position); }
        
        Ok(position)
    }
    
    fn set_pieces(position: &mut Position, pieces_str: &str) -> Result<(), FenParseError> {
        let mut sq_index = 0_u8;
        for pieces_char in pieces_str.chars() {
            match pieces_char {
                '1'..='8' => sq_index += pieces_char
                    .to_digit(10)
                    .unwrap() as u8,
                '/' => (),
                'P' | 'N' | 'B' | 'R' | 'Q' | 'K' | 'p' | 'n' | 'b' | 'r' | 'q' | 'k' => {
                    let piece = Piece::from(pieces_char);
                    position.set_piece(piece, Square::from(sq_index));
                }
                _ => return Err(FenParseError::IllegalPiece(pieces_char)),
            };
            if !pieces_char.is_ascii_digit() && pieces_char != '/' { sq_index += 1; }
        }
        position.populate_occupancies();
        Ok(())
    }
    
    fn set_side(position: &mut Position, side_str: &str) -> Result<(), FenParseError> {
        match side_str {
            "w" => position.side = Color::White,
            "b" => position.side = Color::Black,
            _ => return Err(FenParseError::Side(side_str.to_string())),
        }
        Ok(())
    }
    
    fn set_castling_rights(position: &mut Position, castling_rights_str: &str) -> Result<(), FenParseError> {
        for char in castling_rights_str.chars() {
            match char {
                'K' => position.castling_rights.0 |= CastlingRights::WK.0,
                'Q' => position.castling_rights.0 |= CastlingRights::WQ.0,
                'k' => position.castling_rights.0 |= CastlingRights::BK.0,
                'q' => position.castling_rights.0 |= CastlingRights::BQ.0,
                '-' => (),
                _ => return Err(FenParseError::CastlingRights(char)),
            }
        }
        
        Ok(())
    }
    
    fn set_en_passant_sq(position: &mut Position, en_passant_sq_str: &str) -> Result<(), FenParseError> {
        match en_passant_sq_str {
            "-" => Ok(()),
            _ => {
                position.en_passant_option = Some(Square::try_from(en_passant_sq_str)?);
                Ok(())
            }
        }
    }
}

impl TryInto<Position> for FenString {
    type Error = FenParseError;

    fn try_into(self) -> Result<Position, Self::Error> {
        self.parse()
    }
}

impl From<&str> for FenString {
    fn from(string: &str) -> Self {
        FenString { string: string.to_string() }
    }
}

impl From<String> for FenString {
    fn from(string: String) -> Self {
        FenString { string }
    }
}

impl From<&Position> for FenString {
    fn from(position: &Position) -> Self {
        let mut fen_str = String::new();
        let mut curr_width = 0;
        let mut curr_empty = 0;
        for square in Square::ALL_SQUARES {
            curr_width += 1;

            let piece_option = position.get_piece_option(square);
            match piece_option {
                None => curr_empty += 1,
                Some(piece) => {
                    if curr_empty != 0 {
                        fen_str.push_str(&curr_empty.to_string());
                        curr_empty = 0;
                    }
                    fen_str.push(piece.into())
                }
            }

            if curr_width == FILE_COUNT {
                if curr_empty != 0 {
                    fen_str.push_str(&curr_empty.to_string());
                }

                if square != *Square::ALL_SQUARES.last().unwrap() {
                    fen_str.push('/');
                }

                curr_empty = 0;
                curr_width = 0;
            }
        }

        fen_str.push(' ');

        fen_str.push(
            match position.side {
                Color::White => 'w',
                Color::Black => 'b',
            }
        );

        fen_str.push(' ');

        fen_str.push_str(&position.castling_rights.to_string());

        fen_str.push(' ');

        fen_str.push_str(
            &match position.en_passant_option {
                None => "-".to_owned(),
                Some(en_passant_sq) => en_passant_sq.to_string(),
            }
        );
        
        fen_str.into()
    }
}

impl fmt::Display for FenString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.pad(&self.string)
    }
}
