/**********************************************************************
 *
 *   FreeDoko a Doppelkopf-Game
 *    
 *   Copyright (C) 2001-2004  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 "game_summary.h"
#include "game.h"
#ifdef OUTDATED
#include "gamepointstable.h"
#endif
#include "../party/party.h"
#include "../party/rule.h"
#include "../player/player.h"
#include "../card/trick.h"

/**
 ** Constructor
 **
 ** @param	game	game the summary is made of
 **
 ** @return	-
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 ** @todo	remove const cast
 **
 **/
GameSummary::GameSummary(Game const& game) :
  party_(const_cast<Party*>(&game.party())),
  seed_(game.seed()),
  players_number_(game.playerno()),
  startplayer_no_(game.startplayer().no()),
soloplayer_no_( (game.exists_soloplayer()
		 ? game.soloplayer().no()
		 : UINT_MAX) ),
  game_type_(game.type()),
swines_player_no_( (game.swines_owner()
		    ? game.swines_owner()->no()
		    : UINT_MAX) ),
hyperswines_player_no_( (game.hyperswines_owner()
			 ? game.hyperswines_owner()->no()
			 : UINT_MAX) ),
  teams_(),
  highest_announcement_re_(ANNOUNCEMENT::NOANNOUNCEMENT),
  highest_announcement_contra_(ANNOUNCEMENT::NOANNOUNCEMENT),
  trick_points_(),
  winnerteam_(TEAM::NOTEAM),
  points_(UINT_MAX),
  specialpoints_()
{
  this->evaluate(game);

  return ;
} // GameSummary::GameSummary(Game const& game) :

/**
 ** Constructor
 **
 ** @param	party	the corresponding party
 ** @param	istr	input stream to read the game from
 **
 ** @return	-
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
GameSummary::GameSummary(Party& party, istream& istr) :
  party_(&party),
  seed_(UINT_MAX),
  players_number_(UINT_MAX),
  startplayer_no_(UINT_MAX),
  soloplayer_no_(UINT_MAX),
  game_type_(GAMETYPE::NORMAL),
  swines_player_no_(UINT_MAX),
  hyperswines_player_no_(UINT_MAX),
  teams_(),
  highest_announcement_re_(ANNOUNCEMENT::NOANNOUNCEMENT),
  highest_announcement_contra_(ANNOUNCEMENT::NOANNOUNCEMENT),
  trick_points_(),
  winnerteam_(),
  points_(),
  specialpoints_()
{
  this->read(istr);

  return ;
} // GameSummary::GameSummary(istream& istr) :


/**
 ** Destructor
 **
 ** @param	-
 **
 ** @return	-
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
GameSummary::~GameSummary()
{
  return ;
} // GameSummary::~GameSummary()


/**
 ** writes the game summary into 'ostr'
 **
 ** @param	ostr	output stream
 **
 ** @return	-
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
void
GameSummary::write(ostream& ostr) const
{
  ostr << "seed = " << this->seed() << '\n';
  ostr << "players number = " << this->players_number() << '\n';
  ostr << "startplayer number = " << this->startplayer_no() << '\n';
  ostr << "soloplayer number = " << this->soloplayer_no() << '\n';
  ostr << "gametype = " << this->game_type() << '\n';
  ostr << "swines player number = " << this->swines_player_no() << '\n';
  ostr << "hyperswines player number = " << this->hyperswines_player_no() << '\n';
  ostr << "Teams =";
  for (vector<Team>::const_iterator t = this->teams().begin();
       t != this->teams().end();
       ++t)
    ostr << ' ' << *t;
  ostr << '\n';
  ostr << "highest announcement re = " << this->highest_announcement_re() << '\n';
  ostr << "highest announcement contra = " << this->highest_announcement_contra() << '\n';
  // ToDo: playerpoints
  ostr << "Playerspoints =";
  for (vector<unsigned>::const_iterator p = this->trick_points().begin();
       p != this->trick_points().end();
       ++p)
    ostr << ' ' << *p;
  ostr << '\n';
  ostr << "winnerteam = " << this->winnerteam() << '\n';
  ostr << "points = " << this->points() << '\n';
  ostr << "special points\n"
    << "{\n"
    // ToDo << this->specialpoints()
    << "}\n";

  return ;
} // void GameSummary::write(ostream& ostr) const

/**
 ** reads the game summary from 'ostr'
 **
 ** @param	istr	input stream
 **
 ** @return	-
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 ** @todo	all
 **
 **/
