/* 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_commclient.cc

// CardWords_CommClient is intended to be the communication link to the
// server for any cardwords_client programm.
// It should do what any of these client programms would do the same
// way. While I first thought, every client should derive a class from
// CardWords_BaseClient, I see now that it would be senseless if any derived
// class implenented its own way of logging the user on, recognizing
// server messages and the like.
// Note however, that it will still be necessary for any client programm
// to derive a class from CardWords_CommClient and overwrite the virtual
// functions for (de)register_{reading|writing} (see CardWords_BaseClient).

// CardWords_CommClient will probably get very huge

#include <stdlib.h>
#include <climits>

#include "cardwords_commclient.hh"
#include "cardwords_clientpile.hh"
#include "cardwords_cardtable.hh"
#include "cardwords_bonustable.hh"
#include "cardwords_hand.hh"
#include "cardwords_exceptions.hh"
#include "cardwords_server.hh"
#include "cardwords_carddescription.hh"

CardWords_CommClient::CardWords_CommClient()
  :
  pile   (0),
  table (0),
  bonus (0),
  hand (0),

  // Has the server send the "LOG_AS" message?
  received_log_as_message (false),

  // Has the server accepted the login?
  login_accepted          (false),

  // Has the server rejected the login?
  login_rejected          (false),

  // If yes, for what reason?
  server_full             (false),
  non_unique_name         (false),
  log_mode                (CardWords_CommClient::lm_player),

  playerlist              (0),

  my_go                   (false),

  trying_move             (false),
  trying_trade            (false),
  trying_pass             (false),
  false_word_card_index   (UINT_MAX),

  card_spec_map           (0),
  possible_remaining_cards_map (0),
  game_over               (false)
{}

CardWords_CommClient::~CardWords_CommClient()
{
  delete pile;
  delete table;
  delete bonus;
  delete hand;
}

// The function set_name should only be called while not connected.
// It sets the user name for logging onto the server. An alternative
// name can be given and will be used if the server responds that the
// first name is already in use.
// Previous settings will be overwritten by this call:
void
CardWords_CommClient::set_name(string new_name,
                               const string & new_alt_name = "")
{
  if (is_connected() == true) {
    if (get_used_name() != "") {
      throw CardWords_XInternal_Error(gettext("CardWords_CommClient::"\
                                              "set_name(): "\
                                              "called despite a name is "\
                                              "already in use."));
    }
    // FIXME: if connected but no name is in use now, a call to this function
    // probably means that the names were not unique and the user typed in a
    // new name. Notify the communication somehow that the names have changed.
    // For now:
    throw CardWords_XInternal_Error(gettext("CardWords_CommClient::"\
                                            "set_name(): "\
                                            "called while connected."));
  }
  if (new_name == "") {
    name = new_alt_name;
    alt_name = "";
  }
  else {
    name = new_name;
    alt_name = new_alt_name;
  }
}

// return the names:
const string &
CardWords_CommClient::get_name(void) const
{
  return name;
}

const string &
CardWords_CommClient::get_alt_name(void) const
{
  return alt_name;
}

// This one returns the name that is actually in use. will be 0, if not
// connected:
const string &
CardWords_CommClient::get_used_name(void) const
{
  return used_name;
}

// connect() will set the object in a state that it knows the next messages
// are about logging. The logging messages will be handled automatically
// inside the object.
// connect() will return as soon as the tcp connection is established or
// failed. If the connection fails, it will return -1, if it is up and
// working, it returns 0.
int
CardWords_CommClient::connect(void)
{
  int connect_value = CardWords_BaseClient::connect();

  if (connect_value < 0) {
    return connect_value;
  }
  // All flags should be set correctly by the constructor or a prior
  // disconnect() call:

  assert (get_pile() == 0);
  assert (get_table() == 0);
  assert (get_bonus() == 0);
  
  // The previous charset will be deleted when the new charset is read,
  // no need to check this now.

  assert (get_used_name() == "");

  assert (has_received_log_as_message() == false);
  assert (is_login_accepted() == false);
  assert (is_login_rejected() == false);
  return 0;
}

// disconnect() discards pending messages, deregisters from reading and
// writing and closes the tcp connection immediately, without sending a
// message to the server.
void
CardWords_CommClient::disconnect(void)
{
  CardWords_BaseClient::disconnect();

  // reset the connection flags:
  delete pile;
  pile = 0;
  delete table;
  table = 0;
  delete bonus;
  bonus = 0;
  delete hand;
  hand = 0;

  if (used_name != "") {
    used_name = "";
  }

  received_log_as_message = false;
  login_accepted = false;
  login_rejected = false;
  server_full    = false;
  non_unique_name = false;
}


// Set the desired log mode:
void
CardWords_CommClient::set_log_mode(CardWords_CommClient::Log_Mode lm)
{
  log_mode = lm;
}

CardWords_CommClient::Log_Mode
CardWords_CommClient::get_log_mode(void) const
{
  return log_mode;
}



// During logging, the server will send game info to the client. This Info
// can be accessed by the following functions:

const CardWords_ClientPile *
CardWords_CommClient::get_pile(void) const
{
  return pile;
}

const CardWords_CardTable *
CardWords_CommClient::get_table(void) const
{
  return table;
}

const CardWords_BonusTable *
CardWords_CommClient::get_bonus(void) const
{
  return bonus;
}

// Has the server send the "LOG_AS" message?
bool
CardWords_CommClient::has_received_log_as_message(void) const
{
  return received_log_as_message;
}

// Has the server accepted the login?
bool
CardWords_CommClient::is_login_accepted(void) const
{
  return login_accepted;
}

// Has the server rejected the login?
bool
CardWords_CommClient::is_login_rejected(void) const
{
  return login_rejected;
}

// If yes, for what reason?
bool
CardWords_CommClient::is_server_full(void) const
{
  // only makes sense if login_rejected:
  assert(is_login_rejected() == true);
  return server_full;
}

bool
CardWords_CommClient::is_non_unique_name(void) const
{
  // only makes sense if login_rejected:
  assert(is_login_rejected() == true);
  return non_unique_name;
}

// static function to compare the meaning of the message:
// returns true if messages are equal.
inline bool
CardWords_CommClient::message_compare (const string & test_message,
                                       const string & known_message)
{
  return test_message.compare(known_message, 0, known_message.length()) == 0;
}
// Callback to a complete network message:
void
CardWords_CommClient::
received_complete_message(char message_type_indicator,
                                // see cardwords_server.hh,
                                // MESSAGE_*_INDICATOR,

                          const string & message)
{
  assert(is_connected() == true);
  
  switch (message_type_indicator) {
  case MESSAGE_PERSONAL_INFO_INDICATOR:
    // **** MESSAGE_LOG_AS: ********
    if (message_compare(message, MESSAGE_LOG_AS)) {
      if (get_used_name().length() != 0) {
        throw CardWords_XBadNetworkMessage(gettext("log-as network message "\
                                                   "from cardwords_server "\
                                                   "while a name is already "\
                                                   "in use"),
                                           gettext("valid network message"));
      }
      if (is_login_accepted() == true) {
        throw CardWords_XBadNetworkMessage(gettext("log-as network message "\
                                                   "from cardwords_server "\
                                                   "while our login is "\
                                                   "already accepted"),
                                           gettext("valid network message"));
      }
      if (is_login_rejected() == true) {
        throw CardWords_XBadNetworkMessage(gettext("log-as network message "\
                                                   "from cardwords_server "\
                                                   "while our login is "\
                                                   "already rejected"),
                                           gettext("valid network message"));
      }
      if (has_received_log_as_message() == true) {
        throw CardWords_XBadNetworkMessage(gettext("second log-as network "\
                                                   "message from "\
                                                   "cardwords_server"),
                                           gettext("valid network message"));
      }

      received_log_as_message = true;
      istrstream istr(message.c_str() + strlen(MESSAGE_LOG_AS));

      // read the log possibilities:
      string possibility;
      bool owner_watcher_possible = false;
      bool owner_player_possible = false;
      bool player_possible = false;
      bool watcher_possible = false;
      
      while (istr.good()
             && ( (istr >> possibility),
                  (possibility[0] != MESSAGE_LOG_AS_END)
                  )
             ) {
        if (possibility == STRING_OWNER_PLAYER) {
          owner_player_possible = true;
        }
        else if (possibility == STRING_OWNER_WATCHER) {
          owner_watcher_possible = true;
        }
        else if (possibility == STRING_PLAYER) {
          player_possible = true;
        }
        else if (possibility == STRING_WATCHER) {
          watcher_possible = true;
        }
        else {
          throw CardWords_XBadInput(gettext("network message from "\
                                            "cardwords_server, "\
                                            "message has invalid log "\
                                            "possibility"),
                                    gettext("valid log possibility "\
                                            "or \';\'"));
        }
      }

      // Now look if we can log on in the desired mode:
      if (
          (log_mode == lm_player        &&        player_possible == false)
          ||
          (log_mode == lm_watcher       &&       watcher_possible == false)
          ||
          (log_mode == lm_owner_player  &&  owner_player_possible == false)
          ||
          (log_mode == lm_owner_watcher && owner_watcher_possible == false)
          ) {
        // not possible to log in in desired mode, so the server is full:
        login_rejected = true;
        server_full = true;
        
        error(en_server_full);
        
        return;
      }

      // delete previos charset definitions:
      CardWords_Char::reset();
        
      // define the charset:
      CardWords_Char::define_from_stream(istr);

      // define the cards:
      pile = new CardWords_ClientPile(istr);
      {
        ostrstream card_spec_stream;

        card_spec_stream << *pile << ends;

        card_spec_stream.freeze(true);
        card_spec_map =
          new CardWords_CardOccurrenceMap(card_spec_stream.str());
        possible_remaining_cards_map =
          new CardWords_CardOccurrenceMap(card_spec_stream.str());
        card_spec_stream.freeze(false);
      }

      // define the bonus table:
      bonus = new CardWords_BonusTable(istr);

      // define the table:
      table = read_card_table(istr);

      // create the hand:
      hand = new CardWords_Hand(bonus->get_hand_capacity());
      
      // test if the client is compatible with these charset, cardset, table
      // and bonus definitions:
      if (test_client_compatible_with_server_settings() == false) {
        error(en_incompatible_server_settings);
        disconnect();
        return;
      }

      log_on(get_name());
      state_changed(SC_SERVER_RESPONDED_1ST_TIME);
      return;
    }
    // **** MESSAGE_NON_UNIQUE_NAME: ********
    if (message_compare(message, MESSAGE_NON_UNIQUE_NAME)) {
      if (get_used_name() == get_name()
          && get_alt_name() != "") {
        log_on(get_alt_name());
        state_changed(SC_TRYING_ALT_NAME);
      }
      else {
        // unable to log on because the name(s) are already in use:
        login_rejected = true;
        non_unique_name = true;
        
        error(en_non_unique_name);
      }
      return;
    }
    // **** MESSAGE_WELCOME: ********
    if (message_compare(message, MESSAGE_WELCOME)) {
      login_accepted = true;
      string logname;
      istrstream message_stream(message.c_str()
                                + strlen(MESSAGE_WELCOME));
      message_stream >> logname;
      if (used_name.length() == 0) {
        throw CardWords_XBadNetworkMessage(gettext("welcome network message "\
                                                   "from cardwords_server "\
                                                   "while no name is yet "\
                                                   "in use"),
                                           gettext("valid network message"));
      }
      if (logname != used_name) {
        throw CardWords_XBadNetworkMessage(gettext("welcome network message "\
                                                   "from cardwords_server "\
                                                   "with another name than "\
                                                   "is in use"),
                                           gettext("valid network message"));
      }

      state_changed(SC_LOGGED_IN);
      return;
    }
    // **** MESSAGE_NEW_CARD: ********
    if (message_compare(message, MESSAGE_NEW_CARD)) {
      if (login_accepted == false) {
        throw CardWords_XBadNetworkMessage(gettext("new-card network "\
                                                   "message from "\
                                                   "cardwords_server "\
                                                   "while not logged in"),
                                           gettext("valid network message"));
      }
      if (used_name.length() == 0) {
        throw CardWords_XBadNetworkMessage(gettext("new-card network "\
                                                   "message from "\
                                                   "cardwords_server "\
                                                   "while no name is used"),
                                           gettext("valid network message"));
      }
        
      if (get_hand()->free_places() == 0U) {
        throw CardWords_XBadNetworkMessage(gettext("new-card network "\
                                                   "message from "\
                                                   "cardwords_server "\
                                                   "while there is no space "\
                                                   "on our hand"),
                                           gettext("valid network message"));
      }

      CardWords_CardDescription desc;
      istrstream message_stream(message.c_str()
                                + strlen(MESSAGE_NEW_CARD));

      message_stream >> desc;
      
      hand->add_card(get_pile()->matching_card(desc));

      assert((*possible_remaining_cards_map)[desc] != 0);
      --(*possible_remaining_cards_map)[desc];
      remaining_card_diminished(desc);
      
      state_changed(SC_NEW_CARD);
      return;
    }
    // **** MESSAGE_BYE ******
    if (message_compare(message, MESSAGE_BYE)) {
      // The server does not want us
      disconnect();
      state_changed(SC_MESSAGE_BYE_RECEIVED);
      return;
    }
    // **** MESSAGE_YOUR_GO *****
    if (message_compare(message, MESSAGE_YOUR_GO)) {
      // It's our go, the user has to think about his word now:
      if (get_playerlist() == 0) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says it is my go"),
                                  gettext("logged-players-message, "\
                                          "game-starts-message, "\
                                          "go-of-message"));
      }
      if (get_playerlist()->is_game_running() == false) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says it is my go"),
                                  gettext("game-starts-message, "\
                                          "go-of-message"));
      }
      if (get_playerlist()->get_active_player_name() != get_used_name()) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says it is my go"),
                                  gettext("go-of-message"));
      }
      my_go = true;
      state_changed(SC_MY_GO);
      return;
    }
        
    // **** MESSAGE_GO_FINISHED *****
    if (message_compare(message, MESSAGE_GO_FINISHED)) {
      // Our go is over:
      if (my_go == false) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says my go is finished"),
                                  gettext("your-go-message"));
      }
      my_go = false;
      state_changed(SC_MY_GO_FINISHED);
      return;
    }
    // **** MESSAGE_ACCEPTING_YOUR_TRADE *****
    if (message_compare(message, MESSAGE_ACCEPTING_YOUR_TRADE)) {
      if (is_my_go() == false) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says the server accepts "\
                                          "my trade, however it is not even "\
                                          "my go"),
                                  gettext("your-go-message"));
      }
      if (am_i_trying_trade() == false) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says the server "\
                                          "accepts my "\
                                          "trade, however i did not "\
                                          "try a trade"),
                                  gettext("valid message"));
      }
      assert(am_i_trying_move() == false);
      assert(am_i_trying_pass() == false);

      trying_trade = false; // FIXME: here or better while handling
      //                              PLAYER_TRADES_CARDS?
      
      // Our tryde is being worked on by the server.
      // Remove the cards from the hand to make room for the new ones:
      CardWords_CardSelection trade;
      istrstream message_stream(message.c_str()
                                + strlen(MESSAGE_ACCEPTING_YOUR_TRADE));

      message_stream >> trade;

      if (trade != current_trying_trade) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says the server "\
                                          "accepts my trade, but I "\
                                          "requested a different trade"),
                                  gettext("my trade"));
      }
        
      CardWords_CardContainer1Dim temp_cardcontainer(trade.size());
      size_t index;
      CardWords_Card * card;

      for (index = 0; index < trade.size(); ++index) {

        card = get_hand()->matching_card(trade[index]);
        if (card == 0) {
          // No such card on the hand, it cannot be traded.
          throw CardWords_XBadInput(gettext("network message from "\
                                            "cardwords_server, "\
                                            "message says the server "\
                                            "accepts my "\
                                            "trade, but the trade "\
                                            "contains a card"\
                                            " that I don't have"),
                                    gettext("MESSAGE_NO_SUCH_CARD"));

        }
        temp_cardcontainer.add_card(card);
        ++(*possible_remaining_cards_map)[trade[index]];
        remaining_card_increased(trade[index]);
      }

      temp_cardcontainer.clear(pile);
      
      state_changed(SC_TRADE_ACCEPTED);
      return;
    }

    
  case MESSAGE_INFO_INDICATOR:
    // **** MESSAGE_LOGGED_PLAYERS ******
    if (message_compare(message, MESSAGE_LOGGED_PLAYERS)) {
      // new info about which players are logged in.
      if (get_playerlist() == 0) {
        // This is the first info that we get about the players:
        playerlist = new CardWords_PlayerList(message);
      }
      else {
        // Something might have changed regarding the logged players:
        playerlist->update_info(message);
      }
      state_changed(SC_PLAYER_INFO);
      return;
    }
    // **** MESSAGE_REMAINING_CARDS ****
    if (message_compare(message, MESSAGE_REMAINING_CARDS)) {
      // The server tells us how many cards are left in the pile.
      istrstream message_stream(message.c_str()
                                + strlen(MESSAGE_REMAINING_CARDS));

      size_t remaining_cards;
      message_stream >> remaining_cards;
      pile->set_remaining_cards(remaining_cards);

      state_changed(SC_REMAINING_CARDS);
      return;
    }
    // **** MESSAGE_GAME_STARTS ****
    if (message_compare(message, MESSAGE_GAME_STARTS)) {
      if (get_playerlist() == 0) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says game starts"),
                                  gettext("logged-players-message"));
      }
      playerlist->game_starts(message);

      state_changed(SC_GAME_STARTED);
      return;
    }
    // **** MESSAGE_GO_OF ****
    if (message_compare(message, MESSAGE_GO_OF)) {
      if (get_playerlist() == 0) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says it someone's go"),
                                  gettext("logged-players-message and game-"\
                                          "starts-message"));
      }
      if (get_playerlist()->is_game_running() == false) {
        throw CardWords_XBadInput(gettext("network message from "\
                                          "cardwords_server, "\
                                          "message says it is my go"),
                                  gettext("game-starts-message"));
      }
      playerlist->set_active_player(message);
      // Don't panic, even if this is my name, we get another personal message
      // (MESSAGE_YOUR_GO) from the server before any action must be taken.
      state_changed(SC_ACTIVE_PLAYER);
      return;
    }
    // **** MESSAGE_PLAYER_TRIES_MOVE ****
    if (message_compare(message, MESSAGE_PLAYER_TRIES_MOVE)) {
      istrstream move_stream (message.c_str()
			      + strlen(MESSAGE_PLAYER_TRIES_MOVE));
      string name;
      move_stream >> name >> current_move;
      if (get_playerlist()->get_active_player_name() != name) {
	throw CardWords_XBadInput(gettext("MESSAGE_PLAYER_TRIES_MOVE network "\
                                          "message from cardwords_server "),
                                  gettext("the same player name as before"));
      }
      if (my_go) {
        if (am_i_trying_move() != true) {
          throw CardWords_XBadInput(gettext("MESSAGE_PLAYER_TRIES_MOVE "\
                                            "network "\
                                            "message from cardwords_server "\
                                            "while it is my go and I did "\
                                            "not try a move"),
                                    gettext("valid network message"));
        }
        // am_i_trying_move() == true
        assert(am_i_trying_trade() == false);
        assert(am_i_trying_pass() == false);
      }
      state_changed(SC_PLAYER_TRIES_MOVE);
      return;
    }
    // **** MESSAGE_PLAYER_GETS_POINTS ****
    if (message_compare(message, MESSAGE_PLAYER_GETS_POINTS)) {
      string name;
      istrstream points_stream (message.c_str()
				+ strlen(MESSAGE_PLAYER_GETS_POINTS));
      points_stream >> name >> current_points;
      if (get_playerlist()->get_active_player_name() != name) {
	throw CardWords_XBadInput(gettext("MESSAGE_PLAYER_GETS_POINTS "\
                                          "network message "\
                                          "from cardwords_server "),
                                  gettext("the same player name as before"));
      }
      playerlist->set_last_points_of(get_playerlist()
                                     ->get_active_player_pos(),
                                     current_points);
      state_changed(SC_PLAYER_GETS_POINTS);
      return;
    }
    // **** MESSAGE_MOVE_DONE ****
    if (message_compare(message, MESSAGE_MOVE_DONE)) {
      istrstream move_stream (message.c_str()
			      + strlen(MESSAGE_MOVE_DONE));
      CardWords_Move move;
      move_stream >> move;
      if (move != current_move) {
	throw CardWords_XBadInput(gettext("MESSAGE_MOVE_DONE network "\
					"message from cardwords_server "),
                                gettext("the same move as before"));
      }

      if (my_go) {
        table->add_move(move, hand);
      }
      else {
        table->add_move(move, pile);
        // also update the info about possible remaining cards:
        size_t index;
        for (index = 0; index < move.size(); ++index) {
          remaining_card_diminished(move[index]);
        }
      }
      state_changed(SC_MOVE_DONE);
      return;
    }
    // **** MESSAGE_PLAYER_PASSES ****
    if (message_compare(message, MESSAGE_PLAYER_PASSES)) {
      istrstream pass_stream (message.c_str()
			      + strlen(MESSAGE_PLAYER_PASSES));
      string name;
      pass_stream >> name;
      if (get_playerlist()->get_active_player_name() != name) {
	throw CardWords_XBadInput(gettext("MESSAGE_PLAYER_PASSES network "\
                                          "message from cardwords_server "),
                                  gettext("the same player name as before"));
      }

      if (my_go) {
        assert (am_i_trying_move() == false);
        assert (am_i_trying_trade() == false);
        assert (am_i_trying_pass() == true);
        trying_pass = false;
      }
      state_changed(SC_PLAYER_PASSES);
      return;
    }
    // **** MESSAGE_MOVE_NOT_ALLOWED ****
    if (message_compare(message, MESSAGE_MOVE_NOT_ALLOWED)) {
      istrstream not_allowed_stream (message.c_str()
                                     + strlen(MESSAGE_MOVE_NOT_ALLOWED));
      CardWords_Move move;
      not_allowed_stream >> move;
      
      if (my_go) {
        if (am_i_trying_move() != true) {
          throw CardWords_XBadInput(gettext("MESSAGE_MOVE_NOT_ALLOWED "\
                                            "network message from "\
                                            "cardwords_server while"\
                                            " it is my go and I did not "\
                                            "try a move"),
                                    gettext("valid network message"));
        }
        // am_i_trying_move() == true
        assert(am_i_trying_trade() == false);
        assert(am_i_trying_pass() == false);

        trying_move = false;
      }
      state_changed(SC_MOVE_NOT_ALLOWED);
      return;
    }
    // **** MESSAGE_MOVE_WOULD_CREATE_UNKNOWN_WORD_AT ****
    if (message_compare(message, MESSAGE_MOVE_WOULD_CREATE_UNKNOWN_WORD_AT)) {
      istrstream
        unknown_stream (message.c_str()
                        + strlen(MESSAGE_MOVE_WOULD_CREATE_UNKNOWN_WORD_AT));
      CardWords_Move move;

      false_word_card_index = size_t(-1);
      unknown_stream >> move >> false_word >> false_word_card_index;
      
      if (my_go) {
        if (am_i_trying_move() != true) {
          throw CardWords_XBadInput(gettext("MESSAGE_MOVE_WOULD_CREATE_"\
                                            "UNKNOWN_WORD_AT network "\
                                            "message from cardwords_server "\
                                            "while it is my go and I did "\
                                            "not try a move"),
                                    gettext("valid network message"));
        }
        // am_i_trying_move() == true
        assert(am_i_trying_trade() == false);
        assert(am_i_trying_pass() == false);

        trying_move = false;
      }
      state_changed(SC_MOVE_WOULD_CREATE_UNKNOWN_WORD_AT);
      return;
    }
    // **** MESSAGE_MOVE_WOULD_CREATE_UNKNOWN_WORD ****
    if (message_compare(message, MESSAGE_MOVE_WOULD_CREATE_UNKNOWN_WORD)) {
      istrstream
        unknown_stream (message.c_str()
                        + strlen(MESSAGE_MOVE_WOULD_CREATE_UNKNOWN_WORD));
      CardWords_Move move;
      
      unknown_stream >> move >> false_word;
      false_word_card_index = size_t(-1);
      
      if (my_go) {
        if (am_i_trying_move() != true) {
          throw CardWords_XBadInput(gettext("MESSAGE_MOVE_WOULD_CREATE_"\
                                            "UNKNOWN_WORD network "\
                                            "message from cardwords_server "\
                                            "while it is my go and I did "\
                                            "not try a move"),
                                    gettext("valid network message"));
        }
        // am_i_trying_move() == true
        assert(am_i_trying_trade() == false);
        assert(am_i_trying_pass() == false);

        trying_move = false;
      }
      state_changed(SC_MOVE_WOULD_CREATE_UNKNOWN_WORD);
      return;
    }
    // **** MESSAGE_PLAYER_MUST_PASS ****
    if (message_compare(message, MESSAGE_PLAYER_MUST_PASS)) {
      istrstream must_pass_stream (message.c_str()
                                    + strlen(MESSAGE_PLAYER_MUST_PASS));
      string name;

      must_pass_stream >> name;

      if (get_playerlist()->get_active_player_name() != name) {
	throw CardWords_XBadInput(gettext("MESSAGE_PLAYER_MUST_PASS network "\
                                          "message from cardwords_server "),
                                  gettext("the same player name as before"));
      }

      if (my_go) {
        trying_trade = false;
        trying_move  = false;
        trying_pass  = true;
      }
      state_changed(SC_PLAYER_MUST_PASS);
      return;
    }
    // **** MESSAGE_NEW_WORD ****
    if (message_compare(message, MESSAGE_NEW_WORD)) {
      istrstream new_word_stream (message.c_str()
                                  + strlen(MESSAGE_NEW_WORD));

      new_word_stream >> last_new_word;

      state_changed(SC_NEW_WORD);
      return;
    }
    // **** MESSAGE_GAME_ENDS ****
    if (message_compare(message, MESSAGE_GAME_ENDS)) {
      game_over = true;
      state_changed(SC_GAME_OVER);
      
      return;
    }

    
  case MESSAGE_ANSWER_INDICATOR:
    cout << "Unhandled message of type "
         << message_type_indicator << ":" << endl
         << message << endl;
    return;
  default:
    throw CardWords_XBadInput(gettext("network message from "\
                                      "cardwords_server, "\
                                      "message has invalid "\
                                      "type indicator"),
                              gettext("valid message type indicator"));
  }
}

const CardWords_Hand *
CardWords_CommClient::get_hand(void) const
{
return hand;
}
CardWords_Hand *
CardWords_CommClient::get_hand(void)
{
  return hand;
}
  


// This static method tries to read the owner password from
// ~/.cardwords/owner_password:
static void
read_password (string * password,
               const string & home_directory)
{
  // Read the owner's password from the file
  // ~/.cardwords/owner_password
  // which is only readable by the owner of the server process.
  struct stat home_dir_stat;
  struct stat cardwords_dir_stat;
  // unused: struct stat password_file_stat;
  // unused: uid_t  process_uid = getuid();
  // unused: int old_umask = umask (0); 

  assert (password != 0);
  
  if (home_directory == "") {
    return;
  }
  if (stat(home_directory.c_str(), & home_dir_stat) == -1) {
    return;
  }
  if ((home_dir_stat.st_mode & S_IFDIR) == 0) {
    // Home directory is not a directory.
    return;
  }
  if ((home_dir_stat.st_mode & S_IRUSR) == 0) {
    // Home directory is not readable for user.
    return;
  }
  if ((home_dir_stat.st_mode & S_IXUSR) == 0) {
    // Home directory is not executable for user.
    return;
  }
  if (chdir(home_directory.c_str()) == -1) {
    // Unable to chdir to the home directory.
    return;
  }
  if (stat("./.cardwords", & cardwords_dir_stat) == -1) {
    // The .cardwords directory does not yet exist.
    return;
  }
  if ((cardwords_dir_stat.st_mode & S_IFDIR) == 0) {
    // .cardwords in user's home directory is not a directory.
    return;
  }
  if ((cardwords_dir_stat.st_mode & S_IRUSR) == 0) {
    // .cardwords directory is not readable for user.
    return;
  }
  if ((cardwords_dir_stat.st_mode & S_IXUSR) == 0) {
    // .cardwords directory is not executable for user.
    return;
  }
  if ((cardwords_dir_stat.st_mode & S_IWUSR) == 0) {
    // .cardwords directory is not writeable for user.
    return;
  }
  if (stat("./.cardwords/owner_password", & cardwords_dir_stat) != 0) {
    // Password file does not exist.
    return;
  }

  ifstream ifstr("./.cardwords/owner_password");
  if (ifstr.bad()) {
    // Unable to open file ./.cardwords/owner_password .
    return;
  }
  ifstr >> (*password);
}



void
CardWords_CommClient::log_on(const string & logname)
{
  ostrstream log_on_message;

  assert (logname != "");
  assert (logname == name || logname == alt_name);

  log_on_message << REQUEST_REGISTER << ' ';
  
  if (log_mode == lm_owner_player
      ||
      log_mode == lm_owner_watcher) {

    // send the type and the password:
    if (log_mode == lm_owner_player) {
      log_on_message << STRING_OWNER_PLAYER;
    }
    else { // lm_owner_watcher
      log_on_message << STRING_OWNER_WATCHER;
    }
    
    log_on_message << ' ';

    if (home_directory.length() == 0) {
#ifdef DEFAULT_HOMEDIR
      if (DEFAULT_HOMEDIR) {
        home_directory = DEFAULT_HOMEDIR;
      }
#else // DEFAULT_HOMEDIR
      if (getenv("HOME")) {
        home_directory = getenv("HOME");
      }
#endif // DEFAULT_HOMEDIR
      if (home_directory.length() == 0) {
        error(en_no_home_directory);
        return;
      }
    }

    string password;
    read_password( & password, home_directory);
      
    if (password.length() != OWNER_PASSWORD_LENGTH) {
      error(en_no_password_file);
      return;
    }
    log_on_message << password << ' ';
  }
  else {
    // log_mode == lm_player
    // ||
    // log_mode == lm_watcher
    if (log_mode == lm_player) {
      log_on_message << STRING_PLAYER;
    }
    else { // log_mode == lm_watcher
      log_on_message << STRING_WATCHER;
    }
    log_on_message << ' ';
  }

  // send the name:
  log_on_message << logname << ends;
  used_name = logname;
  
  log_on_message.freeze(true);
  send_message(log_on_message.str());
  log_on_message.freeze(false);
}
  
  

// 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.
// CardWords_CommClient::state_changed() is a virtual function and should be
// overwritten because it does nothing useful, just prints to cout.
void
CardWords_CommClient::
state_changed(enum CardWords_CommClient::State_Change sc)
{
  cout << gettext("State changed:") << endl;
  
  switch (sc) {
    // The Connection is established and the server is waiting for our login:
  case SC_SERVER_RESPONDED_1ST_TIME:
    {
      int hand_capacity;
      bool output;
      cout << gettext("Received a 1st response from the server.")
           << endl
           << gettext("The charset consists of ")
           << CardWords_Char::noOfChars()
           << gettext(" chars.")
           << endl
           << gettext("The cardset consists of ")
           << get_pile()->occupied_places()
           << gettext(" cards.")
           << endl
           << gettext("The Hands can hold ")
           << (hand_capacity = get_bonus()->get_hand_capacity())
           << gettext(" cards.")
           << endl
           << gettext("A bonus is given for moves with the following number "\
                      "of cards:")
           << endl;
      output = false;
      for (int cards = 1; cards <= (int)hand_capacity; ++cards) {
        if ((*get_bonus())[cards] != 0) {
          cout << "   " << cards << gettext(" cards: ")
               << (*get_bonus())[cards] << gettext(" points.")
               << endl;
          output = true;
        }
      }
      if (output == false) {
        cout << gettext("   None.") << endl;
      }
      cout << gettext("Trading cards is worth the following points:") << endl;
      output = false;
      for (int cards = 0; cards >= -(int)hand_capacity; --cards) {
        if ((*get_bonus())[cards] != 0) {
          cout << "   " << -cards << gettext(" cards: ")
               << (*get_bonus())[cards] << gettext(" points.")
               << endl;
          output = true;
        }
      }
      if (output == false) {
        cout << gettext("   None. ") << endl;
      }
      cout << gettext("The table\'s dimension are: width: ")
           << get_table()->get_width()
           << gettext(", height: ")
           << get_table()->get_height()
           << "."
           << endl
           << gettext("The table\'s starting condition is: ");
      if ( ( get_table()->get_starting_pos().first
             < get_table()->get_width())
           && ( get_table()->get_starting_pos().second
                < get_table()->get_height())
           ) {
        cout << gettext("Start at field ")
             << get_table()->get_starting_pos()
             << "."
             << endl;
      }
      else {
        if ( get_table()->get_starting_pos().first
             < get_table()->get_width()) {
          cout << gettext("Starting column is ")
               << get_table()->get_starting_pos().first
               << "."
               << endl;
        }
        else {
          if ( get_table()->get_starting_pos().second
               < get_table()->get_height()) {
            cout << gettext("Starting row is ")
                 << get_table()->get_starting_pos().second
                 << "."
                 << endl;
          }
          else {
            cout << gettext("Start everywhere you like.")
                 << endl;
          }
        }
      }
    }
    break;
    // The default name is already in use, we try to use the alt_name:
  case SC_TRYING_ALT_NAME:
    cout << gettext("Someone already uses the name ")
         << get_name()
         << gettext(" on the server, thus trying ")
         << get_alt_name()
         << gettext(" now.")
         << endl;
    break;
    // We received the welcome message and have thus logged in:
  case SC_LOGGED_IN:
    cout << gettext("The server accepted us and we are logged in as ")
         << get_used_name()
         << endl;
    break;
    // We drew a new card:
  case SC_NEW_CARD:
    cout << gettext("We drew a new card, our hand reads now:")
         << endl << "   " << *get_hand() << endl;
    break;
    // Some info about the logged players may have changed:
  case SC_PLAYER_INFO:
    cout << gettext("Received info about the logged players:")
         << endl
         << "   " << get_playerlist()
         << "."
         << endl;
    break;
    // The server told us the number of cards left in the pile:
  case SC_REMAINING_CARDS:
    cout << gettext("There are ")
         << get_pile()->get_remaining_cards()
         << gettext(" cards left in the pile.")
         << endl;
    break;
  case SC_MESSAGE_BYE_RECEIVED:
    cout << gettext("The server tells us to go.")
         << endl;
    break;
  
  case SC_GAME_STARTED:
    cout << gettext("The game started and the order of the players is:")
         << "   " << get_playerlist()
         << "."
         << endl;
    break;
  case SC_ACTIVE_PLAYER:
    cout << gettext("It is the go of \"")
         << get_playerlist()->get_active_player_name()
         << gettext("\" now.")
         << endl;
    break;
  case SC_MY_GO:
    cout << gettext("It is *my* go now")
         << endl;
    break;
  case SC_MY_GO_FINISHED:
    cout << gettext("It is not my go any more")
         << endl;
    break;
  case SC_PLAYER_TRIES_MOVE:
    cout << gettext("Player \"")
	 << get_playerlist()->get_active_player_name()
	 << gettext("\" tries the move ")
	 << get_current_move()
	 << endl;
    break;
  case SC_PLAYER_GETS_POINTS:
    cout << gettext("Player \"")
	 << get_playerlist()->get_active_player_name()
	 << gettext("\" gets ")
	 << get_current_points()
	 << gettext(" for his move/trade/pass.")
	 << endl;
    break;
  case SC_MOVE_DONE:
    cout << gettext("The move ")
	 << get_current_move()
	 << gettext(" of player \"")
	 << get_playerlist()->get_active_player_name()
	 << gettext("\" has been approved by the server.")
	 << endl;
    break;
  case SC_PLAYER_PASSES:
    cout << gettext("Player ")
         << get_playerlist()->get_active_player_name()
         << gettext(" passes.")
         << endl;
    break;
  case SC_TRADE_ACCEPTED:
    cout << gettext("The server accepted our trade ")
         << current_trying_trade
         << endl;
    break;
  case SC_MOVE_WOULD_CREATE_UNKNOWN_WORD_AT:
    cout << gettext("The move ")
         << current_move
         << gettext(" would have created the side word ")
         << false_word
         << gettext(" at its ")
         << false_word_card_index
         << gettext("'th moved card which is unknown to the server.")
         << endl;
    break;
  case SC_MOVE_WOULD_CREATE_UNKNOWN_WORD:
    cout << gettext("The move ")
         << current_move
         << gettext(" would have created the word ")
         << false_word
         << gettext(" which is unknown to the server.")
         << endl;
    break;
  case SC_MOVE_NOT_ALLOWED:
    cout << gettext("The move ")
         << current_move
         << gettext(" is not allowed because it does not fit on the table "\
                    "or does not touch other words on the table or does "\
                    "not obeye the start condition.")
         << endl;
    break;
  case SC_PLAYER_MUST_PASS:
    cout << gettext("The server decides that ")
         << (my_go
             ? gettext("I")
             : get_playerlist()->get_active_player_name().c_str()
             )
         << gettext (" must pass.")
         << endl;
    break;
  case SC_NEW_WORD:
    cout << gettext("The word `")
         << get_last_new_word()
         << gettext("has been added to the dictionary.")
         << endl;
    break;
  case SC_GAME_OVER:
    cout << gettext("Game over.") << endl;
    break;
  }
}

// Test if the client is compatible with these charset, cardset, table
// and bonus definitions:
bool
CardWords_CommClient::test_client_compatible_with_server_settings()
{
  return true;
}


// set and read the home directory:
void
CardWords_CommClient::set_home_directory (const string & h_d)
{
  if (h_d.length() != 0) {
    home_directory = h_d;
  }
}

const string &
CardWords_CommClient::get_home_directory (void) const
{
  return home_directory;
}

// 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_CommClient::error (CardWords_CommClient::Error_Number)
{
  disconnect();
}

const CardWords_PlayerList *
CardWords_CommClient::get_playerlist(void) const
{
  return playerlist;
}
// returns the move that a player tries or just layed:
const CardWords_Move &
CardWords_CommClient::get_current_move(void) const
{
  return current_move;
}
// returns the points that a player got for his move or trade or pass:
int
CardWords_CommClient::get_current_points(void) const
{
  return current_points;
}
bool
CardWords_CommClient::is_my_go(void) const
{
  return my_go;
}

bool
CardWords_CommClient::am_i_trying_move(void) const
{
  return trying_move;
}

bool
CardWords_CommClient::am_i_trying_trade(void) const
{
  return trying_trade;
}

bool
CardWords_CommClient::am_i_trying_pass(void) const
{
  return trying_pass;
}

// easy functions for trying a move or a trade or pass:
void
CardWords_CommClient::try_move(const CardWords_Move & move)
{
  assert (is_my_go() == true);

  // check if every card is contained in the hand:
  assert (having_all_cards(move) == true); // Would be a bug if false.

  current_trying_move = move;
  trying_move = true;

  trying_pass = trying_trade = false;

  ostrstream request;
  request << REQUEST_MOVE << ' ' << move << ends;
  request.freeze(true);
  send_message(request.str());
  request.freeze(false);
} 
  
void
CardWords_CommClient::try_trade(const CardWords_CardSelection & trade)
{
  assert (is_my_go() == true);

  // check if every card is contained in the hand:
  assert (having_all_cards(trade) == true); // Would be a bug if false.
  
  current_trying_trade = trade;
  trying_trade = true;

  trying_pass = trying_move = false;
  
  ostrstream request;
  request << REQUEST_TRADE << ' ' << trade << ends;
  request.freeze(true);
  send_message(request.str());
  request.freeze(false);
} 

void
CardWords_CommClient::try_pass(void)
{
  assert (is_my_go() == true);

  trying_pass = true;

  trying_move = trying_trade = false;
  
  ostrstream request;
  request << REQUEST_PASS << ends;
  request.freeze(true);
  send_message(request.str());
  request.freeze(false);
}
void
CardWords_CommClient::add_word(const CardWords_String & word)
{
  assert (get_log_mode() == lm_owner_player
          || get_log_mode() == lm_owner_watcher);
  ostrstream request;
  request << REQUEST_ADD_WORD
          << ' '
          << word
          << endl << ends;
  request.freeze(true);
  send_message(request.str());
  request.freeze(false);
}
  
// this method returns true if there is a matching card in the hand for
// every card in the card descrtiption:
bool
CardWords_CommClient::
having_all_cards(const CardWords_CardSelection &card_selection)
{
  // Make sure all cards the player wants to move or trade are on his hand:
  CardWords_CardContainer1Dim temp_cardcontainer(card_selection.size());
  CardWords_Card * card;

  for (size_t index = 0; index < card_selection.size(); ++index) {
    CardWords_CardDescription card_desc(card_selection[index]);
    
    if (card_desc.is_wildcard()) {
      card_desc.set_meaning(CardWords_Char::blanko);
    }

    card = get_hand()->matching_card(card_desc);
    if (card == 0) {
      // A card is not contained
      temp_cardcontainer.clear(get_hand()); // Undo the temporary changes.
      return false;
    }

    // Remove the card just found so it can not be counted a second time:
    temp_cardcontainer.add_card(card);
  }

  // All cards have been found.
  temp_cardcontainer.clear(get_hand()); // Undo the temporary changes.
  return true;
}
  
const CardWords_String &
CardWords_CommClient::get_false_word(void) const
{
  return false_word;
}

size_t
CardWords_CommClient::get_false_word_card_index(void) const
{
  return false_word_card_index;
}
  
const CardWords_CardOccurrenceMap *
CardWords_CommClient::get_card_spec_map(void) const
{
  return card_spec_map;
}

const CardWords_CardOccurrenceMap *
CardWords_CommClient::get_possible_remaining_cards_map(void) const
{
  return possible_remaining_cards_map;
}

void
CardWords_CommClient::
remaining_card_diminished(const CardWords_CardDescription & desc)
{
  cout << gettext("1 occurrence of card ")
       << desc
       << gettext(" is no longer available.")
       << endl;
}
// When the player trades cards, they get back to the pile:
void
CardWords_CommClient::
remaining_card_increased(const CardWords_CardDescription & desc)
{
  cout << gettext("1 occurrence of card ")
       << desc
       << gettext(" is again available.")
       << endl;
}

const CardWords_String &
CardWords_CommClient::get_last_new_word(void) const throw()
{
  return last_new_word;
}

bool
CardWords_CommClient::is_game_running(void) const
{
  if (is_game_over()) {
    return false;
  }
  return (get_playerlist() != 0
          && get_playerlist()->is_game_running());
}
bool
CardWords_CommClient::is_game_over(void) const
{
  return game_over;
}
