/**********************************************************************
 *
 *   FreeDoko a Doppelkopf-Game
 *    
 *   Copyright (C) 2001-2003  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_virtual_games.h"

#include "exception.h"

#include "VirtualGamesInterface.h"
#include "trickweighting.h"
#include "ai.h"
#include "../../party/party.h"
#include "../../game/game.h"
#include "../../game/gamepoints.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
 **
 **/
WVirtualGames::WVirtualGames(VirtualGamesInterface const& vgi, unsigned const& future_limit) :
vgi_p(&vgi),
  future_limit_p(min(future_limit, ::setting(Setting::MAX_FUTURE_LIMIT))),
  end_depth_p(0),
  player_virt_p(),
weighting_p()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT,
		"WVirtualGames::WVirtualGames()");

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

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

  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,
		  "WVirtualGames::~WVirtualGames()");
} // WVirtualGames::~WVirtualGames()

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

  DEBUG_RETURNING(*(this->vgi_p),
		  INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		  "WVirtualGames::vgi()");
} // VirtualGamesInterface const& VirtualGames::vgi() const

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

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

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

  DEBUG_RETURNING(this->end_depth_p,
		  INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		  "WVirtualGames::end_depth()");
} // unsigned const& VirtualGames::end_depth() const

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

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

/**
 **
 ** calculates the maximal depth of tricks,
 ** so that no more of 'future limit' tricks are considered
 ** took the code from W_virtual::calctricks()
 ** @see        W_virtual::calctricks()
 **
 ** @param      -
 **
 ** @return     the calculated end depth
 **
 ** @version    0.4.4
 **
 ** @author     Borg Enders
 **
 **/
unsigned const&
WVirtualGames::end_depth_calculate()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		"WVirtualGames::end_depth_calculate()");

  // #combination for all tricks
  unsigned long int alltricks = 1;
  // #combination for one trick
  unsigned long int onetrick = 1;

  Trick const& trick = this->vgi().game().trick_current();

  // count the number of card-combinations for the current trick
  for (unsigned i = trick.actcardno();
       i < this->vgi().game().playerno();
       i++)
    onetrick *= this->vgi().handofplayer(trick.player_of_card(i)
				       ).validcards(trick).cardsnumber();

  alltricks *= onetrick;

  // count the number of card-combinations for the other tricks
  // (since I don't know what color is played, I take all cards of the hand)
  for (this->end_depth_p = 1;
       // '<' because also the current trick is counted in tricks_remaining_no
       (this->end_depth_p < this->vgi().game().tricks_remaining_no());
       this->end_depth_p += 1){

    onetrick = 1;
    for (unsigned i = 0; i < this->vgi().game().playerno(); i++) {
#ifndef POSTPHONED
      if (this->vgi().game().trick_current().cardno_of_player(this->vgi().game().player(i))
	  >= this->vgi().game().trick_current().actcardno())
	// the player still has to play a card in the current trick
	onetrick *= (this->vgi().handofplayer(this->vgi().game().player(i)).cardsnumber()
		     - this->end_depth_p);
      else
	// the player has already played a card, so he has one card less 
	// on the hand
	onetrick *= (this->vgi().handofplayer(this->vgi().game().player(i)).cardsnumber()
		     - (this->end_depth_p - 1));

      DEBUG_ASSERTION((onetrick > 0),
		      "WVirtualGames::end_depth_calculate():\n"
		      "  onetrick == 0\n"
		      << "cardsnumber = " << this->vgi().handofplayer(this->vgi().game().player(i)).cardsnumber() << '\n'
		      << "end_depth = " << this->end_depth_p << '\n'
		      << "trickno = " << this->vgi().game().trick_current_no() << '\n'
		      << "remaining trickno = " << this->vgi().game().tricks_remaining_no() << '\n'
		     );
#else
      onetrick *= this->vgi().hand().cardsnumber() - this->end_depth_p;
#endif
    }

    if (alltricks < this->future_limit_p / onetrick) {
      alltricks *= onetrick; 
    } else  {
      break;
    }
  } // for (this->end_depth_p)

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

#ifdef POSTPHONED
  this->end_depth_p = max(unsigned(2), this->end_depth());