void
GameSummary::read(istream& istr)
{

  return ;
} // void GameSummary::read(ostream& ostr) ostr

/**
 ** -> result
 **
 ** @param	-
 **
 ** @return	whether the game was a duty solo
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
bool
GameSummary::is_duty_solo() const
{
  return false;
} // bool GameSummary::is_duty_solo() const

/**
 ** -> result
 **
 ** @param	-
 **
 ** @return	whether the game was a lust solo
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
bool
GameSummary::is_lust_solo() const
{
  return GAMETYPE::is_solo(this->game_type());
} // bool GameSummary::is_lust_solo() const

/**
 ** -> result
 **
 ** @param	playerno	the player
 **
 ** @return	whether the player gets points
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
bool
GameSummary::player_get_points(unsigned const playerno) const
{
  using namespace COUNTING_NS;
  if (this->winnerteam() == TEAM::NOTEAM) {
    switch(this->party().rule()(Rule::COUNTING)) {
    case PLUS:
    case MINUS:
      return ( ( (this->points() > 0)
		 && (this->team(playerno) == TEAM::RE))
	       || ( (this->points() < 0)
		    && (this->team(playerno) == TEAM::CONTRA)) );

    case PLUSMINUS:
      return (this->points() != 0);

    } // switch(this->party().rule()(Rule::COUNTING))

  } else { // if !(p.winnerteam() == TEAM::NOTEAM)
    switch(this->party().rule()(Rule::COUNTING)) {
    case PLUS:
      return (this->team(playerno) == this->winnerteam());

    case MINUS:
      return (this->team(playerno) != this->winnerteam());

    case PLUSMINUS:
      return true;
    } // switch(this->party().rule()(Rule::COUNTING))
  } // if !(p.winnerteam() == TEAM::NOTEAM)

  DEBUG_ASSERT(false);
  return false;
} // bool GameSummary::player_get_points(unsigned const playerno) const

/**
 ** -> result
 **
 ** @param	playerno	the player
 **
 ** @return	whether the player gets points
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 ** @todo	plusminus
 ** @todo	solo with sum zero
 **
 **/
int
GameSummary::points(unsigned const playerno) const
{
  if (this->player_get_points(playerno))
    return this->points();
  else
    return 0;
} // int GameSummary::points(unsigned const playerno) const

