/* This file is part of cardwords
   (c) 1999 Tobias Peters
   see file COPYING for the copyright terms.
   
   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 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
*/

// cardwords_gtkclient.cc

#include "cardwords_gtkclient.hh"
#include "cardwords_clientpile.hh"
#include "cardwords_bonustable.hh"
#include "cardwords_hand.hh"
#include "cardwords_direction.hh"
#include "cardwords_server.hh"
#include <strstream.h>
#include <unistd.h> // for getopt(), getuid(), geteuid()
#include "cardwords_options.hh"
#include <sys/stat.h>
#include <sys/types.h>
#include "cardwords_stringconverters.hh"
#include "cardwords_gtk_popup.hh"
#include <gtk--/button.h>
#include <gtk--/main.h>
#include <gtk--/fileselection.h>

static
void
exit_callback(void)
{
  exit(0);
}

CardWords_GtkClient::CardWords_GtkClient(CardWords_Options * opt)
  : use_digits_for_the_columns    (false),
    column_indices_grow_rightwards(true),
    row_indices_grow_upwards      (false),
    allow_action_selection        (false),

    server               (static_cast<CardWords_ChildProcess *>(0)),
    options_dialog       (static_cast<CardWords_Gtk_OptionsDialog *>(0)),
    cell_height          (34U),
    cell_width           (34U),
    all_cards_list       (static_cast<CardWords_Gtk_CardList *>(0)),
    remaining_cards_list (static_cast<CardWords_Gtk_CardList *>(0)),
    options              (opt)
{
  gdk_socket_read_tag = gdk_socket_write_tag = -1;

  menu_bar = 0;
  info_box = 0;
  table_and_info_box = 0;
  gtk_table = 0;
  gtk_tableinfo = 0;
  gtk_playerlist = 0;
  gtk_pileinfo = 0;
  table_box = 0;
  actionsel = 0;
  hand_display = 0;
  gtk_addword = 0;

  options_dialog = new CardWords_Gtk_OptionsDialog(options);
  pack_start(*options_dialog);
  options_dialog->show();

  start_local_button = new Gtk::Button(gettext("Start a local server"));

  connect_remote_button = new Gtk::Button(gettext("Connect to the "\
                                                 "remote server"));

  save_options_button = new Gtk::Button(gettext("Save options"));

  exit_button = new Gtk::Button(gettext("Exit"));
  
  start_local_button->clicked.
    connect(SigC::slot(this,
                 &CardWords_GtkClient::start_local_button_callback));

  connect_remote_button->clicked.
    connect(SigC::slot(this,
                 &CardWords_GtkClient::connect_remote_button_callback));

  save_options_button->clicked.
    connect(SigC::slot(this,
                 &CardWords_GtkClient::save_options_button_callback));

  exit_button->clicked.connect(SigC::slot(exit_callback));

  button_box = new Gtk::HBox;
  button_box->pack_start(*save_options_button);
  button_box->pack_start(*start_local_button);
  button_box->pack_start(*connect_remote_button);
  button_box->pack_start(*exit_button);

  save_options_button->show();
  start_local_button->show();
  connect_remote_button->show();
  exit_button->show();

  pack_end(*button_box);
  button_box->show();
  
  help_window = new CardWords_Gtk_HelpWindow;
  popup       = new CardWords_Gtk_Popup;

  options_dialog->help_signal.
    connect(SigC::slot(help_window, &CardWords_Gtk_HelpWindow::show_help_text));
}

void
CardWords_GtkClient::save_options_button_callback(void)
{
  assert(options_dialog != 0);
  options_dialog->store_options();

  options->save(options->get_filename());
}


CardWords_GtkClient::~CardWords_GtkClient()
{
  if (gtk_playerlist != 0) {
    gtk_playerlist->hide();
    info_box->remove(*gtk_playerlist);
    delete gtk_playerlist;
  }
  if (gtk_tableinfo!= 0) {
    gtk_tableinfo->hide();
    info_box->remove(*gtk_tableinfo);
    delete gtk_tableinfo;
  }
  if (gtk_pileinfo != 0) {
    gtk_pileinfo->hide();
    info_box->remove(*gtk_pileinfo);
    delete gtk_pileinfo;
  }

  gtk_addword->hide();
  info_box->remove(*gtk_addword);
  delete gtk_addword;
  
  info_box->hide();
  table_and_info_box->remove(*info_box);
  delete info_box;
  
  if (gtk_table != 0) {
    assert(table_box != 0);
    gtk_table->hide();
    table_box->remove(*gtk_table);
    table_box->hide();
    table_and_info_box->remove(*table_box);
    delete gtk_table;
    delete table_box;
  }

  table_and_info_box->hide();
  remove(*table_and_info_box);
  delete table_and_info_box;

  if (hand_display != 0) {
    hand_display->hide();
    remove(*hand_display);
    delete hand_display;
    hand_display = 0;
  }

  delete help_window;
  delete popup;
}

// Functions for registering this object's fd in the gtk main select(2)
// call:
void
CardWords_GtkClient::register_reading(void)
{
  assert (is_registered_for_reading() == false);
  
  CardWords_CommClient::register_reading();
  
  gdk_socket_read_tag = gdk_input_add(get_fd(),
                                      GDK_INPUT_READ,
                                      & socket_read_possible,
                                      this);
}

void
CardWords_GtkClient::register_writing(void)
{
  assert (is_registered_for_writing() == false);
  
  CardWords_BaseClient::register_writing();
  
  gdk_socket_write_tag = gdk_input_add(get_fd(),
                                       GDK_INPUT_WRITE,
                                       & socket_write_possible,
                                       this);
}

