/* see.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/>.
*/


// see.cpp

// includes

#include "attack.h"
#include "board.h"
#include "colour.h"
#include "move.h"
#include "piece.h"
#include "see.h"
#include "util.h"
#include "value.h"

// macros

#define ALIST_CLEAR(alist) ((alist)->size=0)

namespace engine {

// types

struct alist_t {
   int size;
   int square[15];
};

struct alists_t {
   alist_t alist[ColourNb][1];
};

// prototypes

static int  see_rec       (alists_t * alists, const board_t * board, int colour, int to, int piece_value);

static void alist_build   (alist_t * alist, const board_t * board, int to, int colour);
static void alists_hidden (alists_t * alists, const board_t * board, int from, int to);

static void alist_add     (alist_t * alist, int square, const board_t * board);
static void alist_remove  (alist_t * alist, int pos);
static int  alist_pop     (alist_t * alist, const board_t * board);

// functions

// see_move()

int see_move(int move, const board_t * board) {

   int att, def;
   int from, to;
   alists_t alists[1];
   int value, piece_value;
   int piece, capture;
   alist_t * alist;
   int pos;

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

   // init

   from = MOVE_FROM(move);
   to = MOVE_TO(move);

   // move the piece

   piece_value = 0;

   piece = board->square[from];
   ASSERT(piece_is_ok(piece));

   att = PIECE_COLOUR(piece);
   def = COLOUR_OPP(att);

   // promote

   if (MOVE_IS_PROMOTE(move)) {
      ASSERT(PIECE_IS_PAWN(piece));
      piece = move_promote(move);
      ASSERT(piece_is_ok(piece));
      ASSERT(COLOUR_IS(piece,att));
   }

   piece_value += VALUE_PIECE(piece);

   // clear attacker lists

   ALIST_CLEAR(alists->alist[Black]);
   ALIST_CLEAR(alists->alist[White]);

   // find hidden attackers

   alists_hidden(alists,board,from,to);

   // capture the piece

   value = 0;

   capture = board->square[to];

   if (capture != Empty) {

      ASSERT(piece_is_ok(capture));
      ASSERT(COLOUR_IS(capture,def));

      value += VALUE_PIECE(capture);
   }

   // promote

   if (MOVE_IS_PROMOTE(move)) {
      value += VALUE_PIECE(piece) - ValuePawn;
   }

   // en-passant

   if (MOVE_IS_EN_PASSANT(move)) {
      ASSERT(value==0);
      ASSERT(PIECE_IS_PAWN(board->square[SQUARE_EP_DUAL(to)]));
      value += ValuePawn;
      alists_hidden(alists,board,SQUARE_EP_DUAL(to),to);
   }

   // build defender list

   alist = alists->alist[def];

   alist_build(alist,board,to,def);
   if (alist->size == 0) return value; // no defender => stop SEE

   // build attacker list

   alist = alists->alist[att];

   alist_build(alist,board,to,att);

   // remove the moved piece (if it's an attacker)

   for (pos = 0; pos < alist->size && alist->square[pos] != from; pos++)
      ;

   if (pos < alist->size) alist_remove(alist,pos);

   // SEE search

   value -= see_rec(alists,board,def,to,piece_value);

   return value;
}

// see_square()

int see_square(const board_t * board, int to, int colour) {

   int att, def;
   alists_t alists[1];
   alist_t * alist;
   int piece_value;
   int piece;

   ASSERT(board!=NULL);
   ASSERT(SQUARE_IS_OK(to));
   ASSERT(COLOUR_IS_OK(colour));

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

   // build attacker list

   att = colour;
   alist = alists->alist[att];

   ALIST_CLEAR(alist);
   alist_build(alist,board,to,att);

   if (alist->size == 0) return 0; // no attacker => stop SEE

   // build defender list

   def = COLOUR_OPP(att);
   alist = alists->alist[def];

   ALIST_CLEAR(alist);
   alist_build(alist,board,to,def);

   // captured piece

   piece = board->square[to];
   ASSERT(piece_is_ok(piece));
   ASSERT(COLOUR_IS(piece,def));

   piece_value = VALUE_PIECE(piece);

   // SEE search

   return see_rec(alists,board,att,to,piece_value);
}

// see_rec()

static int see_rec(alists_t * alists, const board_t * board, int colour, int to, int piece_value) {

   int from, piece;
   int value;

   ASSERT(alists!=NULL);
   ASSERT(board!=NULL);
   ASSERT(COLOUR_IS_OK(colour));
   ASSERT(SQUARE_IS_OK(to));
   ASSERT(piece_value>0);

   // find the least valuable attacker

   from = alist_pop(alists->alist[colour],board);
   if (from == SquareNone) return 0; // no more attackers

   // find hidden attackers

   alists_hidden(alists,board,from,to);

   // calculate the capture value

   value = +piece_value; // captured piece
   if (value == ValueKing) return value; // do not allow an answer to a king capture

   piece = board->square[from];
   ASSERT(piece_is_ok(piece));
   ASSERT(COLOUR_IS(piece,colour));
   piece_value = VALUE_PIECE(piece);

   // promote

   if (piece_value == ValuePawn && SQUARE_IS_PROMOTE(to)) { // HACK: PIECE_IS_PAWN(piece)
      ASSERT(PIECE_IS_PAWN(piece));
      piece_value = ValueQueen;
      value += ValueQueen - ValuePawn;
   }

   value -= see_rec(alists,board,COLOUR_OPP(colour),to,piece_value);

   if (value < 0) value = 0;

   return value;
}

// alist_build()

static void alist_build(alist_t * alist, const board_t * board, int to, int colour) {

   const sq_t * ptr;
   int from;
   int piece;
   int delta;
   int inc;
   int sq;
   int pawn;

   ASSERT(alist!=NULL);
   ASSERT(board!=NULL);
   ASSERT(SQUARE_IS_OK(to));
   ASSERT(COLOUR_IS_OK(colour));

   // piece attacks

   for (ptr = &board->piece[colour][0]; (from=*ptr) != SquareNone; ptr++) {

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

      if (PSEUDO_ATTACK(piece,delta)) {

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

         sq = from;
         do {
            sq += inc;
            if (sq == to) { // attack
               alist_add(alist,from,board);
               break;
            }
         } while (board->square[sq] == Empty);
      }
   }

   // pawn attacks

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

   from = to - (inc-1);
   if (board->square[from] == pawn) alist_add(alist,from,board);

   from = to - (inc+1);
   if (board->square[from] == pawn) alist_add(alist,from,board);
}

// alists_hidden()

static void alists_hidden(alists_t * alists, const board_t * board, int from, int to) {

   int inc;
   int sq, piece;

   ASSERT(alists!=NULL);
   ASSERT(board!=NULL);
   ASSERT(SQUARE_IS_OK(from));
   ASSERT(SQUARE_IS_OK(to));

   inc = DELTA_INC_LINE(to-from);

   if (inc != IncNone) { // line

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

      if (SLIDER_ATTACK(piece,inc)) {

         ASSERT(piece_is_ok(piece));
         ASSERT(PIECE_IS_SLIDER(piece));

         alist_add(alists->alist[PIECE_COLOUR(piece)],sq,board);
      }
   }
}

// alist_add()

static void alist_add(alist_t * alist, int square, const board_t * board) {

   int piece;
   int size, pos;

   ASSERT(alist!=NULL);
   ASSERT(SQUARE_IS_OK(square));
   ASSERT(board!=NULL);

   // insert in MV order

   piece = board->square[square];
   size = ++alist->size; // HACK
   ASSERT(size>0&&size<16);

   for (pos = size-1; pos > 0 && piece > board->square[alist->square[pos-1]]; pos--) { // HACK
      ASSERT(pos>0&&pos<size);
      alist->square[pos] = alist->square[pos-1];
   }

   ASSERT(pos>=0&&pos<size);
   alist->square[pos] = square;
}

// alist_remove()

static void alist_remove(alist_t * alist, int pos) {

   int size, i;

   ASSERT(alist!=NULL);
   ASSERT(pos>=0&&pos<alist->size);

   size = alist->size--; // HACK
   ASSERT(size>=1);

   ASSERT(pos>=0&&pos<size);

   for (i = pos; i < size-1; i++) {
      ASSERT(i>=0&&i<size-1);
      alist->square[i] = alist->square[i+1];
   }
}

// alist_pop()

static int alist_pop(alist_t * alist, const board_t * board) {

   int sq;
   int size;

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

   sq = SquareNone;

   size = alist->size;

   if (size != 0) {
      size--;
      ASSERT(size>=0);
      sq = alist->square[size];
      alist->size = size;
   }

   return sq;
}

}  // namespace engine

// end of see.cpp

