/**********************************************************************
 *
 *   FreeDoko a Doppelkopf-Game
 *    
 *   Copyright (C) 2001-2005  by Diether Knof and Borg Enders
 *
 *   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 can find this license in the file 'gpl.txt'.
 *
 *   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., 59 Temple Place, Suite 330, Boston,
 *   MA  02111-1307  USA
 *
 *  Contact:
 *    Diether Knof dknof@gmx.de
 *    Borg Enders  borg@borgsoft.de
 *
 ********************************************************************/

#include "constants.h"

#include "gametree.h"

#include "exception.h"

#include "VirtualGamesInterface.h"
#include "ai.h"
#include "../../../game/gamepoints.h"
#include "../../../ui/ui.h"

namespace FORK_0_5_4 {

static unsigned counter = 0;

// whether to save the runtime
#define SAVE_RUNTIME

#ifdef RELEASE
#undef SAVE_RUNTIME
#endif

/**
 **
 ** constructor
 **
 ** @param      gametree_interface	the interface
 ** @param      future_limit		the future limit
 ** @param      with_heuristics		whether to use heuristics
 **
 ** @return     -
 **
 ** @version    0.5.4
 **
 ** @author     Diether Knof
 **
 **/
Gametree::Gametree(GametreeInterface const& gametree_interface,
		   unsigned const future_limit,
		   bool const with_heuristics) :
gametree_interface_(&gametree_interface),
end_depth_(0),
  with_heuristics(with_heuristics)
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_INIT,
		"Gametree::Gametree()");

  this->end_depth_calculate(future_limit);

  DEBUG_RETURNING(VOID,
		  INFO_GAMETREE && INFO_INIT,
		  "Gametree::Gametree()");
} // Gametree::Gametree(GametreeInterface const& gametree_interface, unsigned const future_limit)

/**
 **
 ** destructor
 **
 ** @param      -
 **
 ** @return     -
 **
 ** @version    0.5.4
 **
 ** @author     Diether Knof
 **
 **/
Gametree::~Gametree()
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_INIT,
		"Gametree::~Gametree()");

  DEBUG_RETURNING(VOID,
		  INFO_GAMETREE && INFO_INIT,
		  "Gametree::~Gametree()");
} // Gametree::~Gametree()

/**
 **
 ** -> result
 **
 ** @param      -
 **
 ** @return     the gametree interface
 **
 ** @version    0.5.4
 **
 ** @author     Diether Knof
 **
 **/
GametreeInterface const&
Gametree::gametree_interface() const
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_VALUE,
		"Gametree::gametree_interface()");

  DEBUG_RETURNING(*(this->gametree_interface_),
		  INFO_GAMETREE && INFO_VALUE,
		  "Gametree::gametree_interface()");
} // GametreeInterface const& Gametree::gametree_interface() const

/**
 **
 ** -> result
 **
 ** @param      -
 **
 ** @return     the maximal depth to calc into the future (in tricks)
 **
 ** @version    0.5.4
 **
 ** @author     Diether Knof
 **
 **/
unsigned const&
Gametree::end_depth() const
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_VALUE,
		"Gametree::end_depth()");

  DEBUG_RETURNING(this->end_depth_,
		  INFO_GAMETREE && INFO_VALUE,
		  "Gametree::end_depth()");
} // unsigned const& VirtualGames::end_depth() const

/**
 **
 ** Calculates and sets the end depth (how many tricks in the future are
 ** to be calculated).
 **
 ** @param      future_limit	the future limit
 **
 ** @return     the calculated end depth
 **
 ** @version    0.5.4
 **
 ** @author     Diether Knof
 **
 **/
