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

#include "../game/game.h"
#include "../player/player.h"
#include "../card/trick.h"
#include "../os/bug_report_replay.h"

#include <fstream>

#ifdef BOOST
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#endif

// the references check
ReferencesCheck* references_check = NULL;

/**
 ** constructor
 **
 ** @param     -
 **
 ** @return    -
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
ReferencesCheck::ReferencesCheck() :
  references_dir_(),
  report_file_("References.report.csv"),
#ifdef BOOST
  dir_itr(),
#endif
  statistics_()
{ }

/**
 ** constructor
 **
 ** @param     -
 **
 ** @return    -
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
ReferencesCheck::ReferencesCheck(string const& references_dir) :
  references_dir_(),
  report_file_("References.report.csv")
{
  this->set_directory(references_dir);

  return ;
} // ReferencesCheck::ReferencesCheck(string references_dir)

/**
 ** destructor
 **
 ** @param     -
 **
 ** @return    -
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
ReferencesCheck::~ReferencesCheck()
{
  this->write_statistics();
  return ;
} // ReferencesCheck::~ReferencesCheck()

/**
 ** set the directory
 **
 ** @param     references_dir   the directory with the references
 **
 ** @return    -
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
void
ReferencesCheck::set_directory(string const& references_dir)
{
  this->references_dir_ = references_dir;
  if (this->references_dir().empty())
    return ;
  ofstream ostr((this->references_dir() + "/" + this->report_file()).c_str());
  if (ostr.fail()) {
    this->report_file_.clear();
    return ;
  } // if (ostr.fail())

  ostr << "seed;"
    << "trick;"
    << "player;"
    << "discrepancy;"
    << "real action;"
    << "ref action;"
    << "comment\n";

  cout << "Created file '"
    << this->references_dir() + "/" + this->report_file()
    << "' for the references report.\n";

#ifdef BOOST
  for (this->dir_itr
       = boost::filesystem::directory_iterator(this->references_dir());
       dir_itr != boost::filesystem::directory_iterator();
       ++dir_itr) {
    try {
      if (dir_itr->leaf() == this->report_file())
        continue;
      if (!boost::filesystem::is_directory(*this->dir_itr))
        break;
    }
    catch (std::exception const& ex) {
      cerr << dir_itr->leaf() << " " << ex.what() << endl;
      this->references_dir_ = "";
    }
  } // for (this->dir_itr)
#endif

  this->statistics_.reset();

  return ;
} // void ReferencesCheck::set_directory(string references_dir)

/**
 ** open the next file
 **
 ** @param     -
 **
 ** @return    whether another file was loaded
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
bool
ReferencesCheck::next_reference()
{
#ifdef BOOST
  for (++this->dir_itr;
       this->dir_itr != boost::filesystem::directory_iterator();
       ++this->dir_itr) {
    try {
      if (this->dir_itr->leaf() == this->report_file()) {
        continue;
      }
      if (!boost::filesystem::is_directory(*this->dir_itr)) {
        if (this->load_reference())
          throw GAMESTATUS::PARTY_NEW;
        return true;
      }
    }
    catch (std::exception const& ex) {
      cerr << dir_itr->leaf() << " " << ex.what() << endl;
      this->references_dir_ = "";
    }
  } // for (this->dir_itr)
  throw GAMESTATUS::QUIT;
#endif

  return false;
} // bool ReferencesCheck::next_reference()

/**
 ** open the reference
 **
 ** @param     -
 **
 ** @return    whether the loading was successful
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
bool
ReferencesCheck::load_reference()
{
#ifdef BOOST
  if (this->dir_itr == boost::filesystem::directory_iterator())
    return false;

  OS_NS::BugReportReplay* bug_report_replay
    = new OS_NS::BugReportReplay(this->dir_itr->string());
  if (!bug_report_replay->loaded()) {
    cerr << "Error loading the reference '" << this->dir_itr->string()
      << "'" << endl;
    delete bug_report_replay;
    return false;
  }
#endif

  this->statistics_.no_files += 1;

  return true;
} // bool ReferencesCheck::load_reference()

/**
 ** destructor
 **
 ** @param     game           the game
 ** @param     game_action    the action from the game
 ** @param     check_action   the action to check
 ** @param     comment        the comment of the check
 ** @param     discrepancy    the discrepancy between the two actions
 ** @param     last_check     whether this is the last check
 **
 ** @return    -
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
void
ReferencesCheck::check(Game const& game,
                       GameplayAction const& game_action,
                       GameplayAction const& check_action,
                       string const& comment,
                       GameplayAction::Discrepancy const& discrepancy,
                       bool const last_check)
{
  this->statistics_.no_checks += 1;
  if (!discrepancy)
    this->statistics_.no_success += 1;

  string output;
  { // create the result line
    // format:
    //   seed (trick, player); OK; ; real action; text
    //   seed (trick, player); discrepancy; real action; check action; text
    // semicolon separated list:
    // 1) seed
    // 2) trick
    // 3) player
    // 4) discrepancy
    // 5) real gameplay action / empty
    // 6) gameplay action from the reference
    // 7) comment (optional)
    ostringstream ostr;
#ifdef BOOST
    ostr << setfill('0') << setw(6) << this->dir_itr->leaf()
      << ';' << setw(2) << game.trick_current_no() << ';'
      << check_action.player << ';';
#endif
    if (!discrepancy)
      ostr << "OK";
    else
      ostr << discrepancy;
    ostr << ';' << game_action.str()
      << ';' << check_action.str();
    if (!comment.empty())
      ostr << ';' << comment;
    output = ostr.str();
  } // create the result line

  // print the result line
  cout << "check: " << output << endl;

  { // save the result line
    if (this->references_dir().empty()
        && !this->report_file().empty())
      this->set_directory(".");
    if (!this->report_file().empty()) {
      ofstream ostr((this->references_dir() + "/"
                     + this->report_file()).c_str(),
                    ios::app);
      if (ostr.fail()) {
        this->report_file_.clear();
        return ;
      } // if (ostr.fail())
      ostr << output << '\n';
    } // if (output set)
  } // save the result line

  if (discrepancy || last_check) {
    throw GAMESTATUS::PARTY_NEW;
    exit(discrepancy);
  }

  return ;
} // void ReferencesCheck::check(Game game, GameplayAction game_action, GameplayAction check_action, string comment, GameplayAction::Discrepancy discrepancy, bool last_check)

/**
 ** write the statistics
 **
 ** @param     -
 **
 ** @return    -
 **
 ** @author    Diether Knof
 **
 ** @version   0.7.6
 **/
