/**********************************************************************
 *
 *   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 "constants.h"

#include "../utils/file.h"
#include "../utils/string.h"

#include "bug_report.h"

#include "../party/party.h"
#include "../party/rule.h"
#include "../game/game.h"
#include "../game/game_summary.h"
#include "../card/trick.h"
#include "../misc/setting.h"
#include "../misc/translator.h"
#include "../ui/ui.h"

#ifdef WINDOWS
// for 'mkdir'
#include <io.h>
#else
// for 'mkdir'
#include <sys/stat.h>
#endif

namespace OS_NS {

  string const BUG_REPORTS_DIRECTORY = "FreeDoko.BugReports";
  string const BUG_REPORT_FILE = "FreeDoko.BugReport";

  /**
   **
   ** create a report
   **
   ** @param	message	message of the user
   **
   ** @return	the filename in which the report is written
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  string
    BugReport::report(string const& message)
    {
      ostream* ofstr = NULL;
      string filename;
      { // trying to open a bug report file

	// Remark: could use a stack here
	vector<string> directories;

	directories.push_back(this->directory());
	directories.push_back(::setting(Setting::PRIVATE_DATA_DIRECTORY)
			      + "/" + BUG_REPORTS_DIRECTORY);
	directories.push_back(::setting(Setting::PRIVATE_DATA_DIRECTORY));
	directories.push_back("./" + BUG_REPORTS_DIRECTORY);
	directories.push_back(".");

	for (vector<string>::const_iterator dir = directories.begin();
	     dir != directories.end();
	     dir++) {
	  if (!DK::Utils::File::isdirectory(*dir)) {
	    ::ui->information(::translator("Creating directory '%sdirectory%' for the bug reports.",
					   *dir));
#ifdef WINDOWS
	    mkdir(dir->c_str());
#else
	    mkdir(dir->c_str(), 00777);
#endif
	  } // if (!DK::Utils::File::isdirectory(this->directory()))

	  filename = *dir + "/" + this->filename(*dir);
	  ofstr = new ofstream(filename.c_str(), ios::app);
	  if (ofstr->fail()) {
	    ::ui->information(::translator("Could not open '%sfilename%' for the bug report, trying '%sfilename2%'.",
					   filename.c_str(),
					   (*(dir + 1) + "/" + this->filename(*(dir + 1))
					   )));
	  } // if (ofstr->fail())
	  if (!ofstr->fail())
	    break;
	} // for (dir \in directories)
	if (ofstr->fail()) {
	  cerr << ::translator("Could not save the bug report :-(. \n"
			       "Please report manually.")
	    << endl;
	  cerr << ::translator("Press <Return> to display the bug report.")
	    << endl;
	  cin.get();
	  ofstr = &cerr;
	  filename = "standard error output";
	} // if (ofstr->fail())
      } // trying to open a bug report file

      const time_t time_now = time(NULL);

      string const bug_report_output
	= static_cast<ostringstream*>(this->ostr)->str();
      string const bug_report_human_output
	= static_cast<ostringstream*>(this->ostr_human)->str();

      // output of the version, system, time and message
      *ofstr << "# FreeDoko Bugreport\n"
	<< '\n'
	<< "Version: " << ::version
	<< '\n'
	<< "Compiled: " << __DATE__ << ", " << __TIME__ << '\n'
#ifdef WINDOWS
	<< "System: Windows\n"
#endif
#ifdef LINUX
	<< "System: Linux\n"
#endif
#ifdef HPUX
	<< "System: HPUX\n"
#endif
	<< "Time: " << asctime(localtime(&time_now)) << "\n"
	<< "Language: " << ::setting(Setting::LANGUAGE) << "\n";
      if ((::game_status & GAMESTATUS::GAME)
	  && (::game_status != GAMESTATUS::GAME_NEW))
	*ofstr << "Trick: " << this->game().trick_current_no() << endl;
      if (this->ostr_bak != NULL)
	*ofstr << "With backup\n";
      *ofstr << '\n'
	<< "/--------\\\n"
	<< "|Message:|\n"
	<< "\\--------/\n"
	<< message << "\n\n"
	<< "/-------\\\n"
	<< "|Output:|\n"
	<< "\\-------/\n"
	<< bug_report_output
	<< '\n'
	<< "Finished\n";

      if ((::game_status & GAMESTATUS::GAME)
	  && (::game_status != GAMESTATUS::GAME_NEW)) {
	// output of the current hands
	*ofstr << '\n'
	  << "Current hands:\n"
	  << "{\n";
	for (vector<Player*>::const_iterator player
	     = this->game().player_begin();
	     player != this->game().player_end();
	     player++) {
	  *ofstr << "Player " << (*player)->no()
	    << " (" << (*player)->name() << ")\n"
	    << (*player)->hand()
	    << '\n';
	} // for (player)
	*ofstr << "}\n";

	// output of the current trick
	if (this->game().trick_current_no() != UINT_MAX) {
	  *ofstr << '\n'
	    << "Current trick:\n"
	    << this->game().trick_current();
	} // if (this->game().trick_current_no() != UINT_MAX) 
      } // if (game_status & GAME)
      *ofstr << "\n\nPart human\n"
	<< "{\n"
	<< bug_report_human_output
	<< "}\n";

      *ofstr << endl;


      // output of the backup
      if (this->ostr_bak != NULL) {
	*ofstr
	  << "/-------\\\n"
	  << "|Backup:|\n"
	  << "\\-------/\n"
	  << static_cast<ostringstream*>(this->ostr_bak)->str()
	  << '\n'
	  << "/-------------\\\n"
	  << "|Human backup:|\n"
	  << "\\-------------/\n"
	  << static_cast<ostringstream*>(this->ostr_human_bak)->str()
	  << endl;
      } // if (this->ostr_bak != NULL)

      if (ofstr != cout) {
	dynamic_cast<ofstream*>(ofstr)->close();
	delete ofstr;
      }
      ofstr = NULL;

      // I don't know, how to reinitialize 'ostrstream'
      delete(this->ostr);
      delete(this->ostr_human);
      this->ostr = new ostringstream();
      this->ostr_human = new ostringstream();

      // copy the old data
      *(this->ostr) << bug_report_output;
      *(this->ostr_human) << bug_report_human_output;

      return filename;
    } // virtual string BugReport::report(string const& message)

  /**
   **
   ** Constructor
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  BugReport::BugReport() :
    OS(OS_TYPE::OS_BUG_REPORT),
    ostr(NULL),
    ostr_human(NULL),
    seed(0),
    ostr_bak(NULL),
    ostr_human_bak(NULL),
    seed_bak(UINT_MAX)
  {
    this->ostr = new ostringstream();
    this->ostr_human = new ostringstream();

    return ;
  } // BugReport::BugReport()

  /**
   **
   ** Destructor
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  BugReport::~BugReport()
  {
    delete this->ostr;
    delete this->ostr_human;
    delete this->ostr_bak;
    delete this->ostr_human_bak;

    return ;
  } // BugReport::~BugReport()

  /**
   **
   ** adds the text to the bug report
   **
   ** @param	text	text to add
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::add(string const& text)
    {
      *(this->ostr) << text;

      return ;
    } // void BugReport::add(string const& text)

  /**
   **
   ** the game is opened
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::game_open(Game const& game)
    {
      this->OS::game_open(game);

      this->new_game();
      return ;
    } // void BugReport::game_open(Game const& game)

  /**
   **
   ** the game is redistributed
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7b
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::game_redistribute()
    {
      this->new_game();
      return ;
    } // void BugReport::game_redistribute()

  /**
   **
   ** the game is opened
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::new_game()
    {
      this->stream_clear();

      this->seed = this->game().seed();

      *(this->ostr) << "Seed: " << this->game().seed() << '\n'
	<< "Startplayer: " << this->game().startplayer().no() << '\n'
	<< '\n'
	<< "Rules\n"
	<< "{\n"
	<< this->game().rule()
	<< "}\n";

      *(this->ostr) <<'\n'
	<<"Players\n"
	<< "{\n";

      for (vector<Player*>::const_iterator player
	   = this->game().player_begin();
	   player != this->game().player_end();
	   player++)
	*(this->ostr) << **player
	  << '\n';

      *(this->ostr) << "}\n";

      return ;
    } // void BugReport::new_game()

  /**
   **
   ** the game is started
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::game_start()
    {
      { // the reservation of the human player(s)
	bool in_loop = false;
	for (Player const* p = &this->game().startplayer();
	     (!in_loop
	      || (*p != this->game().startplayer()));
	     p = &this->game().player_following(*p),
	     in_loop = true) {
	  if (p->type() == Player::HUMAN) {
	    Reservation const& reservation = p->game().reservation(*p);
	    if (reservation.swines)
	      *(this->ostr_human) << "Reservation: swines\n";
	    if (reservation.hyperswines)
	      *(this->ostr_human) << "Reservation: hyperswines\n";
	    if (reservation.game_type != GAMETYPE::NORMAL)
	      *(this->ostr_human) << "Reservation: " << reservation.game_type
		<< '\n';
	    if (reservation.game_type == GAMETYPE::MARRIAGE)
	      *(this->ostr_human) << "Marriage: Selector: "
		<< reservation.marriage_selector
		<< '\n';
	  } // if (p->type() == Player::HUMAN)
	} // for (p \in this->game().player)
      } // the reservation of the human player(s)

      *(this->ostr) << '\n'
	<< "Gametype: " << this->game().type()
	<< '\n';

      if (this->game().type() == GAMETYPE::MARRIAGE) {
	*(this->ostr) << "Selector: " << name(this->game().marriage_selector()) << '\n'
	  << "Bride: player "
	  << this->game().soloplayer().no()
	  << '\n';
      } // if (this->game().type() == GAMETYPE::MARRIAGE)

      if (is_solo(this->game().type()))
	*(this->ostr) << "Soloplayer: player "
	  << this->game().soloplayer().no()
	  << '\n';

      // output of the hands
      *(this->ostr) <<'\n'
	<<"Hands:\n"
	<< "{\n";

      for (vector<Player*>::const_iterator player
	   = this->game().player_begin();
	   player != this->game().player_end();
	   player++)
	*(this->ostr) << "Player " << (*player)->no()
	  << " (" << (*player)->name()
	  << ")\n"
	  << const_cast<Player const*>(*player)->hand()
	  << '\n';

      *(this->ostr) << "}\n";

      return ;
    } // void BugReport::game_start()


  /**
   **
   ** the game is finished
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::game_finish()
    { 
      GameSummary const& game_summary
	= this->party().last_game_summary();

      *(this->ostr) << '\n'
	<< "Game summary:\n"
	<< game_summary
	<< '\n';

      if (!game_summary.specialpoints().empty()) {
	*(this->ostr) << "Specialpoints:\n";
	*(this->ostr) << "\tteam\tvalue\tdescription:\n";
	for (Specialpointsvector::const_iterator
	     special_point = game_summary.specialpoints().begin();
	     special_point != game_summary.specialpoints().end();
	     special_point++) {
	  *(this->ostr) << '\t';
	  *(this->ostr) << special_point->team;
	  // extra rule
	  if ((special_point->type == SPECIALPOINT::NO120_SAID)
	      && (this->game().rule()(Rule::ANNOUNCEMENT_RE_DOUBLES)))
	    *(this->ostr) << "\t" << "*2";
	  else
	    *(this->ostr) << "\t"
	      << Value_of_Specialpoint(special_point->type);
	  *(this->ostr) << "\t" << name(special_point->type)
	    << '\n';
	} // for (special_point)
	*(this->ostr) << "\t= " << game_summary.points() << '\n';
      } // if (!game_summary.specialpoints().empty())

      return ;
    } // void BugReport::game_finish()

  /**
   **
   ** a card is played
   **
   ** @param	card	card that is played
   ** @param	player	the player who has played the card
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::card_played(Card const& card, Player const& player)
    { 
      *(this->ostr) << "Played: "
	<< "Player " << player.no() << ": "
	<< card
	<< '\n';

      if (player.type() & Player::HUMAN) {
	*(this->ostr_human) << "Played: "
	  << card
	  << '\n';
	this->backup_clear();
      } // if (player.type() & Player::HUMAN)

#if 0
      *(this->ostr) << '\n'
	<< "Team information for player "
	<< this->game().player_current().no() << ":\n";
      for (vector<Player*>::const_iterator player = this->game().player_begin();
	   player != this->game().player_end();
	   player++)
	*(this->ostr) << (*player)->no() << ": "
	  << this->game().player_current().teaminfo(**player)
	  << '\n';
#endif

      return ;
    } // void BugReport::card_played(card, player)

  /**
   **
   ** the announcement 'announcement' has been made by player 'player'
   **
   ** @param	announcement	the announcement
   ** @param	player		the player, who has made the announcement
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::announcement_made(Announcement const& announcement,
				 Player const& player)
    {
      *(this->ostr) << "Announcement: "
	<< "player " << player.no() << ": "
	<< announcement
	<< '\n';
      if (player.type() & Player::HUMAN)
	*(this->ostr_human) << "Announcement: "
	  << announcement
	  << '\n';

      return ;
    } // void BugReport::announcement_made(Announcement const& announcement, Player const& player)

  /**
   **
   ** the player has swines
   **
   ** @param	player		the player with the swines
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::swines_announced(Player const& player)
    {
      *(this->ostr) << "Swines: "
	<< "player " << player.no()
	<< '\n';

      return ;
    } // void BugReport::swines_announced(Player const& player)

  /**
   **
   ** the player has hyperswines
   **
   ** @param	player		the player with the swines
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::hyperswines_announced(Player const& player)
    {
      *(this->ostr) << "hyperswines: "
	<< "player " << player.no()
	<< '\n';

      return ;
    } // void BugReport::hyperswines_announced(Player const& player)

  /**
   **
   ** 'player' shifts 'cardno' cards
   **
   ** @param	player	the player who shifts the cards
   ** @param	cardno	the number of cards that are shifted
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::poverty_shift(Player const& player, unsigned const cardno)
    {
      *(this->ostr) << "Poverty: "
	<< "player " << player.no() << " shifts "
	<< cardno << " cards."
	<< '\n'
	<< *(player.game().poverty_cards())
	<< '\n';

      if (player.type() & Player::HUMAN)
	*(this->ostr_human) << "poverty: "
	  << "shifting "
	  << cardno << " cards."
	  << '\n'
	  << *(player.game().poverty_cards())
	  << '\n';

      return ;
    } // void BugReport::poverty_shift(Player const& player, unsigned const cardno)

  /**
   **
   ** the player has denied to take the cards
   **
   ** @param	player	player who has denied
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::poverty_take_denied(Player const& player)
    {
      if (player.type() & Player::HUMAN)
	*(this->ostr_human) << "Poverty: "
	  << "denied\n";

      return ;
    } // void BugReport::take_denied(Player const& player)

  /**
   **
   ** all players have denied to take the cards
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::poverty_take_denied_by_all()
    {
      *(this->ostr) << "Poverty: "
	<< "all players denied"
	<< '\n';

      return ;
    } // void BugReport::take_denied_by_all()

  /**
   **
   ** 'player' accepts to take the shifted cards
   ** and returns 'cardno' cards with 'trumpno' trumps
   **
   ** @param	player	the player who has denied to take the cards
   ** @param	cardno	number of cards that are given back
   ** @param	trumpno	number of trumps of the cards
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::poverty_take_accepted(Player const& player,
				     unsigned const cardno, unsigned const trumpno)
    {
      *(this->ostr) << "Poverty: "
	<< "player " << player.no() << " accepts "
	<< "and returns " << cardno << " cards "
	<< "with " << trumpno << " trumps:"
	<< '\n'
	<< *(player.game().poverty_cards());
      if (player.type() & Player::HUMAN)
	*(this->ostr_human) << "Poverty: "
	  << "accepted: "
	  << "returns " << cardno << " cards "
	  << "with " << trumpno << " trumps."
	  << '\n'
	  << *(player.game().poverty_cards())
	  << '\n';

      return ;
    } // void BugReport::poverty_take_accepted(Player const& player, unsigned const cardno, unsigned const trumpno)

  /**
   **
   ** genscher: Information of the new team
   **
   ** @param	player	genscher player
   ** @param	partner	partner of the gensher player
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.7
   **
   **/
  void
    BugReport::genscher(Player const& player,
			Player const& partner)
    {
      *(this->ostr) << "Genscher: "
	<< "player " << player.no()
	<< " selects player " << partner.no()
	<< '\n';
      if (player.type() & Player::HUMAN)
	*(this->ostr_human) << "Genscher: "
	  << "player " << player.no()
	  << " selects player " << partner.no()
	  << '\n';

      return ;
    } // void BugReport::genscher(Player const& player, Player const& partner)

  /**
   **
   ** a new trick is opened
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::trick_open(Trick const& trick)
    {
      this->OS::trick_open(trick);

      *(this->ostr) << '\n'
	<< "Trick: " << this->game().trick_current_no() << '\n';

      return ;
    } // void BugReport::trick_open(Trick const& trick)

  /**
   **
   ** the trick 'trick' is full
   **
   ** @param	trick	the full trick
   **
   ** @return	-
   **
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::trick_full()
    {
      *(this->ostr) << '\n'
	<< "Trick full: " << this->game().trick_current_no() << '\n'
	<< this->trick();

#if 0
      // every four tricks write the current hands in the bug report stream
      if (this->game().trick_current_no() % 4 == 3) {
	*(this->ostr) << '\n'
	  << "Hands:\n";
	for (vector<Player*>::const_iterator player = this->game().player_begin();
	     player != this->game().player_end();
	     player++)
	  *(this->ostr) << "Player " << (*player)->no()
	    << " (" << (*player)->name() << ")\n"
	    << const_cast<Player const*>(*player)->hand()
	    << '\n';
      }
#endif

      return ;
    } // void BugReport::trick_full()

  /**
   **
   ** clear the bug report stream and make a backup before
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::stream_clear()
    {
      this->backup_clear();
      this->ostr_bak = this->ostr;
      this->ostr_human_bak = this->ostr_human;
      this->seed_bak = this->seed;

      this->ostr = new ostringstream();
      this->ostr_human = new ostringstream();

      return ;
    } // virtual void BugReport::stream_clear()

  /**
   **
   ** clear the backup
   **
   ** @param	-
   **
   ** @return	-
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  void
    BugReport::backup_clear()
    {
      // I don't know, how to reinitialize 'ostrstream'

      delete this->ostr_bak;
      this->ostr_bak = NULL;
      delete this->ostr_human_bak;
      this->ostr_human_bak = NULL;
      this->seed_bak = UINT_MAX - 1;

      return ;
    } // virtual void BugReport::backup_clear()

  /**
   **
   ** -> result
   **
   ** @param	-
   **
   ** @return	the directory to save the bug report in
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  string
    BugReport::directory() const
    {
      if (::setting(Setting::SAVE_BUG_REPORTS_ON_DESKTOP)) {
	return (DK::Utils::File::desktop_directory()
		+ "/" + BUG_REPORTS_DIRECTORY);
      } // if (::setting(Setting::SAVE_BUG_REPORTS_ON_DESKTOP))

      return BUG_REPORTS_DIRECTORY;
    } // virtual void BugReport::directory()

  /**
   **
   ** -> result
   **
   ** @param	-
   **
   ** @return	the filename (BUG_REPORT_FILE.`seed`)
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  string
    BugReport::filename() const
    {
      return this->filename(this->directory());
    } // virtual void BugReport::filename()

  /**
   **
   ** -> result
   **
   ** @param	directory	the directory to save the bug report in
   **
   ** @return	the filename (BUG_REPORT_FILE.`seed`)
   ** 
   ** @version	0.6.7
   **
   ** @author	Diether Knof
   **
   **/
  string
    BugReport::filename(string const directory) const
    {
      string filename = (BUG_REPORT_FILE
			 + ((::game_status >= GAMESTATUS::GAME_INIT)
			    ? string("." + DK::Utils::String::to_string(this->game().seed()))
			    : string(".party")
			   ));

      // schaue, ob die Datei noch nicht existiert
      if (!DK::Utils::File::isfile(directory + "/" + filename))
	return filename;

      // die Datei existiert schon. haenge eine Nummer dran und suche die erste, 
      // die nicht existiert.
      unsigned n = 1;
      while (DK::Utils::File::isfile(directory + "/" + filename + "_"
				     + DK::Utils::String::to_string(n))) {
	n += 1;
      } // while (datei existiert)

      return (filename + "_" + DK::Utils::String::to_string(n));
    } // virtual void BugReport::filename(string const directory)

} // namespace OS_NS
