use super::Position;
use crate::board::state_info::StateInfo;
use crate::types::Bitboard;
use crate::types::{Color, PieceType, Square};

impl Position {
    /// 現在王手をかけている駒のBitboardを返す
    ///
    /// # Returns
    /// 手番側の玉に王手をかけている駒のBitboard
    #[must_use]
    pub fn checkers(&self) -> Bitboard {
        self.state_stack[self.st_index].checkers
    }

    /// check_squares のキャッシュを取得
    #[must_use]
    pub fn check_squares_cache(&self) -> &[Bitboard; PieceType::COUNT] {
        &self.state_stack[self.st_index].check_squares
    }

    /// 指定した駒種の check_squares を取得
    #[must_use]
    pub fn check_squares(&self, piece_type: PieceType) -> Bitboard {
        self.check_squares_cache()[piece_type.to_index()]
    }

    /// ピンされている駒のBitboardを返す（キャッシュ済み）
    #[must_use]
    pub fn pinned_pieces(&self, c: Color) -> Bitboard {
        self.blockers_for_king(c).and(self.bitboards.color_pieces(c))
    }

    /// 回避対象を除外してピンされている駒を返す。
    #[must_use]
    pub fn pinned_pieces_avoid(&self, c: Color, avoid: Square) -> Bitboard {
        use crate::board::attack_tables::{
            bishop_step_effect, lance_step_effect, rook_step_effect,
        };

        let king_sq = self.king_square(c);
        if king_sq.is_none() {
            return Bitboard::EMPTY;
        }

        let them = c.flip();
        let rook_like = self.bitboards().pieces_for(PieceType::ROOK, them)
            | self.bitboards().pieces_for(PieceType::DRAGON, them);
        let bishop_like = self.bitboards().pieces_for(PieceType::BISHOP, them)
            | self.bitboards().pieces_for(PieceType::HORSE, them);
        let lance_like = self.bitboards().pieces_for(PieceType::LANCE, them);

        let avoid_bb =
            if avoid.is_none() { Bitboard::ALL } else { Bitboard::from_square(avoid).not() };
        let rook_snipers = rook_like & rook_step_effect(king_sq) & avoid_bb;
        let bishop_snipers = bishop_like & bishop_step_effect(king_sq) & avoid_bb;
        let lance_snipers = lance_like & lance_step_effect(king_sq, c) & avoid_bb;

        let snipers = rook_snipers | bishop_snipers | lance_snipers;
        let occupancy = self.bitboards().occupied() & avoid_bb;
        let mut result = Bitboard::EMPTY;

        for sniper_sq in &snipers {
            let between = Bitboard::between(sniper_sq, king_sq) & occupancy;
            if between.count() == 1 {
                result |= between & self.bitboards.color_pieces(c);
            }
        }

        result
    }

    /// from->to の移動を仮定したピン状態を返す。
    #[must_use]
    pub fn pinned_pieces_after_move(&self, c: Color, from: Square, to: Square) -> Bitboard {
        use crate::board::attack_tables::{
            bishop_step_effect, lance_step_effect, rook_step_effect,
        };

        let king_sq = self.king_square(c);
        if king_sq.is_none() {
            return Bitboard::EMPTY;
        }

        let them = c.flip();
        let rook_like = self.bitboards().pieces_for(PieceType::ROOK, them)
            | self.bitboards().pieces_for(PieceType::DRAGON, them);
        let bishop_like = self.bitboards().pieces_for(PieceType::BISHOP, them)
            | self.bitboards().pieces_for(PieceType::HORSE, them);
        let lance_like = self.bitboards().pieces_for(PieceType::LANCE, them);

        let avoid_bb =
            if from.is_none() { Bitboard::ALL } else { Bitboard::from_square(from).not() };
        let rook_snipers = rook_like & rook_step_effect(king_sq) & avoid_bb;
        let bishop_snipers = bishop_like & bishop_step_effect(king_sq) & avoid_bb;
        let lance_snipers = lance_like & lance_step_effect(king_sq, c) & avoid_bb;

        let snipers = rook_snipers | bishop_snipers | lance_snipers;
        let mut occupancy = self.bitboards().occupied() & avoid_bb;
        if to.is_on_board() {
            occupancy |= Bitboard::from_square(to);
        }

        let mut result = Bitboard::EMPTY;
        for sniper_sq in &snipers {
            let between = Bitboard::between(sniper_sq, king_sq) & occupancy;
            if between.count() == 1 {
                result |= between & self.bitboards.color_pieces(c);
            }
        }

        result
    }