unsigned const&
Gametree::end_depth_calculate(unsigned future_limit)
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_VALUE,
		"Gametree::end_depth_calculate(future_limit)");

  this->end_depth_ = 0;

  { // count the number of card-combinations for the current trick
    Trick const& current_trick
      = this->gametree_interface().game().trick_current();
    for (unsigned i = current_trick.actcardno();
	 i < this->gametree_interface().game().playerno();
	 i++)
      future_limit /= this->gametree_interface().handofplayer(current_trick.player_of_card(i)
							     ).validcards(current_trick).cardsnumber();
  } // count the number of card-combinations for the current trick

  // calculate for all following tricks the number of cards of each player
  while (future_limit > 0) {
    this->end_depth_ += 1;
    if (this->end_depth_ == this->gametree_interface().game().tricks_remaining_no())
      break;

    for (vector<Player*>::const_iterator
	 player = this->gametree_interface().game().player_begin();
	 player != this->gametree_interface().game().player_end();
	 player++) {
      if (this->gametree_interface().game().trick_current().cardno_of_player(**player)
	  >= this->gametree_interface().game().trick_current().actcardno())
	// the player still has to play a card in the current trick
	future_limit /= (this->gametree_interface().handofplayer(**player).cardsnumber()
			 - this->end_depth_);
      else
	// the player has already played a card, so he has one card less 
	// on the hand
	future_limit /= (this->gametree_interface().handofplayer(**player).cardsnumber()
			 - (this->end_depth_ - 1));
    } // for (player \in this->gametree_interface.game().player)
  } // while (future_limit > 0)


  this->end_depth_
    = min(this->end_depth(),
	  ( (this->gametree_interface().last_trick_to_calculate()
	     >= this->gametree_interface().game().trick_current_no())
	    ? (this->gametree_interface().last_trick_to_calculate()
	       - this->gametree_interface().game().trick_current_no())
	    : 0)
	 );

  if (INFO_GAMETREE)
    if (!this->gametree_interface().game().isvirtual())
      COUT << "Player " << this->gametree_interface().no() << ": "
	<< "Gametree: "
	<< "end depth = " << this->end_depth() << endl;

  // always calculate the current trick
  if (this->end_depth() == 0)
    this->end_depth_ = 1;

  DEBUG_RETURNING(this->end_depth(),
		  INFO_GAMETREE && INFO_VALUE,
		  "Gametree::end_depth_calculate(future_limit)"
		  " = " << this->end_depth());
} // unsigned const& VirtualGames::end_depth_calculate(unsigned future_limit)

/**
 **
 ** Calculates the best card (max-min-procedure).
 **
 ** @param      -
 **
 ** @return     the best card to play
 **
 ** @version    0.5.4
 **
 ** @author     Diether Knof
 **
 **/
Card
Gametree::best_card() const
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_VALUE,
		"Gametree::best_card()");

  Card card_best;
  int modi_best = INT_MIN;
  Hand const& valid_hand = this->gametree_interface().hand().validcards(this->gametree_interface().game().trick_current()
								       );
  counter = 0;

#ifdef SAVE_RUNTIME
  // for measuring the runtime
  ofstream ostr("GameTree.time", ios::app);
  clock_t const time_begin = clock();
#endif
  // now calculate for all cards the modi
  for (unsigned c = 0; c < valid_hand.cardsnumber(); c++) {
    // create a virtual game and set the players
    Game virt_game(this->gametree_interface().game());
    // Create new players.
    // The hand is set during the rekursion.
    vector<Player*> player_virt;
    for (vector<Player*>::const_iterator player
	 = this->gametree_interface().game().player_begin();
	 player != this->gametree_interface().game().player_end();
	 player++) {
      if (this->with_heuristics) {
	player_virt.push_back(new Ai(**player));
	dynamic_cast<Aiconfig*>(player_virt.back())->aitype_set(AITYPE::NO_CHOOSEBESTCARD);
      } else { // if !(this->with_heuristics)
	player_virt.push_back((*player)->Player::clone());
      } // if !(this->with_heuristics)
      player_virt.back()->no_set(player_virt.size() - 1);
    } // for (player)
    virt_game.players_set(player_virt);

    // set all hands
    for (vector<Player*>::const_iterator player = virt_game.player_begin();
	 player != virt_game.player_end();
	 player++)
      (*player)->hand_set(this->gametree_interface().handofplayer(this->gametree_interface().game().player((*player)->no())));

    //.st play the card
    virt_game.trick_current() += valid_hand.card(c);
    virt_game.player_current().hand().playcard(valid_hand.card(c));
    virt_game.teaminfo_update();
    virt_game.player_current_p
      = &virt_game.player_following(virt_game.player_current());

    int const modi = this->maxmin_modi(virt_game, this->end_depth());
    if (INFO_GAMETREE)
      if (!this->gametree_interface().game().isvirtual())
	COUT << valid_hand.card(c) << '\t' << modi << endl;

    // test, whether the card is better
    if ( (modi > modi_best)
	 || ( (modi == modi_best)
	      && (valid_hand.card(c).less(card_best,
					  this->gametree_interface().game()))
	    )
       ) {
      modi_best = modi;
      card_best = valid_hand.card(c);
    } // if (modi > modi_best)

    // delete the virtual players
    for (vector<Player*>::iterator player = player_virt.begin();
	 player != player_virt.end();
	 player++)
      delete *player;

    if (::game_status != GAMESTATUS::GAME_PLAY)
      break;
  } // for (c < valid_hand.cardsnumber())

