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

#ifdef USE_UI_GTKMM

#include "ui.h"
#include "translations.h"
#include "preferences.h"
#include "table.h"
#include "party_settings.h"
#include "players.h"
#include "gameinfo_dialog.h"
#include "game_finished.h"
#include "game_review.h"
#include "main_window.h"
#include "chatter.h"
#include "network_log.h"
#include "bug_report.h"
#include "error.h"
#include "splash_screen.h"
#include "first_run.h"
#include "program_updated.h"

#include "../help.h"
#include "../../basistypes.h"
#include "../../party/party.h"
#include "../../game/game.h"
#include "../../card/trick.h"
#include "../../player/human/human.h"
#include "../../misc/setting.h"
#ifndef RELEASE
#include "../../player/ai/cards_information.h"
#include "../../player/ai/team_information.h"
#endif

#include "widgets/stock_button.h"

#include <gtkmm/main.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/stock.h>
#include <gtkmm/label.h>
#include <gtkmm/button.h>
#include <gtkmm/textview.h>
#include <gdk/gdkkeysyms.h>

namespace UI_GTKMM_NS {

  /**
   ** -> result
   **
   ** @param     text   text to convert
   **
   ** @return    utf8 format of 'text'
   **
   ** @author    Diether Knof
   **
   ** @version   0.7.3
   **/
  Glib::ustring
    UI_GTKMM::to_utf8(string const& text)
    {
      Glib::ustring utext(text);
      try {
	Glib::locale_from_utf8(utext);
      } catch (Glib::ConvertError const error) {
	try {
	  utext = Glib::convert(text, "utf8", "latin1");
	} catch (Glib::ConvertError const error) {
	} // try
      } // try

      return utext;
    } // static Glib::ustring UI_GTKMM::to_utf8(string text)


  /**
   ** add a part to the list
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  void
    UI_GTKMM::add_part(Base& base)
    {
      this->parts.push_back(&base);
      return ;
    } // void UI_GTKMM::add_part(Base& base)

  /**
   ** add a window to the list
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  void
    UI_GTKMM::add_window(Gtk::Window& window)
    {
      this->windows.push_back(&window);
      return ;
    } // void UI_GTKMM::add_window(Gtk::Window& window)

  /**
   ** updates the UI
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.5.4
   **/
  void
    UI_GTKMM::update()
    {
      while(this->kit->events_pending())
	this->kit->iteration();

      this->thrower.throw_it();

      return ;
    } // void UI_GTKMM::update()

  /**
   ** sleeps the given time
   ** (in miliseconds, UINT_MAX for infinity)
   **
   ** @param	slee_usec	time to sleep
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.1
   **
   ** @todo	remove variable 'sleeping'
   **/
  void
    UI_GTKMM::sleep(unsigned const sleep_usec)
    {
      sigc::connection timeout_connection
	= Glib::signal_timeout().connect(&UI_GTKMM::stop_sleeping,
					 //sigc::mem_fun(*(this->kit),
					 //&Gtk::Main::quit),
					 sleep_usec);

      UI_GTKMM::sleeping = true;

      while (!this->thrower
	     && UI_GTKMM::sleeping)
	Gtk::Main::iteration();

      timeout_connection.disconnect();

      this->thrower.throw_it();

      return ;
    } // void UI_GTKMM::sleep(unsigned const sleep_usec)

  /**
   ** quits the innermost main loop of gtk
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.5.4
   **/
  void
    UI_GTKMM::main_quit()
    {
      if (this->kit->level() > 0)
	this->kit->quit();

      return ;
    } // void UI_GTKMM::main_quit()

  /**
   ** quits the innermost main loop of gtk
   **
   ** @param	-
   **
   ** @return	true
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.1
   **/
  bool
    UI_GTKMM::stop_sleeping()
    {
      if (Gtk::Main::level() > 0)
	Gtk::Main::quit();

      UI_GTKMM::sleeping = false;

      return true;
    } // static bool UI_GTKMM::stop_sleeping()

  /**
   ** quits the program
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.5.4
   **/
  void
    UI_GTKMM::quit_program()
    {
      this->thrower(GAMESTATUS::QUIT, __FILE__, __LINE__);
      this->main_quit();

      return ;
    } // void UI_GTKMM::quit_program()