void
ReferencesCheck::write_statistics() const
{
  // print the statistics
  cout << '\n'
    << "reference checks\n"
    << "  files:   "
    << setw(static_cast<int>(ceil(log10(this->statistics().no_checks))))
    << this->statistics().no_files << '\n'
    << "  checks:  "
    << this->statistics().no_checks << '\n'
    << "  success: "
    << setw(static_cast<int>(ceil(log10(this->statistics().no_checks))))
    << this->statistics().no_success << '\n'
    << "  failed:  "
    << setw(static_cast<int>(ceil(log10(this->statistics().no_checks))))
    << (this->statistics().no_checks
        - this->statistics().no_success) << '\n'
    << '\n';

  { // save the result line
    if (this->references_dir().empty()
        && !this->report_file().empty()) {
      return ;
    }
    if (!this->report_file().empty()) {
      ofstream ostr((this->references_dir() + "/"
                     + this->report_file()).c_str(),
                    ios::app);
      if (ostr.fail()) {
        return ;
      } // if (ostr.fail())
      ostr << '\n'
        << "files;"
        << this->statistics().no_files << '\n'
        << "checks;"
        << this->statistics().no_checks << '\n'
        << "success;"
        << this->statistics().no_success << '\n'
        << "failed;"
        << (this->statistics().no_checks
            - this->statistics().no_success) << '\n';
    } // if (output set)
  } // save the result line

  return ;
} // ReferencesCheck::write_statistics() const