#ifdef SAVE_RUNTIME
  if (counter >= 2000) {
    unsigned const used_time = ((clock() - time_begin)
				/ (CLOCKS_PER_SEC / 1000));
    ostr << setw(8) << counter
      << "\t" << setw(8) << used_time
      << "\t" << setw(8) << (used_time * 1000 / counter) 
      << endl;
  } // if (counter >= 200)
#endif // #ifdef SAVE_RUNTIME

#ifdef POSTPHONED
  DEBUG_ASSERTION(((counter
		    <= this->gametree_interface().value(Aiconfig::FUTURE_LIMIT))
		   || (this->end_depth() == 0)),
		  "VirtualGames::best_card():\n"
		  "  counter > future limit: "
		  << counter << " > "
		  << this->gametree_interface().value(Aiconfig::FUTURE_LIMIT)
		 );
#endif

  DEBUG_RETURNING(card_best,
		  INFO_GAMETREE && INFO_VALUE,
		  "Gametree::best_card()");
} // Card VirtualGames::best_card() const

/**
 **
 ** Returns the maximal minimum modi.
 ** From players of the same team, the best modi is returned,
 ** for players of the opposite team, the worst modi is returned.
 **
 ** @param      virt_game	the virtual game
 ** @param      tricks_to_calc	how many tricks to be calced
 **
 ** @return     the maximal minimum modi
 **
 ** @version    0.5.4
 **
 ** @author     Diether Knof
 **
 **/