  /**
   ** a key has been pressed
   **
   ** @param	key	the key
   **
   ** @return	true (stop other handlers from being invoked for the event)
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.1
   **
   ** @todo	some shortcuts (search 'ToDo')
   **/
  bool
    UI_GTKMM::key_press(GdkEventKey const* const key)
    {
#if 0
      struct GdkEventKey {
	GdkEventType type;
	GdkWindow *window;
	gint8 send_event;
	guint32 time;
	guint state;
	guint keyval;
	gint length;
	gchar *string;
	guint16 hardware_keycode;
	guint8 group;
      };
#endif

      if (this->table
	  && this->table->in_game_review())
	if (!this->table->game_finished_->game_review->key_press(key))
	  return true;

      if (key->state == GDK_SHIFT_MASK) {
	switch (key->keyval) {
	case GDK_Escape: // quit the program
	  this->quit_program();
	  break;
	} // switch (key->keyval)
      } // if (key->state == GDK_SHIFT_MASK)

      if (::game_status == GAMESTATUS::GAME_PLAY) {
	// play a card?
	Player& player = const_cast<Player&>(this->game().player_current());
	if (player.type() == Player::HUMAN) {
	  SortedHand& hand = player.sorted_hand();
	  if (!(key->state & ~GDK_SHIFT_MASK)) {
	    { // request a card to play
	      unsigned requested_cardno = UINT_MAX;
	      switch (key->keyval) {
	      case GDK_1:
		requested_cardno = 0;
		break;
	      case GDK_2:
		requested_cardno = 1;
		break;
	      case GDK_3:
		requested_cardno = 2;
		break;
	      case GDK_4:
		requested_cardno = 3;
		break;
	      case GDK_5:
		requested_cardno = 4;
		break;
	      case GDK_6:
		requested_cardno = 5;
		break;
	      case GDK_7:
		requested_cardno = 6;
		break;
	      case GDK_8:
		requested_cardno = 7;
		break;
	      case GDK_9:
		requested_cardno = 8;
		break;
	      case GDK_0:
	      case GDK_a:
		requested_cardno = 9;
		break;
	      case GDK_b:
		requested_cardno = 10;
		break;
	      case GDK_c:
		requested_cardno = 11;
		break;
	      } // switch (key->keyval)
	      if (this->table->get_card()) {
		if (   (requested_cardno != UINT_MAX)
		    && !hand.played(requested_cardno)
		    && player.game().trick_current().isvalid(hand.card_all(requested_cardno)) ) {
		  hand.request_position_all(requested_cardno);
		  this->thrower(hand.requested_card(), __FILE__, __LINE__);
		}
	      } // if (this->table->card_get())
	    } // request a card to play
	  } // if ((key->state & ~GDK_SHIFT_MASK) == 0)

	} // if (player.type() == Player::HUMAN)
      } // if (::game_status == GAMESTATUS::GAME_PLAY)

      if (!(key->state & ~GDK_SHIFT_MASK)) {
	switch (key->keyval) {
	case GDK_Clear: // close windows
#ifdef DKNOF
	  COUT << "Space" << endl;
#endif
	  { // close all open gameinfo-dialogs
	    for (vector<GameinfoDialog*>::iterator
		 gid = this->table->gameinfo_.begin();
		 gid != this->table->gameinfo_.end();
		 ++gid)
	      (*gid)->close();
	  } // close all open gameinfo-dialogs
	  break;
#ifdef USE_NETWORK
	  // when network is activated, the menu takes care of the key
	case GDK_c: // show chat window
	  // -> menu.cpp
	  break;

	case GDK_L: // show network log
	  this->ui->network_log->present();
	  break;
#endif

	case GDK_l: // show last trick
	  // -> menu.cpp
	  break;
	case GDK_n: // next announcement
	  if ((::game_status >= GAMESTATUS::GAME_PLAY)
	      && (::game_status < GAMESTATUS::GAME_FINISHED))
	    if (this->game().humanno() == 1) {
	      this->game().announcement_make(this->game().human_player()->next_announcement(),
					     *this->game().human_player()
					    );
	    } // if (only one human
	  break;
	case GDK_m: // announcement reply
	  if ((::game_status >= GAMESTATUS::GAME_PLAY)
	      && (::game_status < GAMESTATUS::GAME_FINISHED))
	    if (this->game().humanno() == 1)
	      this->game().announcement_make(ANNOUNCEMENT::REPLY,
					     *(this->game().human_player())
					    );
	  break;
	case GDK_h: // card suggestion
	  // -> menu.cpp
	  break;
	case GDK_r: // show the rules
	  // -> menu.cpp
	  break;
	case GDK_p: // show the party points
	  // -> menu.cpp
	  break;
	case GDK_P: // show the players
#ifdef WORKAROUND
	  if (this->party_settings
	      && this->party_settings->players)
	    this->party_settings->players->present();
#else
	  // -> menu.cpp
#endif
	  break;
#ifndef RELEASE
	case GDK_e: // print human cards information
	  if ((::game_status & GAMESTATUS::GAME)
	      && this->game().human_player()) {
	    cout << '\n'
	      << this->game().human_player()->name() << ": "
	      << this->game().human_player()->cards_information() << '\n';
	  }
	  break;
	case GDK_t: // print human team information
	  if ((::game_status & GAMESTATUS::GAME)
	      && this->game().human_player()) {
	    cout << '\n'
	      << this->game().human_player()->name() << ": "
	      << this->game().human_player()->team_information() << '\n';
	  }
	  break;
	case GDK_T: // print all team information
	  if (::game_status & GAMESTATUS::GAME) {
	    for (vector<Player*>::const_iterator
		 p = this->game().players_begin();
		 p != this->game().players_end();
		 ++p) {
	      if (!dynamic_cast<Ai*>(*p)) {
		cout << '\n'
		  << (*p)->name() << ": no ai\n";
		continue;
	      }
	      cout << '\n'
		<< (*p)->name() << ": "
		<< dynamic_cast<Ai*>(*p)->team_information() << '\n';
	    } // for (p)
	  }
	  break;
#endif // #ifndef RELEASE

	case GDK_F1: // show manual
	  // -> menu.cpp
	  break;
	case GDK_F2: // show preferences
	  // -> menu.cpp
	  break;
	case GDK_Pause: // create a bug report
	case GDK_Break: // create a bug report
	  this->bug_report->create_report();
	  break;
	case GDK_period: // toggle show valid cards
	  // -> menu.cpp
	  break;
	case GDK_Escape:
	  break;
	} // switch (key->keyval)
      } // if (key->state == 0)

      if ((key->state & ~GDK_SHIFT_MASK) == GDK_CONTROL_MASK) {
	switch (key->keyval) {
	case GDK_c: // exit the program
	  exit(EXIT_SUCCESS);
	  break;
	case GDK_f: // toggle fullscreen
	  if (this->main_window->get_window()->get_state()
	      & Gdk::WINDOW_STATE_FULLSCREEN)
	    this->main_window->unfullscreen();
	  else
	    this->main_window->fullscreen();
	  break;
	case GDK_n: // start new party
	  // -> menu.cpp
	  break;
	case GDK_l: // load party
	  // -> menu.cpp
	  break;
	case GDK_s: // save party
	  // -> menu.cpp
	  break;
	case GDK_q: // quit the program
	  // -> menu.cpp
	  break;
	case GDK_r: // redraw all
	  this->redraw_all();
	  break;
	case GDK_0: // default card size
	  ::setting.set(Setting::ORIGINAL_CARDS_SIZE, true);
	  break;
	case GDK_plus: // increase card size
	  ::setting.set(Setting::CARDS_HEIGHT,
			::setting(Setting::CARDS_HEIGHT) + 1);
	  ::setting.set(Setting::ORIGINAL_CARDS_SIZE, false);
	  break;
	case GDK_minus: // decrease card size
	  ::setting.set(Setting::CARDS_HEIGHT,
			::setting(Setting::CARDS_HEIGHT) - 1);
	  ::setting.set(Setting::ORIGINAL_CARDS_SIZE, false);
	  break;
	} // switch (key->keyval)
      } // if (key->state == GDK_CONTROL_MASK)

      return true;
    } // bool UI_GTKMM::key_press(GdkEventKey const* const key)