void
CardWords_GtkClient::deregister_reading(void)
{
  assert (gdk_socket_read_tag != -1);
  assert (is_registered_for_reading() == true);
  
  CardWords_BaseClient::deregister_reading();
  gdk_input_remove( gdk_socket_write_tag );

  gdk_socket_write_tag = -1;
}

// If you want to discard all pending messages, use discard_messages() prior
// to deregister_writing()
void
CardWords_GtkClient::deregister_writing(void)
{
  assert (gdk_socket_write_tag != -1);
  assert (is_registered_for_writing() == true);

  CardWords_BaseClient::deregister_writing();
  gdk_input_remove( gdk_socket_write_tag );

  gdk_socket_write_tag = -1;
}

// These functions are being called from the gtk main selection mechanism,
// and call read_now(), write_now() method of the client_object
void
CardWords_GtkClient::socket_read_possible (gpointer client_object,
                                           gint fd,
                                           GdkInputCondition condition)
  // client_object is the registered instance.
  // condition is GDK_INPUT_READ or GDK_INPUT_WRITE.
{
  int int_condition = condition;
  CardWords_GtkClient * instance =
    static_cast<CardWords_GtkClient *>(client_object);

  if ((int_condition & int(GDK_INPUT_READ)) != 0
      && instance->is_registered_for_reading() == true) {
    // We can not rely that this function is only called under these
    // conditions because of a bug in gdk. Define DEBUG_GTK_SELECT to see
    // a printed warning in these cases if you don'd believe it.

    assert(fd == instance->get_fd());
    instance->read_now();
  }
#ifdef DEBUG_GTK_SELECT
  else {
    if ((int_condition & int(GDK_INPUT_READ)) == 0) {
      cerr << gettext("*** WARNING: ***  CardWords_GtkClient::"\
                      "socket_read_possible() "\
                      "called with GdkInputCondition that does not contain "\
                      "GDK_INPUT_READ.")
           << endl;
    }
    if (instance->is_registered_for_reading() == false) {
      cerr << gettext("*** WARNING: ***  CardWords_GtkClient::"\
                      "socket_read_possible() "\
                      "called while not registered for reading.")
           << endl;
    }
  }
#endif
}
void
CardWords_GtkClient::socket_write_possible (gpointer client_object,
                                            gint fd,
                                            GdkInputCondition condition)
  // client_object is the registered instance.
  // condition is GDK_INPUT_READ or GDK_INPUT_WRITE.
{
  int int_condition = condition;
  CardWords_GtkClient * instance =
    static_cast<CardWords_GtkClient *>(client_object);

  if ((int_condition & int(GDK_INPUT_WRITE)) != 0
      && instance->is_registered_for_writing() == true) {
    // We can not rely that this function is only called under these
    // conditions because of a bug in gdk. Define DEBUG_GTK_SELECT to see
    // a printed warning in these cases if you don'd believe it.

    assert(fd == instance->get_fd());
    instance->send_now();
  }
#ifdef DEBUG_GTK_SELECT
  else {
    if ((int_condition & int(GDK_INPUT_WRITE)) == 0) {
      cerr << gettext("*** WARNING: ***  "\
                      "CardWords_GtkClient::socket_write_possible() "\
                      "called with GdkInputCondition that does not contain "\
                      "GDK_INPUT_WRITE.")
           << endl;
    }
    if (instance->is_registered_for_writing() == false) {
      cerr << gettext("*** WARNING: ***  "\
                      "CardWords_GtkClient::socket_write_possible() "\
                      "called while not registered for writing.")
           << endl;
    }
  }
#endif
}

