/**********************************************************************
 *
 *   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
 *
 ********************************************************************/


/**
 **
 ** Alpha-Version of FreeDoko 
 **
 **   developed since fall 2001
 **
 **/

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

#include "class/getopt/getopt.h"

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

#include "utils.h"
#include "basistypes.h"

#include "misc/setting.h"
#include "misc/translations.h"
#include "misc/bug_report.h"

#include "party/party.h"

#include "ui/ui.wrap.h"
#include "ui/ui.dummy.h"

#ifdef USE_NETWORK
#include "network/server.h"
#endif

#include "os/seed.h"
#include "os/party_points.h"
#include "os/bug_report_replay.h"
extern OS_NS::BugReportReplay* bug_report_replay;

UI* ui = NULL;
Translator translator;
Setting setting_default;
Setting setting;
Party party;

#include "utils.h"


#include "player/namegen.h"
#ifndef WINDOWS
int
main(int argc, char* argv[])
#else
  // ToDo: use 'WinMain'
int
main(int argc, char* argv[])
#endif
{
  try {
    ::game_status = GAMESTATUS::PROGRAMSTART;
    string party_filename; // file with the party to load from

    using DK::Utils::Version;
    using DK::Utils::Date;


    // make an assertion create a bug report
    ::debug_function = &create_assertion_bug_report;

    // list of all Versions
    ::all_versions.push_back(new Version(0,6,3,     Date(2004,  5,  5), true));
    ::all_versions.push_back(new Version(0,6,4,     Date(2004,  6,  1), true));
    ::all_versions.push_back(new Version(0,6,5,     Date(2004,  6, 24), true));
    ::all_versions.push_back(new Version(0,6,5,'b', Date(2004,  6, 28), true));
    ::all_versions.push_back(new Version(0,6,6,     Date(2004,  9, 25), true));
    ::all_versions.push_back(new Version(0,6,7,     Date(2004, 12, 16), true));
    ::all_versions.push_back(new Version(0,6,7,'b', Date(2004, 12, 20), true));
    ::all_versions.push_back(new Version(0,6,8,     Date(2005,  4, 11), true));
    ::all_versions.push_back(new Version(0,6,9,     Date(2005,  6, 30), true));
    ::all_versions.push_back(new Version(0,7,0,     Date(2005,  8, 22), true));
    ::all_versions.push_back(new Version(0,7,1,     Date(2005, 11, 12), true));
    ::all_versions.push_back(new Version(0,7,2,     Date(2006,  1, 23), true));
    ::all_versions.push_back(new Version(0,7,2,'b', Date(2006,  2,  9), true));
    ::all_versions.push_back(new Version(0,7,3,     Date(2006,  8, 10), true));

    // initialisation for random functions
    ::srand(static_cast<unsigned>(::time(NULL) % RAND_MAX));

    ui = new UI_Wrap;
#ifndef RELEASE
#ifdef DKNOF
    //static_cast<UI_Wrap*>(ui)->append(new_OS(OS_TYPE::GAMEPLAY));
#endif
#endif
#ifdef POSTPHONED
    static_cast<UI_Wrap*>(ui)->append(new_OS(OS_TYPE::CHATTER));
#endif


    // output of the seed each game
    static_cast<UI_Wrap*>(ui)->append(new OS_NS::Seed(new ofstream((::setting(Setting::PRIVATE_DATA_DIRECTORY) + "/seed").c_str(),
								   ios::app)));
#ifndef RELEASE
    // output of the party points
    static_cast<UI_Wrap*>(ui)->append(new OS_NS::PartyPoints(new ofstream("FreeDoko.party_points")));
#endif

#ifdef USE_NETWORK
    // open the network
    ::server = new Network::Server();
    static_cast<UI_Wrap*>(::ui)->append(::server);
    // if not empty, a connection for the program start
    string connection;
#endif // #ifdef USE_NETWORK

#ifdef USE_THREADS
    if (getenv("FREEDOKO_THREADS"))
      THREADS = atoi(getenv("FREEDOKO_THREADS"));
#endif // #ifdef USE_THREADS

    // ui
    UI* ui_tmp = NULL;
    { // parse options

      // This file contains the help you get calling the program with '-?'.
      // It includes what arguments you can use.
#ifdef WINDOWS
#include "text/gpl.dos.string"
#include "text/help.dos.string"
#else
#include "text/gpl.string"
#include "text/help.string"
#endif

      // load the settings
      ::setting.load();

      // parse the options
      GetOpt::Option option;
      while (option =
	     GetOpt::getopt(argc, argv,
			    "help",         '?',  GetOpt::Syntax::BOOL,
			    "hilfe",        'h',  GetOpt::Syntax::BOOL,
			    "version",      'v',  GetOpt::Syntax::BOOL,
			    "license",      'L',  GetOpt::Syntax::BOOL,
			    "defines",      '\0', GetOpt::Syntax::BOOL,
			    "ui",           'u',  GetOpt::Syntax::BSTRING,
			    "settings",     '\0', GetOpt::Syntax::BSTRING,
			    "name",         'n',  GetOpt::Syntax::BSTRING,
			    "language",     'l',  GetOpt::Syntax::BSTRING,
			    "seed",         's',  GetOpt::Syntax::UNSIGNED,
			    "startplayer",  'p',  GetOpt::Syntax::UNSIGNED,
			    "cardset",      'C',  GetOpt::Syntax::BSTRING,
			    "cards_height", 'H',  GetOpt::Syntax::UNSIGNED,
			    "bug_report",   'b',  GetOpt::Syntax::BSTRING,
			    "fast_play",    'F',  GetOpt::Syntax::INT,
#ifdef USE_THREADS
			    "threads",      'T',  GetOpt::Syntax::UNSIGNED,
#endif
#ifdef USE_NETWORK
			    "start_server", '\0', GetOpt::Syntax::BOOL,
			    "connect",      '\0', GetOpt::Syntax::BSTRING,
#endif
			    // end of arguments
			    "", 0, GetOpt::Syntax::END)) {

			      if (option.fail()) {
				// wrong usage
				cerr << argv[0] << "\n"
				  << "wrong usage: "
				  << option.error() << " " << option.value_string() << "\n"
				  << "For the syntax type '" << argv[0] << " -?'"
				  << endl;

				return EXIT_FAILURE;
			      } // if (option.fail())

			      if ((option.name() == "help") 
				  || (option.name() == "hilfe")) {
				// output of the help
				cout << help_string << endl;
				return EXIT_SUCCESS;
			      } else if (option.name() == "version") {
				// output of the version
				cout << "FreeDoko " << ::version << "\n"
				  << "\n"
				  << GPL_string
				  << endl;
				return EXIT_SUCCESS;
			      } else if (option.name() == "license") {
				// output of the license (GPL)
				cout << "FreeDoko -- License:\n\n"
				  << GPL
				  << '\n'
				  << '\n'
				  << "----------\n"
				  << '\n'
				  << "Cardset '" << ::setting(Setting::CARDSET) << "' license:\n"
				  << cardset_license
				  << endl;
				return EXIT_SUCCESS;
			      } else if (option.name() == "defines") {
#ifdef VERSION_DESCRIPTION
				cout << "VERSION_DESCRIPTION = " << VERSION_DESCRIPTION << '\n';
#endif
#ifdef RELEASE
				cout << "RELEASE\n";
#endif
#ifdef ASSERTION_GENERATES_SEGFAULT
				cout << "ASSERTION_GENERATES_SEGFAULT\n";
#endif
#ifdef DKNOF
				cout << "DKNOF\n";
#endif
#ifdef BENDERS
				cout << "BENDERS\n";
#endif
				cout << '\n';
				cout << "directories:\n";
#ifdef PUBLIC_DATA_DIRECTORY_VALUE
				cout << "  PUBLIC_DATA_DIRECTORY_VALUE = " << PUBLIC_DATA_DIRECTORY_VALUE << '\n';
#endif
#ifdef MANUAL_DIRECTORY_VALUE
				cout << "  MANUAL_DIRECTORY_VALUE      = " << MANUAL_DIRECTORY_VALUE << '\n';
#endif
				cout << '\n';
				cout << "modules:\n";
#ifdef USE_UI_TEXT
				cout << "  USE_UI_TEXT  = " << USE_UI_TEXT << '\n';
#endif
#ifdef USE_UI_GTKMM
				cout << "  USE_UI_GTKMM = " << USE_UI_GTKMM << '\n';
#endif
#ifdef USE_NETWORK
				cout << "  USE_NETWORK  = " << USE_NETWORK << '\n';
#endif
#ifdef USE_THREADS
				cout << "  USE_THREADS (" << THREADS << ")\n";
#endif
				return EXIT_SUCCESS;
			      } else if (option.name() == "ui") {
				delete ui_tmp;
				if (option.value_string() == "none")
				  ui_tmp = UI::new_(UI_TYPE::DUMMY);
#ifdef USE_UI_TEXT
				else if (option.value_string() == "text")
				  ui_tmp = UI::new_(UI_TYPE::TEXT);
#endif
#ifdef USE_UI_GTKMM
				else if (option.value_string() == "gtkmm")
				  ui_tmp = UI::new_(UI_TYPE::GTKMM_DOKO);
#endif
				else {
				  cerr << "ui '" << option.value_string() << "' not implemented.\n"
				    << "exiting";
				  return 1;
				} // if (option.value_string() == "...")

			      } else if (option.name() == "settings") {
				setting.load(option.value_string());

			      } else if (option.name() == "name") {
				setting.set(Setting::NAME, option.value_string());

			      } else if (option.name() == "language") {
				setting.set(Setting::LANGUAGE, option.value_string());

			      } else if (option.name() == "seed") {
				::party.set_seed(option.value(GetOpt::Option::UNSIGNED));
				party_filename = "-";

			      } else if (option.name() == "startplayer") {
				unsigned const no = option.value(GetOpt::Option::UNSIGNED);
				if ( !( (no >= 1)
				       && (no <= ::party.playerno()) ) ) {
				  cerr << "startplayer number '" << no << "' not valid.\n"
				    << "must be between '1' and '" << ::party.playerno() << "'.\n"
				    << "ignoring the value\n"
				    << endl;
				  continue;
				}
				::party.set_startplayer(no - 1);
				party_filename = "-";

			      } else if (option.name() == "graphic_size") {
				setting.set(Setting::CARDS_HEIGHT,
					    option.value(GetOpt::Option::UNSIGNED));

			      } else if (option.name() == "cardset") {
				setting.set(Setting::CARDSET, option.value_string());

			      } else if (option.name() == "bug_report") {
				OS_NS::BugReportReplay* bug_report_replay
				  = new OS_NS::BugReportReplay(option.value_string());
				if (!bug_report_replay->loaded()) {
				  cerr << "Error loading the bug report '" << option.value_string()
				    << "'" << endl;
				  delete bug_report_replay;
				}
			      } else if (option.name() == "fast_play") {
				// PAUSE          = 1,                 // skip pauses
				// PLAYER         = PAUSE         << 1 // change human players to ais
				// RANDOM_AI      = PLAYER        << 1, // set the players to the random ai
				// FULL_TRICK     = RANDOM_AI     << 1 // skip full trick window
				// PARTY_START    = FULL_TRICK    << 1 // automatical start party
				// GAME_FINISHED  = PARTY_START   << 1 // skip game finished dialog
				// SHOW_ALL_HANDS = GAME_FINISHED << 1 // show all hands

				FAST_PLAY = option.value(GetOpt::Option::INT);
				if (FAST_PLAY & FAST_NS::PLAYER) {
				  // change all humans to ai's
				  for (unsigned p = 0; p < ::party.playerno(); p++) {
				    if (::party.player(p).type() == Player::HUMAN)
				      ::party.set_playertype(p, Player::AI);
				  }
				} // if (FAST_PLAY | FAST_NS::PLAYER)

				if (FAST_PLAY & FAST_NS::RANDOM_AI) {
				  // set all ai's to random ai's
				  for (unsigned p = 0; p < ::party.playerno(); p++) {
				    if (::party.player(p).type() == Player::AI)
				      ::party.set_playertype(p, Player::AI_RANDOM);
				  }
				} // if (FAST_PLAY & FAST_NS::RANDOM_AI)

#ifdef USE_THREADS
			      } else if (option.name() == "threads") {
				THREADS = option.value(GetOpt::Option::UNSIGNED);
#endif // #ifdef USE_THREADS
#ifdef USE_NETWORK
			      } else if (option.name() == "start_server") {
				::server->create_listener(Network::Server::FREEDOKO_STANDARD_PORT);

			      } else if (option.name() == "connect") {
				connection = option.value_string();
#endif // #ifdef USE_NETWORK

			      } else if (option.name().empty()
					 && party_filename.empty()) {
				party_filename = option.value_string();
			      } else {
				cerr << "Option '" << option.name() << "' unknown!" << endl;
				exit(1);
			      } // if (option.name() == ...)
			    } // while (getopt())
    } // parse options

#ifdef USE_THREADS
    if (THREADS == 0) {
      // ToDo
      // automatic detection of the number of cpu's
      //
      //"egrep -c ^cpu[0-9]+ /proc/stat || :";

      if (THREADS == 0)
	THREADS = 1;
    } // if (THREADS == 0)
    cout << "Thread support is experimental. Use at your own risk.\n";
#endif // #ifdef USE_THREADS

    if (ui_tmp == NULL) {
      // set the default ui
#if defined(USE_UI_GTKMM)
      ui_tmp = UI::new_(UI_TYPE::GTKMM_DOKO);
#elif defined(USE_UI_AATEXT)
      ui_tmp = UI::new_(UI_TYPE::AATEXT);
#elif defined(USE_UI_TEXT)
      ui_tmp = UI::new_(UI_TYPE::TEXT);
#else
      ui_tmp = UI::new_(UI_TYPE::DUMMY);
#endif
    } // if (ui_tmp == NULL)


#ifdef RELEASE
    if (FAST_PLAY)
      ::setting.set(Setting::SAVE_PARTY_CHANGES, false);
#endif

    { // set the ui
      delete static_cast<UI_Wrap*>(::ui)->ui;
      static_cast<UI_Wrap*>(::ui)->ui = ui_tmp;

      if (SEED_OUT) {
	delete static_cast<UI_Wrap*>(::ui)->ui;
	static_cast<UI_Wrap*>(::ui)->ui = UI::new_(UI_TYPE::DUMMY);
      } // if (SEED_OUT)

      // test, whether the ui is valid
      if ((::ui->type() == UI_TYPE::DUMMY)
	  && !FAST_PLAY) {
	cerr << "Uitype is 'dummy', aborting." << endl;
#ifdef RELEASE
	exit(1);
#endif
      } // if (::ui->type() == UI::NONE)
    } // set the ui

    // add the bug report in the ui.wrap,
    // so it gets the information of the gameplay
    static_cast<UI_Wrap*>(::ui)->append(&::bug_report);

    { // if this is the first start, create some directories
      vector<string> dirnames;
      dirnames.push_back(::setting(Setting::PRIVATE_DATA_DIRECTORY));
      dirnames.push_back(::setting(Setting::PRIVATE_DATA_DIRECTORY)
			 + "/" + ::setting(Setting::PARTIES_DIRECTORY));
      dirnames.push_back(::setting(Setting::PRIVATE_DATA_DIRECTORY)
			 + "/" + ::setting(Setting::RULES_DIRECTORY));
      dirnames.push_back(::setting(Setting::PRIVATE_DATA_DIRECTORY)
			 + "/" + ::setting(Setting::AI_DIRECTORY));
      Translator::Translation information = ::translation("");

      for (vector<string>::iterator dir = dirnames.begin();
	   dir != dirnames.end();
	   dir++) {
	if (!DK::Utils::File::isdirectory(*dir)) {
	  information += (::translation("Message::creating directory \'%sdir%\'",
					*dir)
			  + "\n");
#ifdef WINDOWS
	  mkdir(dir->c_str());
#else
	  mkdir(dir->c_str(), 00700);
#endif
	} // if (!isdirectory(*dir))
      } // for (dir \in dirnames)
      if (!information.empty())
	::ui->information(information, INFORMATION::NORMAL);
    } // if this is the first start, create some directories

    { // read the version of the last usage of FreeDoko and write the new version

      // read the old version
      DK::Utils::Version* const version_old
	= DK::Utils::Version::new_from_file(::setting(Setting::PRIVATE_DATA_DIRECTORY)
					    + "/Version");

      { // write the new version
	ofstream ostr((::setting(Setting::PRIVATE_DATA_DIRECTORY)
		       + "/Version").c_str());
	if (!ostr.fail())
	  ostr << version;
      } // write the new version

      if (version_old == NULL) {
	// This seems to be the first start -- give some introduction.
	::ui->first_run(::translation("Greeting::first run"));
      } else { // if !(version_old == NULL)
#ifdef RELEASE
	// give information if the version is updated
	if (*version_old < version)
	  ::ui->program_updated(*version_old);
#endif

	delete version_old;
      } // if (version_old != NULL)

    } // read the version of the last usage of FreeDoko and write the new version

    // init the ui
    ::ui->init(argc = 1, argv);

    if (   !::bug_report_replay
#ifdef USE_NETWORK
	&& server->empty()
#endif
	) {
      // load the party (either the one given on the command line
      // or the default one
      if (!party_filename.empty()) {
	if (party_filename != "-") {
	  try {
	    ::party.load(party_filename);
	    ::game_status = GAMESTATUS::PARTY_INITIAL_LOADED;
	  } catch (ReadException const& exception) {
	    ::ui->information(::translation("Party load error: '%sfilename%'",
					    party_filename)
			      + "\n"
			      + exception.message(),
			      INFORMATION::PROBLEM);
	  }
	}
      } else { // if !(party_filename.empty())
	party_filename = (::setting(Setting::PRIVATE_DATA_DIRECTORY)
			  + "/"
			  + ::setting(Setting::PARTIES_DIRECTORY)
			  + "/"
			  + "current");
	if (DK::Utils::File::isfile(party_filename))
	  if (::party.load(party_filename))
	    ::game_status = GAMESTATUS::PARTY_INITIAL_LOADED;
      } // if !(party_filename.empty())
    } // if (!::bug_report_replay)

    // load the default rules from the private data directory
    if (::game_status != GAMESTATUS::PARTY_INITIAL_LOADED)
      ::party.rule().load(::setting(Setting::PRIVATE_DATA_DIRECTORY)
			  + "/"
			  + ::setting(Setting::RULES_DIRECTORY)
			  + "/last");

    // If no game has been saved in the party, the ui gets a problem with
    // a random startplayer.
    // So we check here, whether a game was already played.
    // Here, because so the rules and players are taken from the saved party
    // and not from the other files.
    if (::party.gameno() == 0) {
      if (::game_status != GAMESTATUS::PARTY_INITIAL_LOADED) {
	::party.set_seed(SEED_START);
	::party.set_startplayer(STARTPLAYER);
      }
      ::game_status = GAMESTATUS::PARTY_NEW;
    }

    // now the preparations have finished

    if (::game_status == GAMESTATUS::PROGRAMSTART)
      ::game_status = GAMESTATUS::PARTY_NEW;

    while (::game_status != GAMESTATUS::QUIT) {
      try {
	try {
	  ::party.open();
	  ::ui->party_open(::party);

#ifdef USE_NETWORK
	  if (!connection.empty()) {
	    ::server->create_connection(connection,
					Network::Server::FREEDOKO_STANDARD_PORT);
	    connection.clear();
	    ::game_status = GAMESTATUS::PARTY_NEW;
	  }
#endif

	  if ( (::game_status == GAMESTATUS::PARTY_INITIAL_LOADED)
	      || (::game_status == GAMESTATUS::PARTY_LOADED) ) {
	    ::ui->party_loaded();
	  } else {
	    ::party.remove_auto_save();
	    ::ui->party_get_settings();
	  }

#ifdef WORKAROUND
	  // in a network game, the gamestatus is set to PARTY_PLAY
	  if (::game_status == GAMESTATUS::PARTY_PLAY)
	    ::game_status = GAMESTATUS::PARTY_NEW;
#endif
	  if (::game_status == GAMESTATUS::PARTY_NEW)
	    ::party.init();

	  // start playing
	  ::game_status = GAMESTATUS::PARTY_PLAY;
	  ::party.play();
	} catch (GameStatus const& gs) {
	  if (gs == GAMESTATUS::PARTY_FINISHED)
	    ::game_status = GAMESTATUS::PARTY_FINISHED;
	  else
	    throw;
	} // try
	::ui->party_finish();

	::game_status = GAMESTATUS::PARTY_NEW;
      } catch (GameStatus const& gs) {
	DEBUG_ASSERTION(((gs == GAMESTATUS::PARTY_NEW)
			 || (gs == GAMESTATUS::PARTY_LOADED)
			 || (gs == GAMESTATUS::QUIT)),
			"main():\n"
			"  caught gamestatus '" << gs << "'");
	::game_status = gs;
      } catch (...) {
	::ui->party_close();
	::party.close();
	throw;
      }
      ::ui->party_close();
      if (::game_status != GAMESTATUS::PARTY_LOADED)
	::party.close();
    } // while (game_status != QUIT)

    // last chance to save the changes
    ::setting.save();

    // clean up
    delete ::ui;
  } catch (std::exception const& e) {
    cerr << "main()\n"
      << "  uncaught standard exception: " << e.what() << endl;
    throw;
  } catch (...) {
    cerr << "main()\n"
      << "  uncaught unknown exception" << endl;
    throw;
  } // try

  return EXIT_SUCCESS;
} // int main(argc, argv)
