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

#include "../player/player.h"
#include "../party/rule.h"
#include "../card/trick.h"

/**
 ** -> result
 **
 ** @param       tcolor	tcolor
 **
 ** @return      number of different cards of the tcolor
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 ** @version     0.7.0
 **/
unsigned
Game::different_cards_no(Card::TColor const& tcolor) const
{
  if (tcolor == Card::TRUMP)
    return (this->trumps_no() / this->rule()(Rule::NUMBER_OF_SAME_CARDS));
  if (tcolor == this->trumpcolor())
    return 0;

  switch (this->type()) {
  case GAMETYPE::NORMAL:
  case GAMETYPE::FOX_HIGHEST_TRUMP:
  case GAMETYPE::POVERTY:
  case GAMETYPE::GENSCHER:
  case GAMETYPE::MARRIAGE:
  case GAMETYPE::MARRIAGE_SOLO:
  case GAMETYPE::MARRIAGE_SILENT:
  case GAMETYPE::SOLO_DIAMOND:
  case GAMETYPE::SOLO_HEART:
  case GAMETYPE::SOLO_SPADE:
  case GAMETYPE::SOLO_CLUB:
    {
      unsigned number = this->rule()(Rule::NUMBER_OF_CARD_VALUES) - 2;
      if ( ( tcolor == Card::HEART )
	  && this->rule()(Rule::DOLLEN))
	number -= 1;
      return number;
    }
  case GAMETYPE::SOLO_JACK:
  case GAMETYPE::SOLO_QUEEN:
  case GAMETYPE::SOLO_KING:
    return ( this->rule()(Rule::NUMBER_OF_CARD_VALUES) - 1 );

  case GAMETYPE::SOLO_QUEEN_JACK:
  case GAMETYPE::SOLO_KING_JACK:
  case GAMETYPE::SOLO_KING_QUEEN:
    return ( this->rule()(Rule::NUMBER_OF_CARD_VALUES) - 2 );

  case GAMETYPE::SOLO_KOEHLER:
    return ( this->rule()(Rule::NUMBER_OF_CARD_VALUES) - 3 );

  case GAMETYPE::SOLO_MEATLESS:
    return this->rule()(Rule::NUMBER_OF_CARD_VALUES);

  case GAMETYPE::THROWN_NINES:
  case GAMETYPE::THROWN_KINGS:
    return this->rule()(Rule::NUMBER_OF_CARD_COLORS);

  case GAMETYPE::THROWN_NINES_AND_KINGS:
    return (2 * this->rule()(Rule::NUMBER_OF_CARD_COLORS));
  } // this->type()

  return 0;
} // unsigned Game::different_cards_no(Card::TColor tcolor) const

/**
 ** -> result
 **
 ** @param       -
 **
 ** @return      number of trumps in the game
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 ** @version     0.4.4
 **/
unsigned
Game::trumps_no() const
{
  // number of trumps
  unsigned trumps = 0;

  switch (this->type()) {
  case GAMETYPE::NORMAL:
  case GAMETYPE::FOX_HIGHEST_TRUMP:
  case GAMETYPE::POVERTY:
  case GAMETYPE::GENSCHER:
  case GAMETYPE::MARRIAGE:
  case GAMETYPE::MARRIAGE_SOLO:
  case GAMETYPE::MARRIAGE_SILENT:
  case GAMETYPE::SOLO_DIAMOND:
  case GAMETYPE::SOLO_HEART:
  case GAMETYPE::SOLO_SPADE:
  case GAMETYPE::SOLO_CLUB:
    trumps = 2 * this->rule()(Rule::NUMBER_OF_CARD_COLORS); // jack and queen
    trumps += (this->rule()(Rule::NUMBER_OF_CARD_VALUES)
	       - 2); // rest trump color
    if (this->rule()(Rule::DOLLEN)
	&& (this->type() != GAMETYPE::SOLO_HEART))
      trumps += 1;
    break;
  case GAMETYPE::SOLO_JACK:
  case GAMETYPE::SOLO_QUEEN:
  case GAMETYPE::SOLO_KING:
    trumps = this->rule()(Rule::NUMBER_OF_CARD_COLORS);
    break;
  case GAMETYPE::SOLO_QUEEN_JACK:
  case GAMETYPE::SOLO_KING_JACK:
  case GAMETYPE::SOLO_KING_QUEEN:
    trumps = 2 * this->rule()(Rule::NUMBER_OF_CARD_COLORS);
    break;
  case GAMETYPE::SOLO_KOEHLER:
    trumps = 3 * this->rule()(Rule::NUMBER_OF_CARD_COLORS);
    break;
  case GAMETYPE::SOLO_MEATLESS:
    trumps = 0;
    break;
  case GAMETYPE::THROWN_NINES:
  case GAMETYPE::THROWN_KINGS:
    trumps = this->rule()(Rule::NUMBER_OF_CARD_COLORS);
    break;

  case GAMETYPE::THROWN_NINES_AND_KINGS:
    trumps = 2 * this->rule()(Rule::NUMBER_OF_CARD_COLORS);
    break;
  } // this->type()
  trumps *= this->rule()(Rule::NUMBER_OF_SAME_CARDS);

  return trumps;
} // unsigned Game::trumps_no() const