    /// 玉を守っているブロッカー（pin候補を含む）を返す
    #[must_use]
    pub fn blockers_for_king(&self, c: Color) -> Bitboard {
        self.state_stack[self.st_index].blockers_for_king[c.to_index()]
    }

    /// 玉に対してピンしている敵の大駒を返す
    #[must_use]
    pub fn pinners(&self, c: Color) -> Bitboard {
        self.state_stack[self.st_index].pinners[c.to_index()]
    }

    /// 現局面で王手がかかっているかを返す。
    #[must_use]
    pub fn is_in_check(&self) -> bool {
        !self.checkers().is_empty()
    }

    /// 指し手が空き王手になる候補かを判定する。
    #[must_use]
    pub fn is_discovery_check_on_king(&self, c: Color, mv: crate::types::Move32) -> bool {
        self.blockers_for_king(c).test(mv.to_move().from_sq())
    }

    /// `Move` 版の空き王手候補判定。
    #[must_use]
    pub fn is_discovery_check_on_king_move(&self, c: Color, mv: crate::types::Move) -> bool {
        self.blockers_for_king(c).test(mv.from_sq())
    }

    /// check_squares を計算する
    /// 各駒種について、その駒を配置すると敵玉に王手となるマスのビットボードを計算
    ///
    /// YaneuraOu の set_check_info() に合わせて、全駒種を無条件に計算する。
    /// 条件分岐によるスライダー計算スキップは、典型的な局面では分岐コストが
    /// 計算コストを上回るため行わない。
    #[inline]
    pub(crate) fn compute_check_squares(&self) -> [Bitboard; PieceType::COUNT] {
        match self.turn() {
            Color::BLACK => self.compute_check_squares_for::<true>(),
            Color::WHITE => self.compute_check_squares_for::<false>(),
        }
    }

    /// `compute_check_squares` の手番定数化版。
    #[inline]
    pub(crate) fn compute_check_squares_for<const BLACK_TURN: bool>(
        &self,
    ) -> [Bitboard; PieceType::COUNT] {
        use crate::board::attack_tables::{bishop_attacks, lance_step_effect, rook_attacks};
        use crate::board::attack_tables::{
            GOLD_ATTACKS, KING_ATTACKS, KNIGHT_ATTACKS, PAWN_ATTACKS, SILVER_ATTACKS,
        };

        let mut check_squares = [Bitboard::EMPTY; PieceType::COUNT];

        let them = if BLACK_TURN { Color::WHITE } else { Color::BLACK };
        let ksq = self.king_square(them);
        if ksq.is_none() {
            return check_squares;
        }

        let occupied = self.bitboards().occupied();

        // 各駒種について、敵玉から逆向きに攻撃範囲を計算（無条件）
        // YaneuraOu の set_check_info() 実装に準拠
        check_squares[PieceType::PAWN.to_index()] = PAWN_ATTACKS[ksq.to_index()][them.to_index()];
        check_squares[PieceType::KNIGHT.to_index()] =
            KNIGHT_ATTACKS[ksq.to_index()][them.to_index()];
        check_squares[PieceType::SILVER.to_index()] =
            SILVER_ATTACKS[ksq.to_index()][them.to_index()];
        check_squares[PieceType::GOLD.to_index()] = GOLD_ATTACKS[ksq.to_index()][them.to_index()];
        check_squares[PieceType::BISHOP.to_index()] = bishop_attacks(ksq, occupied);
        check_squares[PieceType::ROOK.to_index()] = rook_attacks(ksq, occupied);

        // 香は飛車の利きを香のstep effectでマスク
        check_squares[PieceType::LANCE.to_index()] =
            check_squares[PieceType::ROOK.to_index()] & lance_step_effect(ksq, them);

        // 玉を移動させて直接王手になることはない（自殺手）
        check_squares[PieceType::KING.to_index()] = Bitboard::EMPTY;

        // 成り駒の check_squares を設定
        check_squares[PieceType::PRO_PAWN.to_index()] = check_squares[PieceType::GOLD.to_index()];
        check_squares[PieceType::PRO_LANCE.to_index()] = check_squares[PieceType::GOLD.to_index()];
        check_squares[PieceType::PRO_KNIGHT.to_index()] = check_squares[PieceType::GOLD.to_index()];
        check_squares[PieceType::PRO_SILVER.to_index()] = check_squares[PieceType::GOLD.to_index()];
        check_squares[PieceType::HORSE.to_index()] =
            check_squares[PieceType::BISHOP.to_index()] | KING_ATTACKS[ksq.to_index()];
        check_squares[PieceType::DRAGON.to_index()] =
            check_squares[PieceType::ROOK.to_index()] | KING_ATTACKS[ksq.to_index()];

        check_squares
    }