  /**
   **
   ** @param	page	the page to be loaded
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.5.2
   **
   ** @todo	all
   **/
  void
    UI_GTKMM::help_load(string const& page)
    {
      return ;
    } // void UI_GTKMM::help_load(string const& page)

  /**
   **
   ** @param	player	name of the player who has chatted
   ** @param	message	message
   ** @param	self	whether this player has chattet
   **			(else the chatter comes from the network)
   **			default: true
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.8
   **/
  void
    UI_GTKMM::chat(string const& player, string const& message,
		   bool const self)
    {
      this->chatter->chat(player, message);

      this->thrower.throw_it();

      return ;
    } // void UI_GTKMM::chat(string player, string message, bool self = true)

  /**
   ** this is the first run of the program
   **
   ** @param	message		message for the user
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.0
   **/
  void
    UI_GTKMM::first_run(Translator::Translation const& message)
    {
      DEBUG_ASSERTION((this->first_run_window == NULL),
		      "UI_GTKMM::first_run(message)\n"
		      "  the window is already created.");

      this->first_run_window = new FirstRun(this, message);

      this->first_run_window->show();

      return ;
    } // void UI_GTKMM::first_run(Translator::Translation message)

  /**
   ** the program is updated
   **
   ** @param	old_version	the old version
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.0
   **/
  void
    UI_GTKMM::program_updated(DK::Utils::Version const& old_version)
    {
      DEBUG_ASSERTION((this->program_updated_window == NULL),
		      "UI_GTKMM::program_updated(old_version)\n"
		      "  the window is already created.");

      this->program_updated_window = new ProgramUpdated(this, old_version);

      this->program_updated_window->show();

      return ;
    } // void UI_GTKMM::program_updated(DK::Utils::Version old_version)