// When the state of the connection, the game or other things are changed,
// this function is called. It should display the changes for the user or
// do other neat things.
void
CardWords_GtkClient::state_changed(State_Change change)
{
  switch (change) {
    // The Connection is established and the server is waiting for our login:
  case SC_SERVER_RESPONDED_1ST_TIME:
    {
      menu_bar = new CardWords_Gtk_MenuBar;
      pack_start(*menu_bar, false, false);

      info_box = new Gtk::VBox;
      table_and_info_box = new Gtk::HBox;

      table_and_info_box->pack_end(*info_box);
      info_box->show();

      pack_start(*table_and_info_box);
      table_and_info_box->show();

      gtk_addword = new CardWords_Gtk_AddWord(this);
      info_box->pack_end(*gtk_addword);
      // don't show it yet.

      gtk_tableinfo =
        new CardWords_Gtk_CardTableInfo(get_table(),
                                        use_digits_for_the_columns,
                                        column_indices_grow_rightwards,
                                        row_indices_grow_upwards);
      info_box->pack_start(*gtk_tableinfo,false, false);
      gtk_tableinfo->show();

      gtk_table = new CardWords_Gtk_CardTable(get_table(),
                                              cell_width, cell_height,
                                              use_digits_for_the_columns,
                                              column_indices_grow_rightwards,
                                              row_indices_grow_upwards,
                                              gtk_tableinfo);
      table_box = new Gtk::VBox;
      table_box->set_homogeneous(false);
      table_box->pack_start(*gtk_table, false, false);
      table_and_info_box->pack_start(*table_box, false, false);
      gtk_table->show();
      table_box->show();

      gtk_table->cell_clicked.
        connect(SigC::slot(this,
                     &CardWords_GtkClient::
                     new_start_location_chosen_through_mouse_click));

      actionsel =
        new CardWords_Gtk_ActionSelection(get_hand(),
                                          gtk_table);
      pack_start(*actionsel,false,false);
      actionsel->show();
      get_toplevel()->key_press_event.
        connect(SigC::slot(this, &CardWords_GtkClient::key_pressed));

      actionsel->card_clicked.
        connect(SigC::slot(this, &CardWords_GtkClient::remove_card_from_actionsel));

      actionsel->action_selection_activated.
        connect(SigC::slot(this, &CardWords_GtkClient::action_is_chosen));



      hand_display = new CardWords_Gtk_HandDisplay(gtk_table->get_styles(),
                                                   get_hand());
      pack_start(*hand_display, false, false);
      hand_display->show();

      hand_display->card_clicked.
        connect(SigC::slot(this,
                     &CardWords_GtkClient::move_card_from_hand_to_actionsel));

      bool remains_unchanged;
      all_cards_list =
        new CardWords_Gtk_CardList(get_card_spec_map(),
                                   (remains_unchanged = true));
      remaining_cards_list =
        new CardWords_Gtk_CardList(get_possible_remaining_cards_map(),
                                   (remains_unchanged = false));

      // all_cards_list->show();
      // remaining_cards_list->show();
      list<string> card_list_menu_items;
      card_list_menu_items.push_back(gettext("Show all cards in the game"));
      card_list_menu_items.push_back(gettext("Show cards that could still "\
                                             "be in the pile"));
      const string card_list_menu_name = gettext("Show cards");
      
      menu_bar->add_menu(card_list_menu_name,
                         card_list_menu_items);
      menu_bar->show();
      
      list<string>::iterator iter = card_list_menu_items.begin();
      menu_bar->get_menu_item(card_list_menu_name, *iter)->activate.
        connect(all_cards_list->show.slot());
      ++iter;
      menu_bar->get_menu_item(card_list_menu_name, *iter)->activate.
        connect(remaining_cards_list->show.slot());


      all_cards_list->delete_event.
        connect(SigC::slot(this,
                     &CardWords_GtkClient::
                     all_cards_list_delete_event_callback));

      remaining_cards_list->delete_event.
        connect(SigC::slot(this,
                     &CardWords_GtkClient::
                     remaining_cards_list_delete_event_callback));
    }
    break;
    // Some info about the logged players may have changed:
  case SC_PLAYER_INFO:
    if (gtk_playerlist == 0) {
      gtk_playerlist = new CardWords_Gtk_PlayerList(get_playerlist());
      info_box->pack_start(*gtk_playerlist,false,false);
      gtk_playerlist->show();
    }
    else {
      gtk_playerlist->update(get_playerlist());
    }
    break;
  case SC_NEW_CARD:
    hand_display->update();
    break;
  case SC_PLAYER_TRIES_MOVE:
    gtk_table->pre(get_current_move());
    break;
  case SC_PLAYER_GETS_POINTS:
    gtk_playerlist->update(get_playerlist());
    
    if (get_playerlist()->get_active_player_name() == get_used_name()) {
      // This Player's move: if it is a move, reset the ActionSelection.
      // Thus, all cards go back into the hand and can be drawn from there
      // to the table by CardWords_CommClient:
      if (actionsel->get_move() != 0) {
        actionsel->reset();
        actionsel->update();
      }
    }
    break;
  case SC_REMAINING_CARDS:
    if (gtk_pileinfo == 0) {
      gtk_pileinfo =
        new CardWords_Gtk_PileInfo(get_pile()->get_remaining_cards());
      
      info_box->pack_start(*gtk_pileinfo, false, false);
      gtk_pileinfo->show();
    }
    else {
      gtk_pileinfo->update_no_of_cards(get_pile()->get_remaining_cards());
    }
    break;
  case SC_MOVE_DONE:
    gtk_table->show_move(get_current_move());
    hand_display->update(); // The cards have been taken from the hand.
    break;
  case SC_ACTIVE_PLAYER:
    gtk_playerlist->update(get_playerlist());
    break;
  case SC_MY_GO:
    allow_action_selection = true;
    break;
  case SC_MY_GO_FINISHED:
    allow_action_selection = false;
    gtk_table->unrecent();
    gtk_table->unpre();
    break;
  case SC_TRADE_ACCEPTED:
    hand_display->update();
    break;
  case SC_MOVE_WOULD_CREATE_UNKNOWN_WORD_AT:
  case SC_MOVE_WOULD_CREATE_UNKNOWN_WORD:
    if (get_log_mode() == lm_owner_player
        || get_log_mode() == lm_owner_watcher) {
      gtk_addword->add_word(get_false_word());
    }
    hand_display->update();
    gtk_table->unpre();
    break;
  case SC_MOVE_NOT_ALLOWED:
    hand_display->update();
    gtk_table->unpre();
    break;
  case SC_NEW_WORD:
    {
      ostrstream new_word_message;
      new_word_message << gettext("The word `")
                       << get_last_new_word()
                       << gettext("' has been added to the dictionary.")
                       << ends;
      new_word_message.freeze(true);
      popup->show_message(new_word_message.str());
      new_word_message.freeze(false);
    }
    break;
  case SC_GAME_OVER:
    popup->show_message(gettext("Game over."));
    break;
  default:
    cout << gettext("State change not handled by gtk_client:");
    
  }
  CardWords_CommClient::state_changed(change);
}

