/**********************************************************************
 *
 *   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 <fstream>

#include "w_monte_carlo.h"


#include "VirtualGamesInterface.h"
#include "trickweighting.h"
#include "../ai.dummy/ai.h"
#include "../../party/party.h"
#include "../../game/game.h"
#include "../../game/gamepoints.h"
#include "../../game/exception.h"
#include "../../misc/setting.h"
#include "../../ui/ui.h"

// whether to save the runtime
//#define SAVE_RUNTIME

#ifdef RELEASE
#undef SAVE_RUNTIME
#endif
/**
 **
 ** constructor
 **
 ** @param      vgi              the vgi
 ** @param      future_limit    the future limit
 **
 ** @return     -
 **
 ** @version    0.4.4
 **
 ** @author     Diether Knof
 **
 **/
WMonteCarlo::WMonteCarlo(VirtualGamesInterface const& vgi, unsigned const& future_limit) :
vgi_p(&vgi),
  future_limit_p(future_limit),
  player_virt_p(),
weighting_p()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT,
		"WMonteCarlo::WMonteCarlo()");

  DEBUG_RETURNING(VOID,
		  INFO_W_VIRTUAL_GAMES && INFO_INIT,
		  "WMonteCarlo::WMonteCarlo()");
} // WMonteCarlo::WMonteCarlo(unsigned const& future_limit)

/**
 **
 ** destructor
 **
 ** @param      -
 **
 ** @return     -
 **
 ** @version    0.4.4
 **
 ** @author     Diether Knof
 **
 **/
WMonteCarlo::~WMonteCarlo()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT,
		"WMonteCarlo::~WMonteCarlo()");

  for (vector<Player*>::iterator player = this->player_virt_p.begin();
       player != this->player_virt_p.end();
       player++)
    delete *player;

  DEBUG_RETURNING(VOID,
		  INFO_W_VIRTUAL_GAMES && INFO_INIT,
		  "WMonteCarlo::~WMonteCarlo()");
} // WMonteCarlo::~WMonteCarlo()

/**
 **
 ** -> result
 **
 ** @param      -
 **
 ** @return     the vgi
 **
 ** @version    0.4.4
 **
 ** @author     Diether Knof
 **
 **/
VirtualGamesInterface const&
WMonteCarlo::vgi() const
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		"WMonteCarlo::vgi()");

  DEBUG_RETURNING(*(this->vgi_p),
		  INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		  "WMonteCarlo::vgi()");
} // MonteCarloInterface const& MonteCarlo::vgi() const

/**
 **
 ** -> result
 **
 ** @param      -
 **
 ** @return     the limit of tricks to calc
 **
 ** @version    0.4.4
 **
 ** @author     Diether Knof
 **
 **/
unsigned const&
WMonteCarlo::future_limit() const
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		"WMonteCarlo::future_limit()");

  DEBUG_RETURNING(this->future_limit_p,
		  INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		  "WMonteCarlo::future_limit()");
} // unsigned const& MonteCarlo::future_limit() const


/**
 **
 ** -> result
 **
 ** @param      -
 **
 ** @return     the hand to be examined
 **
 ** @version    0.4.4
 **
 ** @author     Diether Knof
 **
 **/
Hand const&
WMonteCarlo::hand() const
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		"WMonteCarlo::hand()");

  DEBUG_RETURNING(this->hand_p,
		  INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		  "WMonteCarlo::hand()");
} // Hand const& MonteCarlo::hand() const

/**
 **
 ** creates the game and the players.
 **
 ** @param      -
 **
 ** @return     -
 **
 ** @exception	InvalidGameException	the virtual game is invalid
 **
 ** @version    0.5.4
 **
 ** @author     Borg Enders
 ** @author     Diether Knof
 **
 ** @todo	future limit for the virtual players
 **
 **/