#endif

#ifdef HEURISTIC_OUTPUT
  if (this->vgi().game() == ::party.gam())
    cout << "Player " << this->vgi().no() << ": "
      << "WVirtualGames: "
      << "end trick = " <<  << endl;
#endif



  DEBUG_RETURNING(this->end_depth(),
		  INFO_W_VIRTUAL_GAMES && INFO_VALUE,
		  "WVirtualGames::end_depth_calculate()");
} // unsigned const& VirtualGames::end_depth_calculate()

/**
 **
 ** 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
WVirtualGames::init()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT,
		"WVirtualGames::init()");

  // calculate the end depth
  this->end_depth_calculate();

  // 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<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 Ai(static_cast<Ai 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;
      Ai& ai_virtual = static_cast<Ai&>(*(this->player_virt_p.back()));

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

      // set the hand
      ai_virtual.hand_set(this->vgi().handofplayer(**player), false);
      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()),
		      "WVirtualGames::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))

	// change the future limit
#ifndef POSTPHONED
	// *** workaround
	static_cast<Ai&>(ai_virtual).set(Aiconfig::FUTURE_LIMIT,
					 this->vgi().value(Aiconfig::FUTURE_LIMIT) / 10);
#endif // #ifndef POSTPHONED

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

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

/**
 **
 ** -> result
 **
 ** @param      -
 **
 ** @return     the card, that makes the most points
 **
 ** @version    0.5.4
 **
 ** @author     Borg Enders
 **
 **/
Card
WVirtualGames::best_card()
{
  DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION,
		"WVirtualGames::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,
		    "WVirtualGames::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().game().isvirtual()) {
    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().game().isvirtual()) {
      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,
		  "WVirtualGames::best_card()");
} // Card WVirtualGames::best_card()

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

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

#ifdef SAVE_RUNTIME
  // for measuring the runtime
  ofstream ostr("VirtualGames.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 << "VirtualGames: depth: " << virtual_depth
	<< "\t\t(counter: " << counter << ")" << endl;
    }
    if (virtual_depth < virtual_depth_min) {
      virtual_depth_min = virtual_depth;
      cout << "VirtualGames: depth: " << virtual_depth << endl;
    }
  } // if (INFO_W_VIRTUAL_GAMES)

  for (unsigned c = 0; c < this->hand().cardsnumber(); c++) {
    // this->hand() has only validcards
    if (INFO_W_VIRTUAL_GAMES)
      if (!this->vgi().game().isvirtual())
	cout << "VirtualGames: 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 << "  " << this->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 Ai(*static_cast<Ai*>(*ai)));
    }
    // set the players of the game
    game_virt.players_set(ai_virt);

    // play the card 'c'
    game_virt.trick_current() += this->hand().card(c);
    game_virt.player_current().hand().playcard(this->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

      for (unsigned t = 1;
	   ((t < this->end_depth())
	    && (game_virt.trick_current_no() < game_virt.trickno()));
	   t++) {
	if (INFO_W_VIRTUAL_GAMES)
	  if (!this->vgi().game().isvirtual())
	    cout << "  trick depth " << t
	      << " (" << this->end_depth() - 1 << ")" << endl;


	// 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<Ai*>(*ai)->last_trick_to_calculate_set(game_virt.trick_current_no()
							     + (this->end_depth()
								- t));

	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()) ) );


      // add some points, if the own team is in the back,
      if (game_virt.trick_current_no() < game_virt.trickno() - 1) {
	if (game_virt.trick_current().player_of_card(game_virt.playerno()
						     - 1).team()
	    == this->vgi().team())
	  this->weighting_p[c] += 5;
	else
	  this->weighting_p[c] -= 5;
      } // 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 )
      {
	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 << this->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;
      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 " << this->hand().card(c) << ": "
	  << this->weighting_p[c] << "\n";

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

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

#ifdef POSTPHONED
  DEBUG_ASSERTION(((this->end_depth() == 1)
		   || (counter <= this->future_limit())),
		  "WVirtualGames::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,
		  "WVirtualGames::weightings_calc()");
} // void WVirtualGames::weightings_calc()