// Test if the client is compatible with these charset, cardset, table
// and bonus definitions:
bool
CardWords_GtkClient::test_client_compatible_with_server_settings()
{
  size_t x, y;
  // Test the table:
  if (get_table()->get_width() > max_table_dimensions[horizontal]
      ||
      get_table()->get_height() > max_table_dimensions[vertikal]) {
    return false;
  }
  for (x = 0; x < get_table()->get_width(); ++x) {
    for (y = 0; y < get_table()->get_height(); ++y) {
      if (get_table()->is_cell_blocked(x,y) == true) {
        // There cannot be anything wrong with the values:
        continue;
      }
      if (get_table()->get_letter_factor(x,y) < -9
	  || get_table()->get_letter_factor(x,y) > 99
	  || get_table()->get_word_factor(x,y) < -9
	  || get_table()->get_word_factor(x,y) > 99
          || get_table()->get_cell_points(x,y) < -9
          || get_table()->get_cell_points(x,y) > 99) {
	// The table only supports two digits for a cell value:
	return false;
      }

      size_t non_default_count = 0;
      if (get_table()->get_letter_factor(x,y) != DEFAULT_LETTER_FACTOR) {
        ++non_default_count;
      }
      if (get_table()->get_word_factor(x,y) != DEFAULT_WORD_FACTOR) {
        ++non_default_count;
      }
      if (get_table()->get_cell_points(x,y) != DEFAULT_CELL_POINTS) {
        ++non_default_count;
      }
      if (non_default_count > 2U) {
        // This client can not display more than 2 non-default cell
        // attributes.
        return false;
      }

    }
  }

  // Test the cardset:
  ostrstream cardspec;
  get_pile()->print_cardSpecs(cardspec);
  cardspec << ends;

  cardspec.freeze(true);
  istrstream * cardstream = new istrstream(cardspec.str());

  CardWords_CardSelection selection(*cardstream);

  delete cardstream;
  cardspec.freeze(false);

  for (x = 0; x < selection.size(); ++x) {
    if (selection[x].get_points() < -9
	|| selection[x].get_points() > 99) {
      // the table only supports two digits for card values:
      return false;
    }
  }

  
  return true;
}

// The virtual method error() will be called with an Error_Number
// parameter and should inform the user about that error.
// The error() method should call disconnect() or
// CardWords_CommClient::error() before returning to disconnect
// from the server.
void
CardWords_GtkClient::error (CardWords_CommClient::Error_Number)
{
  // ************
}

void
CardWords_GtkClient::remove_card_from_actionsel(CardWords_Card * card)
{
  assert (actionsel != 0);
  assert (hand_display != 0);
  if (allow_action_selection) {
    get_hand()->add_card(card);
    actionsel->update();
    hand_display->update();
  }
}
void
CardWords_GtkClient::move_card_from_hand_to_actionsel(CardWords_Card * card)
{
  assert (actionsel != 0);
  assert (hand_display != 0);
  if (allow_action_selection) {
    actionsel->add_card(card);
    actionsel->update();
    hand_display->update();
  }
}
int
CardWords_GtkClient::key_pressed (GdkEventKey * keyevent)
{
  if (actionsel != 0 && allow_action_selection) {
    if (actionsel->key_pressed(keyevent) != -1) {
      // actionsel has already been updated, hand_display needs it:
      hand_display->update();
      return true;
    }
  }
  return false;
}
    
void
CardWords_GtkClient::action_is_chosen(void)
{
  // actionsel emitted the action_selection_activated signal.
  assert (actionsel != 0);

  if (actionsel->get_move() != 0) {
    // It's a move.
    CardWords_Move move = *actionsel->get_move();
    actionsel->reset();
    actionsel->update();
    hand_display->update();
    try_move(move);
  }
  else if (actionsel->get_trade() != 0) {
    // A trade.
    CardWords_CardSelection trade = *actionsel->get_trade();
    
    actionsel->reset();
    actionsel->update();
    hand_display->update();
    try_trade(trade);
  }
  else {
    // A pass.
    try_pass();
  }
}
void
CardWords_GtkClient::
new_start_location_chosen_through_mouse_click(CardWords_CardTableLocation loc)
{
  if (actionsel != 0 && allow_action_selection) {
    actionsel->change_start_location(loc);
    actionsel->update();
  }
}
static
int
static_remove_button_box_and_connect(gpointer instance)
{
  return (static_cast<CardWords_GtkClient *>(instance)->
          remove_button_box_and_connect());
}
void
CardWords_GtkClient::start_local_button_callback(void)
{
  assert (is_connected() == false);
  assert (server == 0);
  assert (options_dialog != 0);
  options_dialog->store_options();
  options_dialog->hide();
  remove(*options_dialog);
  delete options_dialog;
  
  button_box->hide();

  // The button_box can not yet be deleted, because one of its buttons will be
  // accessed somewhere in the backtrace. Remove it asynchronously:
  gtk_timeout_add(1500,// Call in 1.5 seconds
                  &static_remove_button_box_and_connect,
                  this);

  user_wants_connect = true;

  read_client_options();
  
  string string_value;

  options->get("server_program", &string_value);
  server = new CardWords_ChildProcess(string_value);

  assert (options->get_type("charset_file") == STRING_TYPE);
  // should have been set by the startserverdialog.
  options->get("charset_file", &string_value);
  server->add_argument("-c");
  server->add_argument(string_value);

  assert (options->get_type("cardset_file") == STRING_TYPE);
  options->get("cardset_file", &string_value);
  server->add_argument("-t");
  server->add_argument(string_value);

  assert (options->get_type("cardtable_file") == STRING_TYPE);
   options->get("cardtable_file", &string_value);
  server->add_argument("-b");
  server->add_argument(string_value);

  assert (options->get_type("bonus_file") == STRING_TYPE);
  options->get("bonus_file", &string_value);
  server->add_argument("-B");
  server->add_argument(string_value);

  assert (options->get_type("dictionary_file") == STRING_TYPE);
  options->get("dictionary_file", &string_value);
  if (string_value != "") {
    server->add_argument("-d");
    server->add_argument(string_value);
  }
  
  assert (options->get_type("personal_dictionary_file") == STRING_TYPE);
  options->get("personal_dictionary_file", &string_value);
  if (string_value != "") {
    server->add_argument("-D");
    server->add_argument(string_value);
  }

  unsigned unsigned_value;
  string unsigned_str;
  
  assert (options->get_type("number_of_players") == UNSIGNED_TYPE);
  options->get("number_of_players", &unsigned_value);
  unsigned_str = unsigned2string(unsigned_value);
  server->add_argument("-p");
  server->add_argument(unsigned_str);

  assert (options->get_type("number_of_robots") == UNSIGNED_TYPE);
  options->get("number_of_robots", &unsigned_value);
  unsigned_str = unsigned2string(unsigned_value);
  server->add_argument("-r");
  server->add_argument(unsigned_str);

  assert (options->get_type("number_of_watchers") == UNSIGNED_TYPE);
  options->get("number_of_watchers", &unsigned_value);
  unsigned_str = unsigned2string(unsigned_value);
  server->add_argument("-o");
  server->add_argument(unsigned_str);

  assert (options->get_type("local_port_number") == UNSIGNED_TYPE);
  options->get("local_port_number", &unsigned_value);
  unsigned_str = unsigned2string(unsigned_value);
  server->add_argument("-P");
  server->add_argument(unsigned_str);
  set_remote_port_number(unsigned_value);

  // Add the homedirectory that we found:
  server->add_argument("-H");
  server->add_argument(get_home_directory());
  
  set_remote_hostname("localhost");

  set_log_mode(lm_owner_player);
  
  server->start(false); // start w/o pipes.
  
}
int
CardWords_GtkClient::remove_button_box_and_connect(void)
{
  delete start_local_button;
  delete save_options_button;
  delete connect_remote_button;
  delete exit_button;
  delete button_box;

  start_local_button = 0;
  save_options_button = 0;
  connect_remote_button = 0;
  exit_button = 0;
  button_box = 0;
  
  if (user_wants_connect) {
    if (connect() <= -1) {
      return true;
    }
    register_reading();
  }
  return false;
}