void
WMonteCarlo::init()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT,
		"WMonteCarlo::init()");


  // free the memory
  for (vector<Player*>::iterator player = this->player_virt_p.begin();
       player != this->player_virt_p.end();
       player++)
    delete *player;
  this->player_virt_p.clear();

  // set the cards to the valid cards
  this->hand_p = this->vgi().hand().validcards(this->vgi().game().trick_current()
					      );
  // set the weightings to 0
  this->weighting_p = vector<long long int>(this->hand().cardsnumber(), 0);

  // create the virtual game and the players
  // (the players have a hand independent of the hand of the vgi, so that
  //  reordering the card is no problem)
  for (vector<Player*>::const_iterator player
       = this->vgi().game().player_begin();
       player != this->vgi().game().player_end();
       player++) {

    this->player_virt_p.push_back(new AiDummy(static_cast<AiDummy const&>(this->vgi().game().player(this->vgi().no()))));

    // if the player is not the vgi, some values have to changed
    if (**player != this->vgi().game().player(this->vgi().no())) {
      unsigned const no = this->player_virt_p.size() - 1;
      AiDummy& ai_virtual = static_cast<AiDummy&>(*(this->player_virt_p.back()));

      // set the playernumber
      ai_virtual.no_set(no);

      // set the hand
      ai_virtual.hand_set(this->vgi().handofplayer(**player) );
      DEBUG_ASSERTION((ai_virtual.hand().cardsnumber() + 1
		       // hack (if the player has already played a card in the trick)
		       >= this->vgi().game().tricks_remaining_no()),
		      "WMonteCarlo::init():\n"
		      "  the hand of the virtual player " << ai_virtual.no()
		      << " is too small: "
		      << ai_virtual.hand().cardsnumber()
		      << " < " << this->vgi().game().tricks_remaining_no()
		      << ":\n"
		      << ai_virtual.hand());
    
      // set the teaminfo
      // like teams
      for (unsigned p = 0; p < this->vgi().game().playerno(); p++) {
	if (this->vgi().teamofplayer(this->vgi().game().player(p))
	    == TEAM::UNKNOWN) {  
	  ai_virtual.teaminfo_p[p]
	    = opposite(this->vgi().team());
	} else {
	  ai_virtual.teaminfo_p[p]
	    = maybe_to_team(this->vgi().teamofplayer(this->vgi().game().player(p)));   
	} // if ((this->vgi().teamofplayer(this->vgi().game().player(p)) == TEAM::UNKNOWN))


      } // for (p)
    } // if (**player != this->vgi())
  } // for (player)

  DEBUG_RETURNING(VOID,
		  INFO_W_VIRTUAL_GAMES && INFO_INIT,
		  "WMonteCarlo::init()");
} // void WMonteCarlo::init()


/**
 **
 ** -> result
 **
 ** @param      -
 **
 ** @return     the card, that makes the most points
 **
 ** @version    0.5.4
 **
 ** @author     Borg Enders
 **
 **/
Card
WMonteCarlo::best_card()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		"WMonteCarlo::best_card()");

  // create the virtual game and the virtual players
  this->init();


  // calculate the weightings
  this->weightings_calc();

  if (game_status != GAMESTATUS::GAME_PLAY)
    DEBUG_RETURNING(this->hand().card(0),
		    INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		    "WMonteCarlo::best_card()");



  // search the best valid card

  // the number of the best card
  Card best_card = this->hand().card(0);
  int best_weighting = this->weighting_p[0];
  // search a better valid card

#ifdef BENDERS
  if (this->vgi().no() == 1) {
  COUT << this->vgi().no() << std::endl;
  COUT << this->hand().card(0) << " " << this->weighting_p[0] << std::endl;
  }
#endif
  for (unsigned c = 1; c < this->hand().cardsnumber(); c++) {
#ifdef BENDERS
  if (this->vgi().no() == 1) {
    COUT << this->hand().card(c) << " " << this->weighting_p[c] << std::endl;
  }
#endif
	if(   this->weighting_p[c] > best_weighting // find best weighting
	    || (   this->weighting_p[c] == best_weighting // there is not one best
	    	 &&( (   this->hand().card(c).less(best_card, this->vgi().game()) // smallest card
	    	      && (!this->hand().card(c).isfox() || this->weighting_p[c]>0 ) // no Fox
	    	     )
	    	     || (best_card.isfox() && best_weighting < 0) // replace fox
	    	    )
	    	)
	   )	    	           
	{
      best_card = this->hand().card(c);
      best_weighting = this->weighting_p[c];
    } // if (better card found)
  } // for (c < this->hand().cardsnumber())

  DEBUG_RETURNING(best_card,
		  INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		  "WMonteCarlo::best_card()");
} // Card WMonteCarlo::best_card()