/**
 **
 ** -> result
 **
 ** @param       -
 **
 ** @return      number of played trumps
 **
 ** @version     0.4.4
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 ** @todo        perhaps use a variable,
 **              so that this function does not take much time
 **
 */
unsigned
Game::trumps_played_no() const
{
  // number of trumps
  unsigned trumps = 0;
  for (vector<Trick*>::const_iterator trick = this->tricks().begin();
       trick != this->tricks().end();
       trick++)
    for (unsigned c = 0; c < (*trick)->actcardno(); c++)
      if ((*trick)->card(c).istrump())
	trumps += 1;

  return trumps;
} // unsigned Game::trumps_played_no() const

/**
 **
 ** -> result
 **
 ** @param       -
 **
 ** @return      number of played cards
 **
 ** @version     0.6.8
 **
 ** @author      Diether Knof
 **
 **/
unsigned
Game::played_cards_no() const
{
  if (this->tricks().empty())
    return 0;

  if (::game_status == GAMESTATUS::GAME_FINISHED)
    return (this->trickno()
	    * this->rule()(Rule::NUMBER_OF_PLAYERS_IN_GAME));

  return (this->real_trick_current_no()
	  * this->rule()(Rule::NUMBER_OF_PLAYERS_IN_GAME)
	  + this->trick_current().actcardno());
} // unsigned Game::played_cards_no() const

/**
 **
 ** -> result
 **
 ** @param       card	card
 **
 ** @return      number of played cards 'card'
 **
 ** @version     0.6.8
 **
 ** @author      Diether Knof
 **
 ** @todo        perhaps use a variable,
 **              so that this function does not take much time
 **
 **/
unsigned
Game::played_no(Card const& card) const
{
  // number of trumps
  unsigned no = 0;
  for (vector<Trick*>::const_iterator trick = this->tricks().begin();
       trick != this->tricks().end();
       trick++)
    for (HandCards::const_iterator c = (*trick)->cards().begin();
	 c != (*trick)->cards().end();
	 ++c)
      if (*c == card)
	no += 1;

  return no;
} // unsigned Game::played_no(Card card) const

/**
 ** -> result
 **
 ** @param       -
 **
 ** @return      the trumpcolor
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 ** @version     0.4.4
 **/
Card::Color
Game::trumpcolor() const
{
  switch (this->type()) {
  case GAMETYPE::NORMAL:
  case GAMETYPE::POVERTY:
  case GAMETYPE::FOX_HIGHEST_TRUMP:
  case GAMETYPE::GENSCHER:
  case GAMETYPE::MARRIAGE:
  case GAMETYPE::MARRIAGE_SOLO:
  case GAMETYPE::MARRIAGE_SILENT:
  case GAMETYPE::SOLO_DIAMOND:
    return Card::DIAMOND;
  case GAMETYPE::SOLO_HEART:
    return Card::HEART;
  case GAMETYPE::SOLO_SPADE:
    return Card::SPADE;
  case GAMETYPE::SOLO_CLUB:
    return Card::CLUB;
  case GAMETYPE::SOLO_JACK:
  case GAMETYPE::SOLO_QUEEN:
  case GAMETYPE::SOLO_KING:
  case GAMETYPE::SOLO_QUEEN_JACK:
  case GAMETYPE::SOLO_KING_JACK:
  case GAMETYPE::SOLO_KING_QUEEN:
  case GAMETYPE::SOLO_KOEHLER:
  case GAMETYPE::SOLO_MEATLESS:
  case GAMETYPE::THROWN_NINES:
  case GAMETYPE::THROWN_KINGS:
  case GAMETYPE::THROWN_NINES_AND_KINGS:
    return Card::NOCARDCOLOR;
  } // switch(this->type())

  DEBUG_ASSERTION(false,
		  "Game::trumpcolor()\n"
		  "  after return");
  return Card::NOCARDCOLOR;
} // Card::Color Game::trumpcolor() const

/**
 ** -> result
 **
 ** @param       team    Team
 **
 ** @return      whether the team has got a trick
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 ** @version     0.4.4
 **/
bool
Game::hastrick(const TEAM::Team& team) const
{
  return (this->numberoftricks_of_team(team) > 0);
} // bool Game::hastrick(const TEAM::Team& team) const

