/* Yo Emacs, this -*- C++ -*-

  Copyright (C) 2001 Jens Hoefkens
  jens@hoefkens.com

  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 2 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, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include "kbgstatus.h"
#include "kbgstatus.moc"

#include <stdlib.h>
#include <stdio.h>


/*
 * Parse a rawboard description from FIBS and initialize members.
 */
KBgStatus::KBgStatus(const QString &rawboard)
{
	/*
	 * This is the format string from hell...
	 */
	const char *format = ("%*[^:]%*[:]%99[^:]%*[:]%99[^:]%*[:]%i%*[:]%i%*[:]%i%*[:]"
			      "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]" 
			      "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
			      "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
			      "%i%*[:]"
			      "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
			      "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
			      "%i%*[:]%i%*[:]");

	char opponent[100], player[100];

	QString cap;

	int board[26], ldice[2][2], maydouble[2], scratch[4], onhome[2], onbar[2];
	int points[2];
	int tomove, lturn, color, cube, direction, redoubles, bar, home, length;
	
	// split the incoming line at colons - latin1() is fine, since the string comes from FIBS.
	sscanf (rawboard.latin1(), format,
		player, opponent, 
		&length,
		&points[0], &points[1],
		&board[ 0], &board[ 1], &board[ 2], &board[ 3],	&board[ 4], &board[ 5], 
		&board[ 6], &board[ 7],	&board[ 8], &board[ 9], &board[10], &board[11],
		&board[12], &board[13], &board[14], &board[15],	&board[16], &board[17], 
		&board[18], &board[19],	&board[20], &board[21], &board[22], &board[23],
		&board[24], &board[25], 
		&lturn,
		&ldice[US  ][0], &ldice[US  ][1],	&ldice[THEM][0], &ldice[THEM][1],
		&cube,
		&maydouble[US], &maydouble[THEM],
		&doubled_,
		&color,
		&direction,
		&home, &bar,
		&onhome[US], &onhome[THEM],  // on home
		&onbar[US],  &onbar[THEM],   // on bar
		&tomove,
		&scratch[2], &scratch[3],    // forced move & did crawford
		&redoubles);

	player_[US]   = player;
	player_[THEM] = opponent;

	setCube(cube, maydouble[US], maydouble[THEM]);
	setDirection(direction);
	setColor(color);
	for (int i = 1; i < 25; i++) {
		if (board[i] == 0 || color == board[i]/abs(board[i]))
			setBoard(i, US, abs(board[i]));
		else
			setBoard(i, THEM, abs(board[i]));
	}
	setDice(US  , 0, ldice[US  ][0]); 
	setDice(US  , 1, ldice[US  ][1]);
	setDice(THEM, 0, ldice[THEM][0]); 
	setDice(THEM, 1, ldice[THEM][1]);	

	setHome(US,   onhome[US  ]); 
	setHome(THEM, onhome[THEM]);

	setBar(US,   board[   bar]); 
	setBar(THEM, board[25-bar]);

	setPoints(US,   points[0]);
	setPoints(THEM, points[1]);
	
	if (lturn == 0)
		setLength(-1);
	else
		setLength(length);

	int t = lturn*color;
	if (t  > 0) setTurn(US);
	if (t  < 0) setTurn(THEM);
	if (t == 0) setTurn(NONE);
}

/*
 * Constructor initializes the status to an empty board with cube one
 * and empty dice.
 */
KBgStatus::KBgStatus()
	: QObject()
{
	/*
	 * Initialize members
	 */
	for (int i = 0; i < 26; ++i)
		setBoard(i, US, 0);

	for (int i = US; i <= THEM; i++) {
		setDice  (i, 0, 0);
		setDice  (i, 1, 0);
		setHome  (i, 0);
		setBar   (i, 0);
		setPoints(i, -1);
		setPlayer(i, QString::null);
	}
	setColor(White, US);
	setCube(1, BOTH); // also initializes maydouble
	setDirection(1);
	setLength(-1);
	setTurn(NONE);

	// initialize members without assignment functions
	doubled_ = 0;
}

/*
 * Copy constructor calls private utility function.
 */
KBgStatus::KBgStatus(const KBgStatus &rhs)
	: QObject()
{
	copy(rhs);
}

/*
 * Destructor
 */
KBgStatus::~KBgStatus()
{
	// nothing to do
}

/*
 * Assignment operator shares a lot of code with the copy
 * constructor.
 */
KBgStatus& KBgStatus::operator=(const KBgStatus &rhs)
{
	if (this == &rhs) return *this;
	copy(rhs);
	return *this;
}