/**
 **
 ** calculates the weightings
 ** and writes the values into the vector 'weighting_p;
 **
 ** @param      -
 **
 ** @return     -
 **
 ** @version    0.4.4
 **
 ** @author     Borg Enders
 **
 **/
void
WMonteCarlo::weightings_calc()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		"WMonteCarlo::weightings_calc()");

  ::ui->update();
  if (game_status != GAMESTATUS::GAME_PLAY)
    DEBUG_RETURNING(VOID,
		    INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		    "WMonteCarlo::weightings_calc()");

#ifdef SAVE_RUNTIME
  // for measuring the runtime
  ofstream ostr("MonteCarlo.time", ios::app);
  clock_t time_begin = 0;
  if (!this->vgi().game().isvirtual())
    time_begin = clock();
#endif

  // some statistics
  static unsigned counter = 0;
  static unsigned virtual_depth = 0;
  static unsigned virtual_depth_min = 0;
  static unsigned counter_last = 0;
  if (!this->vgi().game().isvirtual()) {
    counter = 0;
    virtual_depth = 0;
    virtual_depth_min = 0;
    counter_last = 0;
  }


  virtual_depth += 1;

  if (INFO_W_VIRTUAL_GAMES) {
    if (counter >= counter_last + 500) {
      counter_last = counter;
      virtual_depth_min = virtual_depth;
      cout << "MonteCarlo: depth: " << virtual_depth
	<< "\t\t(counter: " << counter << ")" << endl;
    }
    if (virtual_depth < virtual_depth_min) {
      virtual_depth_min = virtual_depth;
      cout << "MonteCarlo: depth: " << virtual_depth << endl;
    }
  } // if (INFO_W_VIRTUAL_GAMES)

  Hand hand = this->vgi().hand().validcards(this->vgi().game().trick_current());
  for (unsigned c = 0; c < hand.cardsnumber(); c++) {
    unsigned gameCnt = 0;
    while ( gameCnt < this->future_limit() /  this->hand().cardsnumber() )
    {
      // this->hand() has only validcards
      if (INFO_W_VIRTUAL_GAMES)
	if (!this->vgi().game().isvirtual())
	  cout << "MonteCarlo: calculating card "
	    << this->hand().card(c)
	    << "\t(" << c + 1 << " / " << this->hand().cardsnumber() << ")" << endl;

      if (INFO_W_VIRTUAL_GAMES)
	if (this->vgi().game() == ::party.game())
	  cout << "  " << hand.card(c) << endl;

      // create the virtual game
      Game game_virt(this->vgi().game());

      // create the players
      vector<Player*> ai_virt;
      for (vector<Player*>::const_iterator ai
	   = this->player_virt_p.begin();
	   ai != this->player_virt_p.end();
	   ai++) {
	ai_virt.push_back(new AiDummy(*static_cast<AiDummy*>(*ai)));
      }
      // set the players of the game
      game_virt.players_set(ai_virt);

      // play the card 'c'
      game_virt.trick_current() += hand.card(c);
      game_virt.player_current().hand().playcard(hand.card(c));
      game_virt.teaminfo_update();
      game_virt.player_current_p
	= &game_virt.player_following(game_virt.player_current());


      try {
	// the other virtual players play a card
	while(!game_virt.trick_current().isfull()) {
	  counter += 1;
	  game_virt.nextplayer();
	}
	if (::game_status != GAMESTATUS::GAME_PLAY)
	  break;

	// first trick is full
	game_virt.evaluatetrick();

	// play tricks till the end depth

	while( (game_virt.trick_current_no() < game_virt.trickno()) 
	       && game_virt.tricks_remaining_no() != 0) {
	  if (INFO_W_VIRTUAL_GAMES)
	    if (!this->vgi().game().isvirtual())
	      cout << "  trick depth " << game_virt.trick_current_no() << endl;


#if 00 // DummyAI needs no FutureLimit
	  // set the future limit of the ai, so that they don't calculate too much
	  for (vector<Player*>::iterator ai = ai_virt.begin();
	       ai != ai_virt.end();
	       ai++)
	    static_cast<DummyAi*>(*ai)->last_trick_to_calculate_set(game_virt.trick_current_no()
								    + (this->end_depth()
								       - t));
#endif	

	  game_virt.trick_p.push_back(Trick(game_virt.player_current()));
	  while(!game_virt.trick_current().isfull()) {
	    counter += 1;
	    game_virt.nextplayer();
	    if (::game_status != GAMESTATUS::GAME_PLAY)
	      break;
	  }
	  if (::game_status != GAMESTATUS::GAME_PLAY)
	    break;
	  game_virt.evaluatetrick();
	  if (::game_status != GAMESTATUS::GAME_PLAY)
	    break;

#if 0
	  // add the modi to the weighting
	  this->weighting_p[c] += this->modi(game_virt.trick_current(),
					     this->vgi().team(),
					     game_virt.trick_current().card_of_player( this->vgi() ) );
#endif
#if 0
	  // output of the modi
	  if (this->vgi().game() == ::party.game()) {
	    COUT << "  Trick " << game_virt.trick_current_no() << ":"
	      << this->weighting_p[c]
	      << "\n"
	      << game_virt.trick_current()
	      ;

	  } // if (this->vgi().game() == ::party.gam())
#endif // output of the modi
	} // for (t < this->end_depth())
	if (::game_status != GAMESTATUS::GAME_PLAY)
	  break;

	// finished with creating tricks,
	// now calc the modi for each trick
	for (unsigned t = this->vgi().game().trick_current_no();
	     t < game_virt.trick_current_no();
	     t++)
	{
	  // add the modi to the weighting
	  this->weighting_p[c]
	    += TrickWeighting::modi(this->vgi(), game_virt.trick(t),
				    game_virt.player(this->vgi().no()).team(),
				    game_virt.trick(t).card_of_player(game_virt.player(this->vgi().no()) ) );
	  gameCnt ++;
	}


	// add some points, if the own team is in the back,
	if (game_virt.trick_current_no() < game_virt.trickno() - 1) {
	this->weighting_p[c]
	  += TrickWeighting::backhand(this->vgi(), this->hand().card(c),
				      game_virt );      	
	} // if (game_virt.trick_current_no() < game_virt.trickno() - 1)


	// game finished, the main reason is the final result...

	if( game_virt.tricks_remaining_no() == 0 )
	{
	  game_virt.finish();
	  GamePoints gp( &game_virt );
	  if ( gp.winnerteam() == this->vgi().team() )
	    this->weighting_p[c] += gp.points() * 10000;
	  else
	    this->weighting_p[c] -= gp.points() * 10000;
	}

#if 0
	// output of the modi
	if (this->vgi().game() == ::party.game()) {
	  COUT << hand.card(c)
	    << ": "
	    << this->weighting_p[c]
	    << endl;

	} // if (this->vgi().game() == ::party.gam())

#endif // output of the modi
      } // try

      catch (InvalidGameException exception) {
	// if the game is invalid, don't take the card
	this->weighting_p[c] = INT_MIN;
	gameCnt += game_virt.trick_current_no();
	DEBUG_CAUGHT();
      } // catch()

      // free the memory

      for (vector<Player*>::iterator ai = ai_virt.begin();
	   ai != ai_virt.end();
	   ai++)
	delete *ai;

      if (INFO_W_VIRTUAL_GAMES)
	if (this->vgi().game() == ::party.game())
	  cout << "weighting " << hand.card(c) << ": "
	    << this->weighting_p[c] << "\n";

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

  if (INFO_W_VIRTUAL_GAMES)
    if (!this->vgi().game().isvirtual())
      cout << "MonteCarlo: counter " << counter 
	<< "  (" << this->vgi().value(Aiconfig::FUTURE_LIMIT) << ")"
	<< endl;

#ifdef POSTPHONED
  DEBUG_ASSERTION(((this->end_depth() == 1)
		   || (counter <= this->future_limit())),
		  "WMonteCarlo::weightings_calc():\n"
		  "  counter is greater than the future limit:\n"
		  "  " << counter << " > " << this->future_limit()
		  << "\t\t(end_depth: " << this->end_depth() << ")");
#endif

#ifdef SAVE_RUNTIME
  if (!this->vgi().game().isvirtual())
    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 >= 2000)
#endif // #ifdef SAVE_RUNTIME


  virtual_depth -= 1;

  DEBUG_RETURNING(VOID,
		  INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		  "WMonteCarlo::weightings_calc()");
} // void WMonteCarlo::weightings_calc()


