/* move_check.cpp

   GNU Chess engine

   Copyright (C) 2001-2011 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


// move_check.cpp

// includes

#include "attack.h"
#include "colour.h"
#include "fen.h"
#include "list.h"
#include "move.h"
#include "move_check.h"
#include "move_do.h"
#include "move_gen.h"
#include "piece.h"
#include "square.h"
#include "util.h"

namespace engine {

// prototypes

static void add_quiet_checks      (list_t * list, const board_t * board);

static void add_castle_checks     (list_t * list, board_t * board);

static void add_check             (list_t * list, int move, board_t * board);

static void find_pins             (int list[], const board_t * board);

// functions

// gen_quiet_checks()

void gen_quiet_checks(list_t * list, board_t * board) {

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);

   ASSERT(!board_is_check(board));

   LIST_CLEAR(list);

   add_quiet_checks(list,board);
   add_castle_checks(list,board);

   // debug

   ASSERT(list_is_ok(list));
}

// add_quiet_checks()

static void add_quiet_checks(list_t * list, const board_t * board) {

   int me, opp;
   int king;
   const sq_t * ptr, * ptr_2;
   int from, to, sq;
   int piece;
   const inc_t * inc_ptr;
   int inc;
   int pawn;
   int rank;
   int pin[8+1];

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);

   // init

   me = board->turn;
   opp = COLOUR_OPP(me);

   king = KING_POS(board,opp);

   find_pins(pin,board);

   // indirect checks

   for (ptr = pin; (from=*ptr) != SquareNone; ptr++) {

      piece = board->square[from];

      ASSERT(is_pinned(board,from,opp));

      if (PIECE_IS_PAWN(piece)) {

         inc = PAWN_MOVE_INC(me);
         rank = PAWN_RANK(from,me);

         if (rank != Rank7) { // promotes are generated with captures
            to = from + inc;
            if (board->square[to] == Empty) {
               if (DELTA_INC_LINE(to-king) != DELTA_INC_LINE(from-king)) {
                  ASSERT(!SQUARE_IS_PROMOTE(to));
                  LIST_ADD(list,MOVE_MAKE(from,to));
                  if (rank == Rank2) {
                     to = from + (2*inc);
                     if (board->square[to] == Empty) {
                        ASSERT(DELTA_INC_LINE(to-king)!=DELTA_INC_LINE(from-king));
                        ASSERT(!SQUARE_IS_PROMOTE(to));
                        LIST_ADD(list,MOVE_MAKE(from,to));
                     }
                  }
               }
            }
         }

      } else if (PIECE_IS_SLIDER(piece)) {

         for (inc_ptr = PIECE_INC(piece); (inc=*inc_ptr) != IncNone; inc_ptr++) {
            for (to = from+inc; board->square[to] == Empty; to += inc) {
               ASSERT(DELTA_INC_LINE(to-king)!=DELTA_INC_LINE(from-king));
               LIST_ADD(list,MOVE_MAKE(from,to));
            }
         }

      } else {

         for (inc_ptr = PIECE_INC(piece); (inc=*inc_ptr) != IncNone; inc_ptr++) {
            to = from + inc;
            if (board->square[to] == Empty) {
               if (DELTA_INC_LINE(to-king) != DELTA_INC_LINE(from-king)) {
                  LIST_ADD(list,MOVE_MAKE(from,to));
               }
            }
         }
      }
   }

   // piece direct checks

   for (ptr = &board->piece[me][1]; (from=*ptr) != SquareNone; ptr++) { // HACK: no king

      for (ptr_2 = pin; (sq=*ptr_2) != SquareNone; ptr_2++) {
         if (sq == from) goto next_piece;
      }

      ASSERT(!is_pinned(board,from,opp));

      piece = board->square[from];
      inc_ptr = PIECE_INC(piece);

      if (PIECE_IS_SLIDER(piece)) {

         for (; (inc=*inc_ptr) != IncNone; inc_ptr++) {
            for (to = from+inc; board->square[to] == Empty; to += inc) {
               if (PIECE_ATTACK(board,piece,to,king)) {
                  LIST_ADD(list,MOVE_MAKE(from,to));
               }
            }
         }

      } else {

         for (; (inc=*inc_ptr) != IncNone; inc_ptr++) {
            to = from + inc;
            if (board->square[to] == Empty) {
               if (PSEUDO_ATTACK(piece,king-to)) {
                  LIST_ADD(list,MOVE_MAKE(from,to));
               }
            }
         }
      }

next_piece: ;
   }

   // pawn direct checks

   inc = PAWN_MOVE_INC(me);
   pawn = PAWN_MAKE(me);

   to = king - (inc-1);
   ASSERT(PSEUDO_ATTACK(pawn,king-to));

   from = to - inc;
   if (board->square[from] == pawn) {
      if (board->square[to] == Empty) {
         ASSERT(!SQUARE_IS_PROMOTE(to));
         LIST_ADD(list,MOVE_MAKE(from,to));
      }
   } else {
      from = to - (2*inc);
      if (board->square[from] == pawn) {
         if (PAWN_RANK(from,me) == Rank2
          && board->square[to] == Empty
          && board->square[from+inc] == Empty) {
            ASSERT(!SQUARE_IS_PROMOTE(to));
            LIST_ADD(list,MOVE_MAKE(from,to));
         }
      }
   }

   to = king - (inc+1);
   ASSERT(PSEUDO_ATTACK(pawn,king-to));

   from = to - inc;
   if (board->square[from] == pawn) {
      if (board->square[to] == Empty) {
         ASSERT(!SQUARE_IS_PROMOTE(to));
         LIST_ADD(list,MOVE_MAKE(from,to));
      }
   } else {
      from = to - (2*inc);
      if (board->square[from] == pawn) {
         if (PAWN_RANK(from,me) == Rank2
          && board->square[to] == Empty
          && board->square[from+inc] == Empty) {
            ASSERT(!SQUARE_IS_PROMOTE(to));
            LIST_ADD(list,MOVE_MAKE(from,to));
         }
      }
   }
}

// add_castle_checks()

static void add_castle_checks(list_t * list, board_t * board) {

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);

   ASSERT(!board_is_check(board));

   if (COLOUR_IS_WHITE(board->turn)) {

      if ((board->flags & FlagsWhiteKingCastle) != 0
       && board->square[F1] == Empty
       && board->square[G1] == Empty
       && !is_attacked(board,F1,Black)) {
         add_check(list,MOVE_MAKE_FLAGS(E1,G1,MoveCastle),board);
      }

      if ((board->flags & FlagsWhiteQueenCastle) != 0
       && board->square[D1] == Empty
       && board->square[C1] == Empty
       && board->square[B1] == Empty
       && !is_attacked(board,D1,Black)) {
         add_check(list,MOVE_MAKE_FLAGS(E1,C1,MoveCastle),board);
      }

   } else { // black

      if ((board->flags & FlagsBlackKingCastle) != 0
       && board->square[F8] == Empty
       && board->square[G8] == Empty
       && !is_attacked(board,F8,White)) {
         add_check(list,MOVE_MAKE_FLAGS(E8,G8,MoveCastle),board);
      }

      if ((board->flags & FlagsBlackQueenCastle) != 0
       && board->square[D8] == Empty
       && board->square[C8] == Empty
       && board->square[B8] == Empty
       && !is_attacked(board,D8,White)) {
         add_check(list,MOVE_MAKE_FLAGS(E8,C8,MoveCastle),board);
      }
   }
}

// add_check()

static void add_check(list_t * list, int move, board_t * board) {

   undo_t undo[1];

   ASSERT(list!=NULL);
   ASSERT(move_is_ok(move));
   ASSERT(board!=NULL);

   move_do(board,move,undo);
   if (IS_IN_CHECK(board,board->turn)) LIST_ADD(list,move);
   move_undo(board,move,undo);
}

// move_is_check()

bool move_is_check(int move, board_t * board) {

   undo_t undo[1];
   bool check;
   int me, opp, king;
   int from, to, piece;

   ASSERT(move_is_ok(move));
   ASSERT(board!=NULL);

   // slow test for complex moves

   if (MOVE_IS_SPECIAL(move)) {

      move_do(board,move,undo);
      check = IS_IN_CHECK(board,board->turn);
      move_undo(board,move,undo);

      return check;
   }

   // init

   me = board->turn;
   opp = COLOUR_OPP(me);
   king = KING_POS(board,opp);

   from = MOVE_FROM(move);
   to = MOVE_TO(move);
   piece = board->square[from];
   ASSERT(COLOUR_IS(piece,me));

   // direct check

   if (PIECE_ATTACK(board,piece,to,king)) return true;

   // indirect check

   if (is_pinned(board,from,opp)
    && DELTA_INC_LINE(king-to) != DELTA_INC_LINE(king-from)) {
      return true;
   }

   return false;
}

// find_pins()

static void find_pins(int list[], const board_t * board) {

   int me, opp;
   int king;
   const sq_t * ptr;
   int from;
   int piece;
   int delta;
   int inc;
   int sq;
   int capture;
   int pin;

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);

   // init

   me = board->turn;
   opp = COLOUR_OPP(me);

   king = KING_POS(board,opp);

   for (ptr = &board->piece[me][1]; (from=*ptr) != SquareNone; ptr++) { // HACK: no king

      piece = board->square[from];

      delta = king - from;
      ASSERT(delta_is_ok(delta));

      if (PSEUDO_ATTACK(piece,delta)) {

         ASSERT(PIECE_IS_SLIDER(piece));

         inc = DELTA_INC_LINE(delta);
         ASSERT(inc!=IncNone);

         ASSERT(SLIDER_ATTACK(piece,inc));

         sq = from;
         do sq += inc; while ((capture=board->square[sq]) == Empty);

         ASSERT(sq!=king);

         if (COLOUR_IS(capture,me)) {
            pin = sq;
            do sq += inc; while (board->square[sq] == Empty);
            if (sq == king) *list++ = pin;
         }
      }
   }

   *list = SquareNone;
}

}  // namespace engine

// end of move_check.cpp