int
Gametree::maxmin_modi(Game& virt_game, unsigned const tricks_to_calc) const
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_VALUE,
		"Gametree::maxmin_modi(virt_game, tricks_to_calc)");

  counter += 1;

  ::ui->update();
  if (::game_status != GAMESTATUS::GAME_PLAY)
    DEBUG_RETURNING(0,
		    INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		    "Gametree::maxmin_modi(virt_game, tricks_to_calc)");

  // the correspoding virtual player to the ai
  // ToDo: 'virt_Game.player()' ist ein Player, kein Ai.
  Ai const& virt_ai = virt_game.player(this->gametree_interface().no());
  // the team of the ai
  Team const ai_team = virt_ai.team();


  if (virt_game.trick_current().isfull()) {
    // evaluate the trick
    virt_game.evaluatetrick();
#ifndef POSTPHONED
    if (tricks_to_calc > 1) {
      virt_game.trick_p.push_back(Trick(virt_game.player_current()));
      DEBUG_RETURNING(maxmin_modi(virt_game, tricks_to_calc - 1),
		      INFO_GAMETREE && INFO_VALUE,
		      "Gametree::maxmin_modi(virt_game, tricks_to_calc)");
    } else { // if !(tricks_to_calc > 1)
      int modi = 0;
      //
      // game finished, the main reason is the final result...
      if (virt_game.tricks_remaining_no() == 0) {
	GamePoints gp(&virt_game);
	if (gp.winnerteam() == ai_team)
	  modi = gp.points();
	else
	  modi = -gp.points();
      } else { // if !(virt_game.tricks_remaining_no() == 0)
	// add the modi for all tricks
	for (unsigned t = this->gametree_interface().game().trick_current_no();
	     t < virt_game.trick_current_no();
	     t++) {
	  modi += this->modi(virt_game.trick(t),
			     virt_ai.team(),
			     virt_game.trick(t).card_of_player(virt_ai));
	}
	// add some points, if the own team is in the back,
	if (virt_game.trick_current_no() < virt_game.trickno() - 1) {
	  if (virt_game.trick_current().player_of_card(virt_game.playerno()
						       - 1).team()
	      == this->gametree_interface().team())
	    modi += 5;
	  else
	    modi -= 5;
	} // if (virt_game.trick_current_no() < virt_game.trickno() - 1)
      } // if !(virt_game.tricks_remaining_no() == 0)

      DEBUG_RETURNING(modi,
		      INFO_GAMETREE && INFO_VALUE,
		      "Gametree::maxmin_modi(virt_game, tricks_to_calc)");
    } // if (tricks_to_calc > 1)
#else
    int modi = this->modi(virt_game.trick_current(),
			  ai_team,
			  virt_game.trick_current().card_of_player(virt_ai)
			 );

    if (tricks_to_calc > 1) {
      virt_game.trick_p.push_back(Trick(virt_game.player_current()));
      modi += maxmin_modi(virt_game, tricks_to_calc - 1);
    } else { // if !(tricks_to_calc > 1)
    } // if !(tricks_to_calc > 1)

    DEBUG_RETURNING(modi,
		    INFO_GAMETREE && INFO_VALUE,
		    "Gametree::maxmin_modi(virt_game, tricks_to_calc)");
#endif // #ifndef POSTPHONED
  } // if (!virt_game.trick_current().isfull())

  // Here we are in the middle of a trick.
  // Try all cards and take the worst/best modi if the player is in the
  // opposite/same team.

  //  The player is in the same team, if the team of the ai is known to all
  //  and the team of the player is the team of the ai.
  bool same_team = false;

  // first look, whether the team of the ai is known to all
  if (virt_game.teaminfo(virt_ai) == ai_team) {
    // next test, whether the team of the player is the same of the ai
    if (virt_game.teaminfo(virt_game.player_current()) == ai_team)
      same_team = true;
    else if ((virt_game.teaminfo(virt_game.player_current()) == TEAM::UNKNOWN)
	     && (this->gametree_interface().teamofplayer(virt_game.player(virt_game.player_current().no()))
		 == ai_team))
      // The team of the player is not known,
      // but the interface says, that the teams are the same.
      same_team = true;
  } // if (team of virt_ai known)


  // Now test all the cards and calc the maxmin modus


  // this ensures, that the modi can be improved
  int maxmin_modi = (same_team ? INT_MIN : INT_MAX);
  Card card = Card();

  // first set the hand of the player
  Hand const hand = virt_ai.handofplayer(virt_game.player_current());
  Hand const valid_hand = hand.validcards(virt_game.trick_current());
  if (this->with_heuristics) {

    // update/set the hand of the current player
    virt_game.player_current().hand_set(hand);

    card = dynamic_cast<Ai&>(virt_game.player_current()).card_get();
    if (card && false) {
      // play the card
      virt_game.trick_current() += card;
      virt_game.player_current().hand().playcard(card);
      virt_game.teaminfo_update();
      virt_game.player_current_p
	= &virt_game.player_following(virt_game.player_current());

      DEBUG_RETURNING(this->maxmin_modi(virt_game, tricks_to_calc),
		      INFO_GAMETREE && INFO_VALUE,
		      "Gametree::maxmin_modi(virt_game, tricks_to_calc)");
    } // if (card)

  } // if (this->with_heuristics)
  // now test all cards
  for (unsigned c = 0; c < valid_hand.cardsnumber(); c++) {
    // create a new virtual game
    Game* const virt_game_2 = new Game(virt_game);
    vector<Player*> player_virt;
    for (vector<Player*>::const_iterator player
	 = virt_game.player_begin();
	 player != virt_game.player_end();
	 player++) {
      player_virt.push_back((*player)->clone());
      player_virt.back()->no_set(player_virt.size() - 1);
      player_virt.back()->hand_set(virt_ai.handofplayer(virt_ai.game().player(player_virt.size() - 1)));
    } // for (player)
    virt_game_2->players_set(player_virt);


    // update/set the hand of the current player
    virt_game_2->player_current().hand_set(hand);

    // now play the card
    virt_game_2->trick_current() += valid_hand.card(c);
    virt_game_2->player_current().hand().playcard(valid_hand.card(c));
    virt_game_2->teaminfo_update();
    virt_game_2->player_current_p
      = &virt_game_2->player_following(virt_game_2->player_current());

    try {
      // get the modi through the recursion
      int const modi = this->maxmin_modi(*virt_game_2, tricks_to_calc);
      // think about this contition ;-)
      if ((modi > maxmin_modi) == same_team) {
	maxmin_modi = modi;
	card = valid_hand.card(c);
      }
    } // try
    catch (InvalidGameException exception) {
      // nothing to do, because 'maxmin_modi' cannot get changed
      DEBUG_CAUGHT();
    } // catch()

    // delete the created virtual players and virtual game
    for (vector<Player*>::iterator player = player_virt.begin();
	 player != player_virt.end();
	 player++)
      delete *player;
    delete virt_game_2;

    if (::game_status != GAMESTATUS::GAME_PLAY)
      break;
  } // for (c < valid_hand.cardsnumber())

  DEBUG_RETURNING(maxmin_modi,
		  INFO_GAMETREE && INFO_VALUE,
		  "Gametree::maxmin_modi(virt_game, tricks_to_calc)");
} // int VirtualGames::maxmin_modi(Game& virt_game, unsigned const tricks_to_calc) const

