/* move_evasion.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_evasion.cpp

// includes

#include "attack.h"
#include "board.h"
#include "colour.h"
#include "list.h"
#include "move.h"
#include "move_evasion.h"
#include "move_gen.h"
#include "piece.h"
#include "util.h"

namespace engine {

// prototypes

static bool gen_evasions      (list_t * list, const board_t * board, const attack_t * attack, bool legal, bool stop);

static bool add_pawn_moves    (list_t * list, const board_t * board, int to, bool legal, bool stop);
static bool add_pawn_captures (list_t * list, const board_t * board, int to, bool legal, bool stop);
static bool add_piece_moves   (list_t * list, const board_t * board, int to, bool legal, bool stop);

// functions

// gen_legal_evasions()

void gen_legal_evasions(list_t * list, const board_t * board, const attack_t * attack) {

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

   gen_evasions(list,board,attack,true,false);

   // debug

   ASSERT(list_is_ok(list));
}

// gen_pseudo_evasions()

void gen_pseudo_evasions(list_t * list, const board_t * board, const attack_t * attack) {

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

   gen_evasions(list,board,attack,false,false);

   // debug

   ASSERT(list_is_ok(list));
}

// legal_evasion_exist()

bool legal_evasion_exist(const board_t * board, const attack_t * attack) {

   list_t list[1]; // dummy

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

   return gen_evasions(list,board,attack,true,true);
}

// gen_evasions()

static bool gen_evasions(list_t * list, const board_t * board, const attack_t * attack, bool legal, bool stop) {

   int me, opp;
   int opp_flag;
   int king;
   const inc_t * inc_ptr;
   int inc;
   int to;
   int piece;

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);
   ASSERT(attack!=NULL);
   ASSERT(legal==true||legal==false);
   ASSERT(stop==true||stop==false);

   ASSERT(board_is_check(board));
   ASSERT(ATTACK_IN_CHECK(attack));

   // init

   LIST_CLEAR(list);

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

   opp_flag = COLOUR_FLAG(opp);

   king = KING_POS(board,me);

   for (inc_ptr = KingInc; (inc=*inc_ptr) != IncNone; inc_ptr++) {
      if (inc != -attack->di[0] && inc != -attack->di[1]) { // avoid escaping along a check line
         to = king + inc;
         piece = board->square[to];
         if (piece == Empty || FLAG_IS(piece,opp_flag)) {
            if (!legal || !is_attacked(board,to,opp)) {
               if (stop) return true;
               LIST_ADD(list,MOVE_MAKE(king,to));
            }
         }
      }
   }

   if (attack->dn >= 2) return false; // double check, we are done

   // single check

   ASSERT(attack->dn==1);

   // capture the checking piece

   if (add_pawn_captures(list,board,attack->ds[0],legal,stop) && stop) return true;
   if (add_piece_moves(list,board,attack->ds[0],legal,stop) && stop) return true;

   // interpose a piece

   inc = attack->di[0];

   if (inc != IncNone) { // line
      for (to = king+inc; to != attack->ds[0]; to += inc) {
         ASSERT(SQUARE_IS_OK(to));
         ASSERT(board->square[to]==Empty);
         if (add_pawn_moves(list,board,to,legal,stop) && stop) return true;
         if (add_piece_moves(list,board,to,legal,stop) && stop) return true;
      }
   }

   return false;
}

// add_pawn_moves()

static bool add_pawn_moves(list_t * list, const board_t * board, int to, bool legal, bool stop) {

   int me;
   int inc;
   int pawn;
   int from;
   int piece;

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);
   ASSERT(SQUARE_IS_OK(to));
   ASSERT(legal==true||legal==false);
   ASSERT(stop==true||stop==false);

   ASSERT(board->square[to]==Empty);

   me = board->turn;

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

   from = to - inc;
   piece = board->square[from];

   if (piece == pawn) { // single push

      if (!legal || !is_pinned(board,from,me)) {
         if (stop) return true;
         add_pawn_move(list,from,to);
      }

   } else if (piece == Empty && PAWN_RANK(to,me) == Rank4) { // double push

      from = to - (2*inc);
      if (board->square[from] == pawn) {
         if (!legal || !is_pinned(board,from,me)) {
            if (stop) return true;
            ASSERT(!SQUARE_IS_PROMOTE(to));
            LIST_ADD(list,MOVE_MAKE(from,to));
         }
      }
   }

   return false;
}

// add_pawn_captures()

static bool add_pawn_captures(list_t * list, const board_t * board, int to, bool legal, bool stop) {

   int me;
   int inc;
   int pawn;
   int from;

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);
   ASSERT(SQUARE_IS_OK(to));
   ASSERT(legal==true||legal==false);
   ASSERT(stop==true||stop==false);

   ASSERT(COLOUR_IS(board->square[to],COLOUR_OPP(board->turn)));

   me = board->turn;

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

   from = to - (inc-1);
   if (board->square[from] == pawn) {
      if (!legal || !is_pinned(board,from,me)) {
         if (stop) return true;
         add_pawn_move(list,from,to);
      }
   }

   from = to - (inc+1);
   if (board->square[from] == pawn) {
      if (!legal || !is_pinned(board,from,me)) {
         if (stop) return true;
         add_pawn_move(list,from,to);
      }
   }

   if (board->ep_square != SquareNone
    && to == SQUARE_EP_DUAL(board->ep_square)) {

      ASSERT(PAWN_RANK(to,me)==Rank5);
      ASSERT(PIECE_IS_PAWN(board->square[to]));

      to = board->ep_square;
      ASSERT(PAWN_RANK(to,me)==Rank6);
      ASSERT(board->square[to]==Empty);

      from = to - (inc-1);
      if (board->square[from] == pawn) {
         if (!legal || !is_pinned(board,from,me)) {
            if (stop) return true;
            ASSERT(!SQUARE_IS_PROMOTE(to));
            LIST_ADD(list,MOVE_MAKE_FLAGS(from,to,MoveEnPassant));
         }
      }

      from = to - (inc+1);
      if (board->square[from] == pawn) {
         if (!legal || !is_pinned(board,from,me)) {
            if (stop) return true;
            ASSERT(!SQUARE_IS_PROMOTE(to));
            LIST_ADD(list,MOVE_MAKE_FLAGS(from,to,MoveEnPassant));
         }
      }
   }

   return false;
}

// add_piece_moves()

static bool add_piece_moves(list_t * list, const board_t * board, int to, bool legal, bool stop) {

   int me;
   const sq_t * ptr;
   int from, piece;

   ASSERT(list!=NULL);
   ASSERT(board!=NULL);
   ASSERT(SQUARE_IS_OK(to));
   ASSERT(legal==true||legal==false);
   ASSERT(stop==true||stop==false);

   me = board->turn;

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

      piece = board->square[from];

      if (PIECE_ATTACK(board,piece,from,to)) {
         if (!legal || !is_pinned(board,from,me)) {
            if (stop) return true;
            LIST_ADD(list,MOVE_MAKE(from,to));
         }
      }
   }

   return false;
}

}  // namespace engine

// end of move_evasion.cpp