void
CardWords_GtkClient::read_client_options(void)
{
  assert(options->get_type("use_digits_for_the_columns") == BOOL_TYPE);
  options->get("use_digits_for_the_columns", &use_digits_for_the_columns);

  assert(options->get_type("column_indices_grow_rightwards") == BOOL_TYPE);
  options->get("column_indices_grow_rightwards",
               &column_indices_grow_rightwards);

  assert(options->get_type("row_indices_grow_upwards") == BOOL_TYPE);
  options->get("row_indices_grow_upwards", &row_indices_grow_upwards);

  assert(options->get_type("gtk_card_height") == UNSIGNED_TYPE);
  options->get("gtk_card_height", &cell_height);

  assert(options->get_type("gtk_card_width") == UNSIGNED_TYPE);
  options->get("gtk_card_width", &cell_width);

  max_table_dimensions[use_digits_for_the_columns
                      ? horizontal
                      : vertikal] = 99;
  max_table_dimensions[use_digits_for_the_columns
                      ? vertikal
                      : horizontal] = 'Z' - 'A' + 1;

  string name, alt_name;
  options->get("name", &name);
  options->get("alt_name", &alt_name);
  CardWords_CommClient::set_name(name,
                                 alt_name);
}  
void
CardWords_GtkClient::connect_remote_button_callback(void)
{
  assert(is_connected() == false);
  assert(options_dialog != 0);

  options_dialog->store_options();
  options_dialog->hide();
  remove(*options_dialog);
  delete options_dialog;
  
  button_box->hide();

  // The button_box can not yet be deleted, because one of its buttons will be
  // accessed somewhere in the backtrace. Remove it asynchronously:
  gtk_timeout_add(1050,// Call in 1 second
                  &static_remove_button_box_and_connect,
                  this);

  user_wants_connect = true;
  set_log_mode(lm_owner_player);
  
  read_client_options();

  string hostname;
  assert(options->get_type("remote_hostname") == STRING_TYPE);
  options->get("remote_hostname", &hostname);

  if (set_remote_hostname(hostname) < 0) {
    // No such hostname
    cerr << gettext("ERROR: ")
         << gettext("No such host: `")
         << hostname
         << gettext("'")
         << endl;
    exit(1);
  }

  unsigned port_number;
  assert(options->get_type("remote_port_number") == UNSIGNED_TYPE);
  options->get("remote_port_number", &port_number);

  set_remote_port_number(port_number);
  user_wants_connect = true;

  set_log_mode(lm_player);
}

// When the player draws a new card or cards from other players are moved
// to the board, the client knows what cards are still remaining.
// This method is a hint for clients to update displays showing
// the remaining possible cards:
void
CardWords_GtkClient::
remaining_card_diminished(const CardWords_CardDescription & desc)
{
#ifdef DEBUG
  // Describe on cout what happened:
  CardWords_CommClient::remaining_card_diminished(desc);
#endif
  if (remaining_cards_list != 0) {
    remaining_cards_list->remaining_card_diminished(desc);
  }
}