    pub(super) fn compute_checkers_for(&self, color: Color) -> Bitboard {
        let us = color;
        let them = us.flip();
        let occupied = self.bitboards.occupied();

        let king_sq = self.king_square(us);
        if king_sq.is_none() {
            return Bitboard::EMPTY;
        }

        self.attackers_to_color_fast(them, king_sq, occupied)
    }

    pub(super) fn recompute_caches(&mut self) {
        let mut state = StateInfo::default();
        self.compute_caches_for_state(&mut state);
        self.sync_caches_from_state(&state);
    }

    pub(crate) fn compute_caches_for_state(&self, state: &mut StateInfo) {
        let (black_blockers, black_pinners) = self.compute_slider_info_for::<true>();
        state.blockers_for_king[Color::BLACK.to_index()] = black_blockers;
        state.pinners[Color::BLACK.to_index()] = black_pinners;

        let (white_blockers, white_pinners) = self.compute_slider_info_for::<false>();
        state.blockers_for_king[Color::WHITE.to_index()] = white_blockers;
        state.pinners[Color::WHITE.to_index()] = white_pinners;

        match self.turn() {
            Color::BLACK => {
                state.checkers = self.compute_checkers_for(Color::BLACK);
                state.check_squares = self.compute_check_squares_for::<true>();
            }
            Color::WHITE => {
                state.checkers = self.compute_checkers_for(Color::WHITE);
                state.check_squares = self.compute_check_squares_for::<false>();
            }
        }
    }

    #[inline]
    pub(crate) fn compute_caches_for_state_no_checkers_for<const BLACK_TURN: bool>(
        &self,
        state: &mut StateInfo,
    ) {
        let (black_blockers, black_pinners) = self.compute_slider_info_for::<true>();
        state.blockers_for_king[Color::BLACK.to_index()] = black_blockers;
        state.pinners[Color::BLACK.to_index()] = black_pinners;

        let (white_blockers, white_pinners) = self.compute_slider_info_for::<false>();
        state.blockers_for_king[Color::WHITE.to_index()] = white_blockers;
        state.pinners[Color::WHITE.to_index()] = white_pinners;

        state.check_squares = self.compute_check_squares_for::<BLACK_TURN>();
    }

    pub(crate) fn sync_caches_from_state(&mut self, state: &StateInfo) {
        let current = &mut self.state_stack[self.st_index];
        current.checkers = state.checkers;
        current.check_squares = state.check_squares;
        current.pinners = state.pinners;
        current.blockers_for_king = state.blockers_for_king;
    }
}