void KBgStatus::copy(const KBgStatus &rhs)
{
	for (int i = 0; i < 26; i++)
		board_[i] = rhs.board_[i];

	for (int i = US; i <= THEM; i++) {
		
		home_[i]    = rhs.home_[i];
		bar_ [i]    = rhs.bar_ [i];
		dice_[i][0] = rhs.dice_[i][0];
		dice_[i][1] = rhs.dice_[i][1];

		maydouble_[i] = rhs.maydouble_[i];
		player_   [i] = rhs.player_   [i];
		points_   [i] = rhs.points_   [i];
	}

	cube_      = rhs.cube_;
	direction_ = rhs.direction_;
	color_     = rhs.color_;
	turn_      = rhs.turn_;
	doubled_   = rhs.doubled_;
}


/*
 * Access functions 
 */
int KBgStatus::board(const int &i) const
{
	return ((0 < i && i < 25) ? color_*board_[i] : 0);
}

int KBgStatus::home(const int &w) const
{
	return ((w == US || w == THEM) ? color_*home_[w] : 0);
}

int KBgStatus::bar(const int &w) const
{
	return ((w == US || w == THEM) ? color_*bar_[w] : 0);
}

int KBgStatus::color(const int &w) const
{
	return ((w == THEM) ? -color_ : color_);
}

int KBgStatus::direction() const
{
	return direction_;
}

int KBgStatus::dice(const int &w, const int &n) const
{
	if ((w == US || w == THEM) && (n == 0 || n == 1))
		return dice_[w][n];
	else 
		return 0;
}

int KBgStatus::cube(const int &w) const
{
	if (w == US || w == THEM)
		return ((maydouble_[w]) ? cube_ : -cube_);
	return 0;
}

int KBgStatus::points(const int& w) const
{
	return ((w == US || w == THEM) ? points_[w] : -1);
}

QString KBgStatus::player(const int &w) const
{
	return ((w == US || w == THEM) ? player_[w] : QString::null);	
}

int KBgStatus::length() const
{
	return length_;
}

int KBgStatus::turn() const
{
	return turn_;
}

bool KBgStatus::doubled() const
{
	return doubled_;
}


/*
 * Assignment functions
 */
void KBgStatus::setBoard(const int &i, const int &w, const int &v)
{	
	if (0 < i && i < 25) {
		if (w == US)
			board_[i] =  abs(v);
		else if (w == THEM)
			board_[i] = -abs(v);
	}
}

void KBgStatus::setHome(const int &w, const int &v)
{
	if (w == US)
		home_[w] =  abs(v);
	else if (w == THEM)
		home_[w] = -abs(v);		
}

void KBgStatus::setBar(const int& w, const int& v)
{
	if (w == US)
		bar_[w] =  abs(v);
	else if (w == THEM)
		bar_[w] = -abs(v);
}

void KBgStatus::setColor(const int &c, const int &w)
{
	if (w == US)
		color_ = ((c < 0) ? Black : White);
	else if (w == THEM)
		color_ = ((c < 0) ? White : Black);
}

void KBgStatus::setDirection(const int &dir)
{
	direction_ = ((dir < 0) ? -1 : +1);
}

void KBgStatus::setDice(const int &w, const int &n, const int &v)
{
	if ((w == US || w == THEM) && (n == 0 || n == 1)) {
		if (0 <= v && v <= 6)
			dice_[w][n] = v;
		else
			dice_[w][n] = 0;
	}
}

void KBgStatus::setCube(const int &c, const bool &us, const bool &them)
{
	int             w = NONE;
	if (us)         w = US;
	if (them)       w = THEM;
	if (us && them)	w = BOTH;
	setCube(c, w);
}

void KBgStatus::setCube(const int &c, const int &w)
{
	// assume that int has at least 32 bits...
	for (int i = 0; i < 31; i++) {
		if (1<<i == (cube_ = c)) break;
		cube_ = 0;
	}
	maydouble_[US  ] = (w == US   || w == BOTH);
	maydouble_[THEM] = (w == THEM || w == BOTH);
}

void KBgStatus::setPoints(const int &w, const int &p)
{
	if (w == US || w == THEM)
		points_[w] = p;
}

void KBgStatus::setPlayer(const int &w, const QString &name)
{
	if (w == US || w == THEM) 
		player_[w] = name;
}

void KBgStatus::setLength(const int &l)
{
	length_ = l;
}

void KBgStatus::setTurn(const int &w) 
{
	if (w == US || w == THEM || w == BOTH)
		turn_ = w;
}
	

/*
 * Utility functions
 */