/**
 ** -> result
 **
 ** @param       team    Team
 **
 ** @return      whether the team has got a trick
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 ** @version     0.4.4
 **/
bool
Game::hastrick(Player const& player) const
{
  return (this->numberoftricks_of_player(player) > 0);
} // bool Game::hastrick(Player player) const

/**
 **
 ** -> result
 **
 ** @param       player  player
 **
 ** @return      number of tricks, the player has won so far
 **              (with the current trick)
 **
 ** @version     0.4.4
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 */
unsigned
Game::numberoftricks_of_player(Player const& player) const
{
  unsigned number = 0;

  vector<Trick*>::const_iterator trick;
  for (trick = this->tricks().begin();
       trick != this->tricks().end();
       trick++)
    if (((*trick)->winnerplayer() == player)
	&& (*trick)->intrickpile())
      number += 1;

  return number;
} // unsigned Game::numberoftricks_of_player(Player const& player) const

/**
 **
 ** -> result
 **
 ** @param       player  player
 ** @param       trickno last trick to view
 **
 ** @return      number of tricks, the player has won til the trick 'trickno'
 **
 ** @version     0.6.7
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 */
unsigned
Game::numberoftricks_of_player(Player const& player,
			       unsigned trickno) const
{
  unsigned number = 0;

  if (trickno != UINT_MAX)
    trickno += 1;
  for (vector<Trick*>::const_iterator
       trick = this->tricks().begin();
       ((trickno > 0)
	&& (trick != this->tricks().end()));
       trick++, trickno--)
    if (((*trick)->winnerplayer() == player)
	&& (*trick)->intrickpile())
      number += 1;

  return number;
} // unsigned Game::numberoftricks_of_player(Player const& player, unsigned trickno) const

/**
 ** -> result
 **
 ** @param       team    team
 **
 ** @return      number of tricks, the team has won so far
 **              (without the current trick)
 **
 ** @version     0.4.4
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **/
unsigned
Game::numberoftricks_of_team(Team const& team) const
{
  unsigned number = 0;

  for (vector<Player*>::const_iterator player = this->players_begin();
       player != this->players_end();
       player++)
    if ((*player)->team() == team)
      number += this->numberoftricks_of_player(**player);

  return number;
} // unsigned Game::numberoftricks_of_team(Team const& team) const

/**
 **
 ** -> result
 **
 ** @param       player  player
 **
 ** @return      points, the player has got
 **
 ** @version     0.6.7
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 */
unsigned
Game::points_of_player(Player const& player) const
{
  unsigned points = 0;

  vector<Trick*>::const_iterator trick;
  for (trick = this->tricks().begin();
       trick != this->tricks().end();
       trick++)
    if (((*trick)->winnerplayer() == player)
	&& ((*trick)->intrickpile()))
      points += (*trick)->points();

  return points;
} // unsigned Game::points_of_player(Player const& player) const

/**
 **
 ** -> result
 **
 ** @param       player  player
 ** @param       trickno last trick to view
 **
 ** @return      points, the player has got til trickno
 **
 ** @version     0.6,7
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 */
unsigned
Game::points_of_player(Player const& player, unsigned trickno) const
{
  unsigned points = 0;

  if (trickno != UINT_MAX)
    trickno += 1;
  for (vector<Trick*>::const_iterator
       trick = this->tricks().begin();
       ((trickno > 0)
	&& (trick != this->tricks().end()));
       trick++, trickno--)
    if (((*trick)->winnerplayer() == player)
	&& (*trick)->intrickpile())
      points += (*trick)->points();

  return points;
} // unsigned Game::points_of_player(Player const& player, unsigned trickno) const

/**
 **
 ** -> result
 **
 ** @param       team    team
 **
 ** @return      points, the team has got
 **
 ** @version     0.4.4
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 **/
unsigned
Game::points_of_team(Team const& team) const
{
  unsigned points = 0;

  for (vector<Player*>::const_iterator player = this->players_begin();
       player != this->players_end();
       player++)
    if ((*player)->team() == team)
      points += this->points_of_player(**player);


  return points;
} // unsigned Game::points_of_team(Team const& team) const

/**
 ** -> result
 **
 ** @param       -
 **
 ** @return      the winner team
 **
 ** @author      Borg Enders
 ** @author      Diether Knof
 **
 ** @version     0.4.4
 **/