int
CardWords_GtkClient::all_cards_list_delete_event_callback(GdkEventAny *)
{
  assert(all_cards_list != 0);
  all_cards_list->hide();
  return true; // GTK must not destroy this window.
}
int
CardWords_GtkClient::
remaining_cards_list_delete_event_callback(GdkEventAny *)
{
  assert(remaining_cards_list != 0);
  remaining_cards_list->hide();
  return true; // GTK must not destroy this window.
}
  
// When the player trades cards, they get back to the pile:
void
CardWords_GtkClient::
remaining_card_increased(const CardWords_CardDescription & desc)
{
#ifdef DEBUG
  // Describe on cout what happened:
  CardWords_CommClient::remaining_card_increased(desc);
#endif
  if (remaining_cards_list != 0) {
    remaining_cards_list->remaining_card_increased(desc);
  }
}

static
int
window_deleted(GdkEventAny*)
{
  Gtk::Main::instance()->quit();
  return 0;
}

static
string
ask_for_home_directory(void);

// This function checks the home directory for the required rights.
// It return false, if the rights do not meet the requirement, or if the
// home directory does not exist.
static
bool
check_home_directory_rights(const string &);

static
bool
check_dot_cardwords_directory_exists(const string & home_directory);

static
bool
check_dot_cardwords_directory_rights(const string & home_directory);

// This function returns only if the user accepts the creating of the
// .cardwords directory and the creation was successful
static
void
create_dot_cardwords_directory(const string & home_directory);

// A class that overrides the delete_impl method of a gtk window
class CardWords_Gtk_QuickDialog : public Gtk::Window
{
public:
  CardWords_Gtk_QuickDialog() : Gtk::Window(GTK_WINDOW_DIALOG) {};
protected:
  gint
  delete_event_impl(GdkEventAny*) {return true;};// Don't destroy!
};

// The main function:
int
main(int argc, char **argv)
{
  // Refuse to run as root:
  if (getuid() == 0 || geteuid() == 0) {
    cerr << "This program is part of a game. Do not run this program as root."
         << endl;
    return 1;
  }

  // ==============================
  // init the gettext translations:
  // ==============================
  setlocale (LC_ALL, "");
  bindtextdomain ("cardwords", LOCALEDIR_STR);
  textdomain ("cardwords");


  // ==========================================================
  // Let gtk read its command line options before we read ours:
  // ==========================================================
  Gtk::Main *gtk_main = new Gtk::Main( &argc, &argv );

  // =================================================================
  // Locate the home directory and check for the .cardwords directory:
  // =================================================================
  string home_directory = (DEFAULT_HOMEDIR
                           ? DEFAULT_HOMEDIR
                           : "");
  if (home_directory == "") {
    home_directory = (getenv("HOME")
                      ? getenv("HOME")
                      : "");
    // is usually still "" now, since DEFAULT_HOMEDIR is usually getenv("HOME")
  }
  // ------------------------------
  // Read the command line options:
  // ------------------------------
  int option_character;
  while ((option_character = getopt(argc, argv, "H:"))
         != EOF) {
    switch (option_character) {
    case 'H':
      home_directory = optarg;
      break;
    default:
      cerr << gettext("Usage: cardwords_gtkclient "\
                      "[gtk-specific options] [-H home_directory]")
           << endl;
      exit (1);
    }
  }

  if (home_directory == "") {
    // -----------------------------------------------
    // Ask the user if we still have no home directory
    // -----------------------------------------------
    home_directory = ask_for_home_directory();
  }
  if (home_directory == "") {
    cerr << gettext("Exiting because you have no home directory.")
         << endl;
    exit(1);
  }
  
  // --------------------------------------
  // Check for the home directory's rights:
  // --------------------------------------
  if (check_home_directory_rights(home_directory) == false) {
    cerr << gettext("Exiting because your home directory `")
         << home_directory
         << gettext("' does not exist, or does not have the required rights.")
         << endl;
    return 1;
  }
  // -----------------------------------
  // Check for the .cardwords directory:
  // -----------------------------------
  if (check_dot_cardwords_directory_exists(home_directory) == false) {
    create_dot_cardwords_directory(home_directory);
  }
  if (check_dot_cardwords_directory_rights(home_directory) == false) {
    cerr << gettext("Exiting because your .cardwords directory does not "\
                    "have the required rights.")
         << endl;
    return 1;
  }

  // =================================
  // Read the default option settings:
  // =================================
  ostrstream options_filename;
  options_filename << home_directory
                   << "/.cardwords/"
                   << "options"
                   << ends;
  
  options_filename.freeze(true);

  CardWords_Options * options = new CardWords_Options(options_filename.str());

  options_filename.freeze(false);

  // ========================
  // Start the client object:
  // ========================
  try {
    CardWords_GtkClient * client = new CardWords_GtkClient(options);

    client->set_home_directory(home_directory);
  
    Gtk::Window *window = new Gtk::Window;

    window->delete_event.connect(SigC::slot(&window_deleted));

    window->add(*client);
    client->show();
    window->show();

    gtk_main->run();
  
    client->disconnect();
    return 0;
  }
  catch (CardWords_Exception & x) {
    cerr << gettext("!!!! Exception caught in cardwords_gtkclient:")
         << endl
         << x
         << endl;
    return 1;
  }
}

static Gtk::Window * ask_for_home_directory_window = 0;
static Gtk::FileSelection * ask_for_home_directory_file_selection = 0;
static bool ask_for_home_directory_finished = false;