int KBgStatus::moves() const
{
	int start, dir;
	
	/*
	 * Return an error if it isn't anybodies turn.
	 */
	if ((turn() != US) && (turn() != THEM)) 
		return -1;

	/*
	 * Determine tha direction of the current move
	 */
	if ((turn() == US && direction() < 0) || (turn() == THEM && direction() > 0)) {
		start = 25;
		dir   = -1;
	} else {
		start =  0;
		dir   =  1;
	}

	/*
	 * Get the current dice transferred into the move[] array. The
	 * final zero is a marker
	 */
	int move[5] = {0, 0, 0, 0, 0};
	move[0] = dice(turn(), 0);
	move[1] = dice(turn(), 1);
	if (move[0] == move[1]) {
		move[3] = move[2] = move[0];
		// saves some work further down
		if (move[0] == 0) 
			return 0;

	}

	bool doubledice = (move[3] != 0);
	int count = 4;

	/*
	 * Get a copy of ourselves. That way we can mess around with
	 * the internals of the game.
	 */
	KBgStatus sc(*this);

	/*
	 * Start with getting all checkers off the bar
	 */
	while (count > 0 && sc.bar(turn()) != 0) {		
		if (move[--count] != 0) {			
			if (color(turn())*sc.board(start+dir*move[count]) >= -1) {
				sc.setBar(turn(), abs(sc.bar(turn()))-1);
				sc.setBoard(start + dir*move[count], turn(), 1);
				move[count] = 0;
			}
		}
	}

	/*
	 * Collect remaining moves in the beginning of the move array 
	 */
	int j = 0;
	for (int i = 0; i < 4; i++) {
		if ((move[j] = move[i])) 
			++j;
		if (i > j) move[i] = 0;
	}


	/*
	 * Find number of remaining moves
	 */
	int moves = 0;
	move[4] = 0;
	while (move[moves])
		++moves;
	
	/*
	 * Done or no more moves because the bar is not empty
	 */
	if (sc.bar(turn()) != 0 || move[0] == 0)
		return (move[3] ? 4 - moves : 2 - moves);

	/*
	 * Try to find possible moves on the board 
	 */
	if (moves == 1 || move[0] == move[1]) {

		/*
		 * Order doesn't matter, all moves are equal
		 */
		while (--moves >= 0 && movePossible(sc, move[moves], start, dir));
		moves++;
		return (doubledice ? 4 - moves : 2 - moves);

	} else {

		/*
		 * Order does matter; try both ways.
		 */
		moves = 0;
		for (int i = 0; i < 25; i++) {
			if (movePossible(sc, move[0], start + i*dir, dir)) {
				moves = 1;
				if (movePossible(sc, move[1], start, dir)) {
					return 2;
				}
			}
			// Restore scratch copy...
			sc = *this;
		}
		for (int i = 0; i < 25; i++) {

			if (movePossible(sc, move[1], start + i*dir, dir)) {
				moves = 1;
				if (movePossible(sc, move[0], start, dir)) {
					return 2;
				}
			}
			// Restore scratch copy...
			sc = *this;
		}
		return moves;
	}
}

bool KBgStatus::movePossible(KBgStatus &sc, int a, int start, int dir) const
{       
	/*
	 * Determine where the first checker in moving direction is
	 * located
	 */
	int first = (dir > 0) ?  1 : 24;
	int last  = (dir > 0) ? 25 :  0;
	while (first != last && color(turn())*sc.board(first) <= 0)
		first += dir;

	/*
	 * Are we moving off ?
	 */
	bool off = false;
	if ((dir > 0 && first > 18) || (dir < 0 && first <  7)) 
		off = true;

	/*
	 * Find a move by exhaustive search.
	 */
	while (true) {

		start += dir;
		int final = start+dir*a;
		
		/*
		 * Make absolutely sure that the loop terminates eventually
		 */
		if (start <= 0 || start >= 25)
			return false;

		if (color(turn())*sc.board(start) > 0) {
			
			if (0 < final && final < 25 && color(turn())*sc.board(final) >= -1) {
				sc.setBoard(start, turn(), abs(sc.board(start)) - 1);
				sc.setBoard(final, turn(), abs(sc.board(final)) + 1);
				return true;
			} else if (off && (final == 0 || final == 25)) {
				sc.setBoard(start, turn(), abs(sc.board(start)) - 1);
				sc.setHome(turn(), abs(sc.home(turn())) + 1);
				return true;
			} else if (off && first == start && (final > 24 || final < 1)) {
				sc.setBoard(start, turn(), abs(sc.board(start)) - 1);
				sc.setHome(turn(), abs(sc.home(turn())) + 1);
				return true;
			}
		}
	}
}

// EOF