/**
 **
 ** -> result
 ** (copy from WVirtualGames)
 **
 ** @param      trick   the trick to be examined
 ** @param      team    the team which should make points
 ** @param      card    the card played by the player
 **
 ** @return     the weighting of the trick for the team 'team'
 **
 ** @version    0.4.4
 **
 ** @author     Borg Enders
 **
 ** @todo       perhaps points for getting the fox home
 **
 **/
int
Gametree::modi(Trick const& trick, Team const& team, Card const& card) const
{
  DEBUG_CALLING(INFO_GAMETREE && INFO_VALUE,
		"Gametree::modi()");

  Game const& game_virt = trick.game();
  Player const& player = game_virt.player(this->gametree_interface().no());
  // Ai const& ai = static_cast<const Ai&>(game_virt.player( this->gametree_interface().no() ));
  Team const winnerteam = trick.winnerplayer().team();

  int modi = 0;

  if (winnerteam == team)
    modi += trick.points();
  else
    modi -= trick.points();

  // have played a very high card
  if (card.isswine()
      || card.ishyperswine()
      || card.isdolle())
    modi -= 14; // previous values: 23
  if (card.isswine()) // so that a dolle is prefered to a swine
    modi -= 2;

  if ( card.istrump() )
    modi-= 4; 

  // have played a high card
  if (this->gametree_interface().value(Aiconfig::LIMITHIGH).less(card,
								 game_virt))
    modi -= 3;

  // create the teams-vector
  vector<Team> teams;
  for (vector<Player*>::const_iterator
       player = game_virt.player_begin();
       player != game_virt.player_end();
       player++) 
  {
#ifdef POSTPHONED
    // determine team of player from ai out of virtual game
    Team const t = ai.teamofplayer( **player );
    if ( t != TEAM::UNKNOWN)
    {
      teams.push_back( maybe_to_team( t ) );
    } else
    {
      teams.push_back( opposite(ai.team()) );
    }
#else
    Team const t = (*player)->game().teaminfo(**player);
    if (t != TEAM::UNKNOWN)
      teams.push_back(t);
    else
      teams.push_back(opposite(team));
#endif

  } // for (player)

  if (maybe_to_team(winnerteam) == team)
  {
    Specialpointsvector spv=trick.specialpoints(game_virt.type(),
						teams,
						(game_virt.tricks_remaining_no()
						 == 0));
    modi += (30
	     * Sum_of_Specialpoints(spv,
				    winnerteam,
				    trick.startplayer().game()));

  }
  else
  {
    Specialpointsvector spv=trick.specialpoints(game_virt.type(),
						teams,
						(game_virt.tricks_remaining_no()
						 == 0));
    modi -= (30
	     * Sum_of_Specialpoints(spv,
				    winnerteam,
				    trick.startplayer().game()));
  }

  // bad style to play fox as first card
  if( card.isfox() && trick.card(0) == card)
  {
    modi -= 90; // previous	
  }


  for (unsigned c = 0; c < trick.actcardno(); c++) {
    // test, whether the fox has been brought home

    if (trick.card(c).isfox() 
	&& (trick.player_of_card(c).team() == winnerteam))
    {

      modi += 40 * ((team == trick.player_of_card(c).team())
		    ? +1
		    : -1);                    
    }
  } // for (c < trick.actcardno())


  // don't play two times the same card
  for (unsigned c = 0; c < trick.actcardno(); c++) {
    if(    trick.card(c) == card 
	   && (trick.cardno_of_player(trick.player_of_card(c))
	       < trick.cardno_of_player( player ) ))
    {
      modi+= card.value() * ((team == trick.player_of_card(c).team())
			     ? -1
			     : 0);   
    }           
  }

  DEBUG_RETURNING(modi,
		  INFO_GAMETREE && INFO_VALUE,
		  "Gametree::modi()");
} // int Gametree::modi(Trick const& trick, Team const& team, Card const& card)
} // namespace FORK_0_5_4