static
void
ask_for_home_directory_ok_callback(void)
{
  assert (ask_for_home_directory_window != 0);
  assert (ask_for_home_directory_file_selection != 0);
  
  ask_for_home_directory_window->hide();
  ask_for_home_directory_file_selection->show();
}
static
void
ask_for_home_directory_file_selection_ok_callback(void)
{
  assert (ask_for_home_directory_file_selection != 0);
  
  ask_for_home_directory_file_selection->hide();
  ask_for_home_directory_finished = true;
}
static
int
ask_for_home_directory_delete_callback(GdkEventAny*)
{
  exit(0);
}

static
string
ask_for_home_directory(void)
{
  CardWords_Gtk_QuickDialog window;
  Gtk::FileSelection file_selection(gettext("Search home directory"));

  Gtk::VBox vbox;
  window.add(vbox);

  Gtk::HBox hbox;
  vbox.pack_end(hbox);

  Gtk::Label
    question(gettext("The program could not determine where your home\n"\
                     "directory is. You can set the environment variable\n"\
                     "`HOME' or use the command line option -H <home dir>\n"\
                     "next time in order to avoid this dialog.\n"\
                     "\n"\
                     "The program will create a directory `.cardwords'\n"\
                     "inside your home directory and store your personal\n"\
                     "options and sometimes an internal password there.\n"\
                     "\n"\
                     "You can now choose a home directory by hand or abort\n"\
                     "the program."));
  question.set_justify(GTK_JUSTIFY_LEFT);

  vbox.pack_start(question);

  Gtk::Button ok_button(gettext("Chose a home directory"));
  hbox.pack_start(ok_button);
  
  Gtk::Button abort_button(gettext("Abort program"));
  hbox.pack_end(abort_button);

  ok_button.show();
  abort_button.show();
  hbox.show();
  question.show();
  vbox.show();
  window.show();

  ok_button.clicked.connect(SigC::slot(&ask_for_home_directory_ok_callback));

  abort_button.clicked.connect(SigC::slot(&exit_callback));

  window.delete_event.connect(SigC::slot(&ask_for_home_directory_delete_callback));

  file_selection.get_ok_button()->clicked.
    connect(SigC::slot(&ask_for_home_directory_file_selection_ok_callback));

  file_selection.get_cancel_button()->clicked.
    connect(SigC::slot(&exit_callback));

  file_selection.delete_event.
    connect(SigC::slot(&ask_for_home_directory_delete_callback));
  
  ask_for_home_directory_window = &window;
  ask_for_home_directory_file_selection = &file_selection;

  // Gtk::Main::instance()->quit() exits the program with my current versions
  // of Gtk-- and Gtk+, thus the dirty workaround here:
  while (ask_for_home_directory_finished == false) {
    assert(ask_for_home_directory_finished == false);
    Gtk::Main::instance()->iteration();
  }

  return file_selection.get_filename();
}