/**
 ** -> result
 **
 ** @param	playerno	the player
 **
 ** @return	the number of points 'playerno' has made in his tricks
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
unsigned
GameSummary::trick_points(unsigned const playerno) const
{
  return this->trick_points()[playerno];
} // unsigned 

/**
 ** -> result
 **
 ** @param	team	the team
 **
 ** @return	the points made by the team
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
unsigned
GameSummary::trick_points(Team const team) const
{
  unsigned points = 0;
  for (unsigned p = 0; p < this->trick_points().size(); ++p)
    if (this->team(p) == team)
      points += this->trick_points(p);

  return points;
} // unsigned GameSummary::trick_points(Team const team) const

/**
 ** -> result
 **
 ** @param	playerno	the player
 **
 ** @return	team of player 'playerno'
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
Team
GameSummary::team(unsigned const playerno) const
{
  return this->teams()[playerno];
} // Team GameSummary::team(unsigned const playerno) const


/**
 ** -> result
 **
 ** @param	playerno	the player
 **
 ** @return	team of player 'playerno'
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
void
GameSummary::evaluate(Game const& game)
throw (InvalidGameException)
{
  // save the team and the trick points
  for(unsigned p = 0; p < game.playerno(); p++ ) {
    this->teams_.push_back(game.team(game.player(p)));
    this->trick_points_.push_back(game.points_of_player(game.player(p)));
  }

  // save the winnerteam
  this->winnerteam_ = game.winnerteam();

  // calculate highest announcements
  vector<unsigned> re_announcement_player(ANNOUNCEMENT::NUMBER, UINT_MAX);
  vector<unsigned> contra_announcement_player(ANNOUNCEMENT::NUMBER, UINT_MAX);
  Announcement reply_announcement = ANNOUNCEMENT::NOANNOUNCEMENT;
  unsigned reply_player;
  for(unsigned p = 0; p < game.playerno(); p++ ) {
    Player const& player = game.player(p);
    vector<Game::AnnouncementWithTrickno> const& announcements
      = game.announcements_of_player(player);

    if (is_reply(announcements.back().announcement)) {
      reply_announcement = announcements.back().announcement;
      reply_player = p;
    }

    if (game.team(player) == TEAM::RE) {
      if (player.announcement() > this->highest_announcement_re_)
	this->highest_announcement_re_ = player.announcement();

      for (unsigned a = 0; a < announcements.size(); a++)
	re_announcement_player[announcements[a].announcement] = p;
    } // if (game.team(player) == TEAM::RE)

    if (game.team(player) == TEAM::CONTRA) {
      if( player.announcement() > highest_announcement_contra_)
	this->highest_announcement_contra_ = player.announcement();

      for (unsigned a = 0; a < announcements.size(); a++)
	contra_announcement_player[announcements[a].announcement] = p;
    } // if (game.team(player) == TEAM::RE)
  } // for (p < game.playerno())

  { // special points
    // Order of the points
    // * re no 0
    // * contra no 0
    // * no 0 reply
    // * re no 0 said
    // * contra no 0 said
    // * re no 30
    // * contra no 30
    // * no 30 reply
    // * re no 30 said
    // * contra no 30 said
    // * re no 60
    // * contra no 60
    // * no 60 reply
    // * re no 60 said
    // * contra no 60 said
    // * re no 90
    // * contra no 90
    // * no 90 reply
    // * re no 90 said
    // * contra no 90 said
    // * re won && re > 120 points
    // * re won && re 120 points && re no announcement && contra no announcement
    // * contra won && contra > 120 points
    // * consta won && contra 120 points && contra no announcement
    // * no 120 reply
    // * contra no 0 said && re > 30 points
    // * contra no 30 said && re > 60 points
    // * contra no 60 said && re > 90 points
    // * contra no 90 said && re > 120 points
    // * re no 0 said && contra > 30 points
    // * re no 30 said && contra > 60 points
    // * re no 60 said && contra > 90 points
    // * re no 90 said && contra > 120 points
    // * re no 120 said
    // * contra no 120 said
    // * against club queen
    // * multiplicator 3
    // * specialpoints

    { // announcements

      // shortcuts
      // re no 90
#define TRICKPOINTS_RE_NO(A) \
      if ((game.points_of_team(TEAM::RE) < A) \
	  && (game.winnerteam() != TEAM::RE)) \
      this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO ## A, \
						   TEAM::CONTRA))
      // contra no 90
#define TRICKPOINTS_CONTRA_NO(A) \
      if ((game.points_of_team(TEAM::CONTRA) < A) \
	  && (game.winnerteam() != TEAM::CONTRA)) \
      this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO ## A, \
						   TEAM::RE))
      // re got 90 (against 'no 90')
#define TRICKPOINTS_RE_GOT(A) \
      if ((this->highest_announcement_contra() == ANNOUNCEMENT::NO ## A) \
	  && (game.points_of_team(TEAM::RE) >= A)) \
      this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO ## A ## _WON, \
						   TEAM::RE))
      // contra got 90 (against 'no 90')
#define TRICKPOINTS_CONTRA_GOT(A) \
      if ((this->highest_announcement_re() == ANNOUNCEMENT::NO ## A) \
	  && (game.points_of_team(TEAM::CONTRA) >= A)) \
      this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO ## A ## _WON, \
						   TEAM::CONTRA))
      // replied on announcement
#define ANNOUNCEMENT_REPLY(A) \
      if (reply_announcement == ANNOUNCEMENT::NO ## A ## _REPLY) { \
	Specialpoints sp( SPECIALPOINT::NO ## A ## _REPLY, \
			  game.player(reply_player).team(), \
			  game.winnerteam()); \
	sp.player_of_no = reply_player; \
	this->specialpoints_.push_back(sp); \
      } else
      // re announced no 60
#define TRICKPOINTS_RE_NO_ANNOUNCED(A) \
      if (is_real(this->highest_announcement_re()) \
	  && (this->highest_announcement_re() >= ANNOUNCEMENT::NO ## A ) \
	  && (game.winnerteam() != TEAM::NOTEAM)) { \
	Specialpoints sp(SPECIALPOINT::NO ## A ## _SAID, \
			 TEAM::RE, \
			 game.winnerteam()); \
	sp.player_of_no = re_announcement_player[ANNOUNCEMENT::NO ## A]; \
	this->specialpoints_.push_back(sp); \
      } else
      // contra announced no 60
#define TRICKPOINTS_CONTRA_NO_ANNOUNCED(A) \
      if (is_real(this->highest_announcement_contra()) \
	  && (this->highest_announcement_contra() >= ANNOUNCEMENT::NO ## A ) \
	  && (game.winnerteam() != TEAM::NOTEAM)) { \
	Specialpoints sp(SPECIALPOINT::NO ## A ## _SAID, \
			 TEAM::CONTRA, \
			 game.winnerteam()); \
	sp.player_of_no = contra_announcement_player[ANNOUNCEMENT::NO ## A]; \
	this->specialpoints_.push_back(sp); \
      } else

#define POINTS(A) \
      TRICKPOINTS_RE_NO(A); \
      TRICKPOINTS_CONTRA_NO(A); \
      TRICKPOINTS_RE_GOT(A); \
      TRICKPOINTS_CONTRA_GOT(A); \
      ANNOUNCEMENT_REPLY(A); \
      TRICKPOINTS_RE_NO_ANNOUNCED(A); \
      TRICKPOINTS_CONTRA_NO_ANNOUNCED(A)

      { // 0
	// re no 0
	if (( !game.hastrick(TEAM::RE))
	    && (game.winnerteam() != TEAM::RE))
	  this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO0,
						       TEAM::CONTRA));
	// contra no 0
	if (( !game.hastrick( TEAM::CONTRA ) )
	    && (game.winnerteam() != TEAM::CONTRA))
	  this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO0,
						       TEAM::RE));
	// re 0 got
	if ( (this->highest_announcement_contra() == ANNOUNCEMENT::NO0)
	     && game.hastrick(TEAM::RE) )
	  this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO0_WON,
						       TEAM::RE));
	// contra 0 got
	if ( (this->highest_announcement_re() == ANNOUNCEMENT::NO0)
	     && game.hastrick(TEAM::CONTRA) )
	  this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO0_WON,
						       TEAM::CONTRA));
	ANNOUNCEMENT_REPLY(0);
	TRICKPOINTS_RE_NO_ANNOUNCED(0);
	TRICKPOINTS_CONTRA_NO_ANNOUNCED(0);
      } // 0

      POINTS(30);
      POINTS(60);
      POINTS(90);

      { // 120
	// re no 120
	if ( (game.winnerteam() == TEAM::CONTRA)
	     && ( (game.points_of_team(TEAM::RE) < 120)
		  || ( (game.points_of_team(TEAM::RE) == 120)
		       && (this->highest_announcement_contra()
			   == ANNOUNCEMENT::NOANNOUNCEMENT) ) ) )
	  this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO120,
						       TEAM::CONTRA));
	// contra no 120
	if ( (game.winnerteam() == TEAM::RE)
	     && ( (game.points_of_team( TEAM::CONTRA ) < 120)
		  || ( (game.points_of_team(TEAM::CONTRA) == 120)
		       && (this->highest_announcement_re()
			   == ANNOUNCEMENT::NOANNOUNCEMENT )
		       && (this->highest_announcement_contra()
			   > ANNOUNCEMENT::NOANNOUNCEMENT) ) ) )
	  this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::NO120,
						       TEAM::RE));

	ANNOUNCEMENT_REPLY(120);
	TRICKPOINTS_RE_NO_ANNOUNCED(120);
	TRICKPOINTS_CONTRA_NO_ANNOUNCED(120);
      } // 120

#undef TRICKPOINTSS_RE_NO
#undef TRICKPOINTSS_CONTRA_NO
#undef TRICKPOINTSS_RE_GOT
#undef TRICKPOINTSS_CONTRA_GOT
#undef ANNOUNCEMENT_REPLY
#undef TRICKPOINTS_RE_NO_ANNOUNCED
#undef TRICKPOINTS_CONTRA_NO_ANNOUNCED
#undef POINTS

    } // announcements

    // against club queens
    if (!GAMETYPE::is_solo(this->game_type())
	&& (game.winnerteam() == TEAM::CONTRA) )
      this->specialpoints_.push_back(Specialpoints(SPECIALPOINT::CONTRA_WON,
					     TEAM::CONTRA));

    // multiplicator 3 for solo games
    if ( GAMETYPE::is_solo(this->game_type())
	 && (this->winnerteam() == TEAM::RE) ) {
      Specialpoints sp(SPECIALPOINT::SOLO, TEAM::RE);
      sp.player_of_no = game.soloplayer().no();
      this->specialpoints_.push_back(sp);
    }

    // last collect all specialpoints of each trick
    for (unsigned t = 0; t < game.trick_current_no(); ++t) {
      Specialpointsvector const spv = game.trick(t).specialpoints();
      for (Specialpointsvector::const_iterator s = spv.begin();
	   s != spv.end();
	   s++)
	if (s->is_valid(this->teams()))
	  this->specialpoints_.push_back(*s);
    }

    // calculating the main game points
    this->points_ = Sum_of_Specialpoints(this->specialpoints(),
					 this->winnerteam(),
					 game);

    DEBUG_ASSERTION((( this->winnerteam() == TEAM::RE )
		     || ( this->winnerteam() == TEAM::CONTRA )
		     || ( this->winnerteam() == TEAM::NOTEAM )),
		    "GameSummary::evaluate(Game):" << endl
		    << "  wrong team at end: " << this->winnerteam());


#ifdef OUTDATED
    DEBUG_ASSERTION(this->test(this->party().gamepointstable().gamepoints(this->party().gameno())),
		    "GameSummary::evaluate(game):\n"
		    "  data does not match with the data of the gamepoints");
#endif
  } // special points

  return ;
} // void GameSummary::evaluate(Game const& game)

#ifdef OUTDATED
/**
 ** compares this game summary with the gamepoints
 ** (test for errors)
 **
 ** @param	gamepoints	gamepoints to compare
 **
 ** @return	whether the data is the same
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
bool
GameSummary::test(GamePoints const& gamepoints) const
{
  COUT << "\n"
    << "game_summary:\n"
    << *this << "\n"
    << "gamepoints:\n"
    << gamepoints << "\n"
    << endl;
  if (this->points() != gamepoints.points()) {
    cerr << "points" << endl;
    cerr << this->points() << " != " << gamepoints.points() << endl;
    return false;
  }

  for (unsigned p = 0; p < this->party().playerno(); ++p) {
    if (this->trick_points(p) != gamepoints.playerspoints(p)) {
      cerr << "playerpoins " << p << endl;
      return false;
    }
    if (this->team(p) != gamepoints.team(p)) {
      cerr << "team " << p << endl;
      return false;
    }
  }

  if (this->trick_points(TEAM::RE) != gamepoints.teampoints(TEAM::RE)) {
    cerr << "trick_points(RE)" << endl;
    return false;
  }
  if (this->trick_points(TEAM::CONTRA) != gamepoints.teampoints(TEAM::CONTRA)) {
    cerr << "trick_points(CONTRA)" << endl;
    return false;
  }

  if (this->game_type() != gamepoints.gametype()) {
    cerr << "gametype" << endl;
    return false;
  }

  if (this->winnerteam() != gamepoints.winnerteam()) {
    cerr << "winnerteam" << endl;
    return false;
  }

#if 0
  if (this->specialpoints() != gamepoints.pointlist()) {
    cerr << "pointlist" << endl;
    return false;
  }
#endif

  return true;
} // bool GameSummary::test(GamePoints const& gamepoints) const
#endif // #ifndef OUTDATED

/**
 ** writes the game summary into the stream
 **
 ** @param	ostr		output stream
 ** @param	game_summary	game summary
 **
 ** @return	output stream
 **
 ** @author	Diether Knof
 **
 ** @version	0.6.7
 **
 **/
ostream&
operator<<(ostream& ostr, GameSummary const& game_summary)
{
  game_summary.write(ostr);
  return ostr;
} // ostream& operator<<(ostream& ostr, GameSummary const& game_summary)