Team
Game::winnerteam() const
{
  DEBUG_ASSERTION((this->tricks_remaining_no() == 0),
		  "Game::winnerteam():\n"
		  "  game is not finished, yet\n"
		  << "current no: " << this->trick_current_no() << '\n'
		  << "remaining no: " << this->tricks_remaining_no() << '\n');

  unsigned i;
  vector<Team> t;
  Announcement highest_announcement_re = ANNOUNCEMENT::NOANNOUNCEMENT;
  Announcement highest_announcement_contra = ANNOUNCEMENT::NOANNOUNCEMENT;

  t = vector<Team>( this->playerno() );

  for( i = 0; i < this->playerno(); i++ )
    t[ i ] = this->team( this->player( i ) );

  // calculate highest announcements
  for( i = 0; i < this->playerno(); i++ )
  {
    Player const& pl = this->player( i );
    if ( this->team( this->player( i ) ) == TEAM::RE ) {
      if( pl.announcement() > highest_announcement_re )
	highest_announcement_re = pl.announcement();
    }

    if ( this->team( this->player(i) ) == TEAM::CONTRA ) {
      if( pl.announcement() > highest_announcement_contra )
	highest_announcement_contra = pl.announcement();
    }
  }


  Team winnerteam = TEAM::NOTEAM;

  // noone has made an announcement
  if ( (highest_announcement_re == ANNOUNCEMENT::NOANNOUNCEMENT )
      && ( highest_announcement_contra == ANNOUNCEMENT::NOANNOUNCEMENT ) ) {
    if (this->points_of_team(TEAM::RE) > 120)
      winnerteam = TEAM::RE;   
    else
      winnerteam = TEAM::CONTRA;   
  } // if (noone has announced)

  // re/contra has made an announcement and has kept it
  if ( (    ( highest_announcement_re == ANNOUNCEMENT::NO120)
	&& (this->points_of_team( TEAM::CONTRA ) < 120) )
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO90)
	  && (this->points_of_team( TEAM::CONTRA ) < 90 ) )
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO60)
	  && (this->points_of_team( TEAM::CONTRA ) < 60 ) )
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO30)
	  && (this->points_of_team( TEAM::CONTRA ) < 30 ) )
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO0)
	  && !this->hastrick( TEAM::CONTRA ) ) ) {
    winnerteam = TEAM::RE;   
  }
  if ( (    ( highest_announcement_re == ANNOUNCEMENT::NO120_REPLY)
	&& (this->points_of_team( TEAM::RE ) >= 120 ))
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO90_REPLY)
	  && (this->points_of_team( TEAM::RE ) >= 90 ))
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO60_REPLY)
	  && (this->points_of_team( TEAM::RE ) >= 60 ))
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO30_REPLY)
	  && (this->points_of_team( TEAM::RE ) >= 30 ))
      || ( ( highest_announcement_re == ANNOUNCEMENT::NO0_REPLY)
	  && this->hastrick( TEAM::RE ) ) ) {
    winnerteam = TEAM::RE;   
  }
  if ( (    ( highest_announcement_contra == ANNOUNCEMENT::NO120)
	&& (    (this->points_of_team( TEAM::RE ) < 120 )
	    || ( (this->points_of_team( TEAM::RE ) == 120 )
		&& (highest_announcement_re
		    > ANNOUNCEMENT::NOANNOUNCEMENT)
		&& (highest_announcement_re
		    != ANNOUNCEMENT::NO120_REPLY)
	       ) ) )
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO90)
	  && (this->points_of_team( TEAM::RE ) < 90 ))
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO60)
	  && (this->points_of_team( TEAM::RE ) < 60 ))
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO30)
	  && (this->points_of_team( TEAM::RE ) < 30 ))
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO0)
	  && !this->hastrick( TEAM::RE ) ) ) {
    winnerteam = TEAM::CONTRA;   
  }
  if ( (    ( highest_announcement_contra == ANNOUNCEMENT::NO120_REPLY)
	&& (this->points_of_team( TEAM::CONTRA ) >= 120 ))
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO90_REPLY)
	  && (this->points_of_team( TEAM::CONTRA ) >= 90 ))
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO60_REPLY)
	  && (this->points_of_team( TEAM::CONTRA ) >= 60 ))
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO30_REPLY)
	  && (this->points_of_team( TEAM::CONTRA ) >= 30 ))
      || ( ( highest_announcement_contra == ANNOUNCEMENT::NO0_REPLY)
	  && this->hastrick( TEAM::CONTRA ) ) ) {
    winnerteam = TEAM::CONTRA;   
  }

  // one player has made no announcement
  // but the other hasn't made enough points for his announcements
  if ( ( highest_announcement_re == ANNOUNCEMENT::NOANNOUNCEMENT )
      && (winnerteam == TEAM::NOTEAM) )
    winnerteam = TEAM::RE;

  if ( ( highest_announcement_contra == ANNOUNCEMENT::NOANNOUNCEMENT )
      && (winnerteam == TEAM::NOTEAM) )
    winnerteam = TEAM::CONTRA;


  return winnerteam;
} // Team Game::winnerteam() const