// This function checks the home directory for the required rights.
// It return false, if the rights do not meet the requirement, or if the
// home directory does not exist.
static
bool
check_home_directory_rights(const string & home_directory)
{
  struct stat home_dir_stat;
  uid_t  process_uid = getuid();
  if (stat(home_directory.c_str(), & home_dir_stat) == -1) {
    perror(home_directory.c_str());
    fflush(stderr);
    cerr << gettext("WARNING: ")
         << gettext("The home directory could not be accessed.")
         << endl;
    return false;
  }
  if (home_dir_stat.st_uid != process_uid) {
    cerr << gettext("WARNING: ")
         << gettext("Your home directory does not belong to you. "\
                    "You can specify another directory to be used as your "\
                    "home directory on the command line using the -H "\
                    "option.")
         << endl;
    return false;
  };
  if ((home_dir_stat.st_mode & S_IFDIR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("Your home directory is not a directory. "\
                    "You can specify another directory to be used as your "\
                    "home directory on the command line using the -H option.")
         << endl;
    return false;
  }
  if ((home_dir_stat.st_mode & S_IRUSR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("Your home directory is not readable for you. "\
                    "You can specify another directory to be used as your "\
                    "home directory on the command line using the -H "\
                    "option.")
         << endl;
    return false;
  }
  if ((home_dir_stat.st_mode & S_IXUSR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("Your home directory is not executable for you. "\
                    "You can specify another directory to be used as your "\
                    "home directory on the command line using the -H "\
                    "option.")
         << endl;
    return false;
  }
  if (chdir(home_directory.c_str()) == -1) {
    perror(home_directory.c_str());
    fflush(stderr);
    cerr << gettext("WARNING: ")
         << gettext("Unable to chdir to the home directory.")
         << endl;
    return false;
  }
  if (check_dot_cardwords_directory_exists(home_directory) == false
      && (home_dir_stat.st_mode & S_IWUSR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("Your home directory does not contain a subdirectory "
                    "`.cardwords' (which is needed for the game) and at "\
                    "the same time ")
         << gettext("your home directory is not writable for you. "\
                    "You can specify another directory to be used as your "\
                    "home directory on the command line using the -H "\
                    "option.")
         << endl;
    return false;
  }
  return true;
}

static
bool
check_dot_cardwords_directory_exists(const string & home_directory)
{
  string dot_cardwords_dirname = home_directory;
  dot_cardwords_dirname += "/.cardwords";

  struct stat dot_cardwords_dir_stat;
  if (stat(dot_cardwords_dirname.c_str(),
           & dot_cardwords_dir_stat) == -1) {
    return false;
  }

  return true;
}

static
bool
check_dot_cardwords_directory_rights(const string & home_directory)
{
  string dot_cardwords_dirname = home_directory;
  dot_cardwords_dirname += "/.cardwords";

  struct stat dot_cardwords_dir_stat;
  uid_t  process_uid = getuid();

  if (stat(dot_cardwords_dirname.c_str(),
           & dot_cardwords_dir_stat) == -1) {
    perror(dot_cardwords_dirname.c_str());
    fflush(stderr);
    cerr << gettext("WARNING: ")
         << gettext("The `.cardwords' directory inside your home "\
                    "directory could not be accessed.")
         << endl;
    return false;
  }
  if (dot_cardwords_dir_stat.st_uid != process_uid) {
    cerr << gettext("WARNING: ")
         << gettext("The `.cardwords' directory inside your home directory "\
                    "does not belong to you.")
         << endl;
    return false;
  };
  if ((dot_cardwords_dir_stat.st_mode & S_IFDIR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("`.cardwords' inside your home "\
                    "directory is not a directory. ")
         << endl;
    return false;
  }
  if ((dot_cardwords_dir_stat.st_mode & S_IRUSR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("The `.cardwords' directory inside your home directory "\
                    "is not readable for you.")
         << endl;
    return false;
  }
  if ((dot_cardwords_dir_stat.st_mode & S_IXUSR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("The `.cardwords' directory inside your home "\
                    "directory is not executable for you.")
         << endl;
    return false;
  }
  if ((dot_cardwords_dir_stat.st_mode & S_IWUSR) == 0) {
    cerr << gettext("WARNING: ")
         << gettext("The `.cardwords' directory inside your home "\
                    "directory is not writeable for you.")
         << endl;
    return false;
  }
  if ((dot_cardwords_dir_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
    cerr << gettext("WARNING: The `.cardwords' directory is writeable for "\
                    "others. Better chmod it.") << endl;
    return false;
  }

  if (chdir(dot_cardwords_dirname.c_str()) == -1) {
    perror(dot_cardwords_dirname.c_str());
    fflush(stderr);
    cerr << gettext("WARNING: ")
         << gettext("Unable to chdir to ")
         << dot_cardwords_dirname.c_str()
         << endl;
    return false;
  }

  return true;
}

static bool create_dot_cardwords_directory_yes_chosen = false;
static
void
create_dot_cardwords_directory_yes_callback(void)
{
  create_dot_cardwords_directory_yes_chosen = true;
}

// This function returns only if the user accepts the creating of the
// .cardwords directory and the creation was successful
static
void
create_dot_cardwords_directory(const string & home_directory)
{
  CardWords_Gtk_QuickDialog window;

  Gtk::VBox vbox;
  window.add(vbox);

  Gtk::HBox hbox;
  vbox.pack_end(hbox);

  ostrstream question_text;
  question_text << gettext("This program is part of cardwords,"\
                           " Copyright (C) 1998 1999 Tobias Peters\n"\
                           "cardwords is free software; you can redistribute"\
                           " it and/or modify\n"
                           "it under the terms of the GNU General Public"\
                           " License as published by\n"
                           "the Free Software Foundation; either version 2"\
                           " of the License, or\n"
                           "(at your option) any later version.\n"
                           "\n"
                           "This program is distributed in the hope"\
                           " that it will be useful,\n"
                           "but WITHOUT ANY WARRANTY; without even"\
                           " the implied warranty of\n"
                           "MERCHANTABILITY or FITNESS FOR A "\
                           " PARTICULAR PURPOSE.  See the\n"
                           "GNU General Public License for "\
                           " more details.\n"
                           "\n"
                           "You should have received a copy of "\
                           " the GNU General Public License\n"
                           "along with this program; if not, write"\
                           " to the Free Software\n"
                           "Foundation, Inc., 59 Temple Place,"\
                           " Suite 330, Boston, MA  02111-1307  USA\n")
                << endl
                << gettext("cardwords needs a subdirectory `.cardwords'\n"\
                           "inside your home directory (`")
                << home_directory
                << gettext("'),\n"\
                           "where your personal option settings and, if\n"\
                           "you start a local server, the owner's (your)\n"\
                           "passwords of that server. You do not need\n"\
                           "to worry about that, it will all be handled\n"\
                           "automatically.\n"\
                           "\n"\
                           "May the program now create this directory ?")
                << ends;
  question_text.freeze(true);
  Gtk::Label
    question(question_text.str());
  question_text.freeze(false);
  question.set_justify(GTK_JUSTIFY_LEFT);

  vbox.pack_start(question);

  Gtk::Button yes_button(gettext("Yes"));
  hbox.pack_start(yes_button);
  
  Gtk::Button abort_button(gettext("No, abort program"));
  hbox.pack_end(abort_button);

  yes_button.show();
  abort_button.show();
  hbox.show();
  question.show();
  vbox.show();
  window.show();

  yes_button.clicked.
    connect(SigC::slot(&create_dot_cardwords_directory_yes_callback));

  abort_button.clicked.connect(SigC::slot(&exit_callback));

  window.delete_event.
    connect(SigC::slot(&ask_for_home_directory_delete_callback));

  // Gtk::Main::instance()->quit() exits the program with my current versions
  // of Gtk-- and Gtk+, thus the dirty workaround here:
  while (create_dot_cardwords_directory_yes_chosen == false) {
    Gtk::Main::instance()->iteration();
  }

  // The user chose yes, otherwise the program would have called exit().
  window.hide();

  string dot_cardwords_dirname = home_directory;
  dot_cardwords_dirname += "/.cardwords";

  if (mkdir (dot_cardwords_dirname.c_str(), 0755) == -1) {
    perror(gettext("Creating the directory `.cardwords' "\
                   "inside the home directory"));
    fflush(stderr);
    exit(1);
  }  
}

void
CardWords_GtkClient::disconnect(void)
{
  CardWords_CommClient::disconnect();
}