  /**
   **
   ** @param	message		message to display
   ** @param	type		the type of the message
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.2
   **
   ** @todo	remove memory leak
   ** @todo	add a parameter for a title
   ** @todo	put the windows on top of all windows
   **
   ** @bug	memory leak (the dialog is never deleted)
   **/
  void
    UI_GTKMM::information(Translator::Translation const& message,
			  INFORMATION::Type const type)
    {
      Gtk::MessageType message_type = Gtk::MESSAGE_INFO;
      switch (type) {
      case INFORMATION::NORMAL:
	message_type = Gtk::MESSAGE_INFO;
	break;
      case INFORMATION::WARNING:
	message_type = Gtk::MESSAGE_WARNING;
	break;
      case INFORMATION::PROBLEM:
	message_type = Gtk::MESSAGE_ERROR;
	break;
      } // switch(type)

      Gtk::MessageDialog* information
	= new Gtk::MessageDialog(message.translation(),
				 false,
				 message_type,
				 Gtk::BUTTONS_NONE,
				 false);
      {
#include "../icon.xpm"
	information->set_icon(Gdk::Pixbuf::create_from_xpm_data(icon_xpm));
      }

      // memory leak: no call of 'ui->remove_from_translation(*close_button)'
      Gtk::Button* close_button
	= Gtk::manage(new Gtk::StockButton(Gtk::Stock::CLOSE, "close"));
      this->translations->add(*close_button, ::translation("close"));
      information->add_action_widget(*close_button, Gtk::RESPONSE_CLOSE);
      close_button->show_all();

      close_button->signal_clicked().connect(sigc::mem_fun(*information,
							   &Gtk::Widget::hide)
					    );

#ifdef OUTDATED
      if (this->main_window)
	information->set_transient_for(*this->main_window);
#endif

      if (type == INFORMATION::PROBLEM) {
	information->set_modal();
	Gtk::Main::run(*information);
	delete information;
      } else {
	information->present();
      }
      return ;
    } // void UI_GTKMM::information(Translator::Translation message, INFORMATION::Type type)

  /**
   ** shown an error window
   ** and quit the program
   **
   ** @param	message		message to display
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.3
   **/
  void
    UI_GTKMM::error(string const& message)
    {
      static bool already_running = false;

      if (already_running) {
	cerr << '\n'
	  << ::translation("Press <Return> to exit.").translation() << endl;
	cin.get();
      } else { // if !(already_running)

	already_running = true;

	if (this->splash_screen)
	  this->splash_screen->hide();

	Error error(message, this);
#ifdef RELEASE
	error.run();
#else
	error.present();
	while (error.is_visible())
	  this->sleep(10);
#endif
      } // if !(already_running)

#ifdef ASSERTION_GENERATES_SEGFAULT
      cerr << ::translation("Creating segmentation fault.").translation()
	<< endl;
      SEGFAULT;
#endif // #ifdef ASSERTION_GENERATES_SEGFAULT

      ::exit(EXIT_FAILURE);

      return ;
    } // void UI_GTKMM::error(string const& message)

  /**
   ** generate an error (for testings)
   **
   ** @param	error_message	error message
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.6.3
   **/
  void
    UI_GTKMM::generate_error(string const error_message)
    {
      ::generate_error(error_message);

      this->thrower.throw_it();

      return ;
    } // void UI_GTKMM::generate_error(string const error_message)

  /**
   ** iconifies all windows
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  void 
    UI_GTKMM::iconify_all_windows()
    {
      for (std::list<Gtk::Window*>::iterator w = this->windows.begin();
	   w != this->windows.end();
	   ++w)
	(*w)->iconify();

      return ;
    } // void UI_GTKMM::conify_all_windows()

  /**
   ** deiconifies all windows
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  void
    UI_GTKMM::deiconify_all_windows()
    {
      for (std::list<Gtk::Window*>::iterator w = this->windows.begin();
	   w != this->windows.end();
	   ++w)
	(*w)->deiconify();

      return ;
    } // void UI_GTKMM::deiconify_all_windows()

} // namespace UI_GTKMM_NS

#endif // #ifdef USE_UI_GTKMM
