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

#ifndef CARDWORDS_COMMCLIENT_HH
#define CARDWORDS_COMMCLIENT_HH

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

class CardWords_ClientPile;
class CardWords_CardTable;
class CardWords_BonusTable;
class CardWords_Hand;

#include "cardwords_baseclient.hh"
#include "cardwords_playerlist.hh"
#include "cardwords_move.hh"
#include "cardwords_cardoccurrencemap.hh"

class CardWords_CommClient : public CardWords_BaseClient {
public:
  // enums for all for possible modes to log into a cardwords_server:
  enum Log_Mode {lm_player, lm_watcher, lm_owner_player, lm_owner_watcher};

  // 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.
  enum Error_Number {
    // The server does not allow logs in the desired log mode:
    en_server_full,
    // The server uses a charset, cardset, table or bonus that
    // the client can't cope with:
    en_incompatible_server_settings,
    // No password file has been found to read the password for logging as
    // owner:
    en_no_password_file,
    // No home directory set:
    en_no_home_directory,
    // unable to log on because the name(s) are already in use:
    en_non_unique_name
  };
    
  CardWords_CommClient();
  virtual ~CardWords_CommClient();

  // 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
  set_name(string name,
           const string & alt_name = "");

  // return the names:
  const string &
  get_name(void) const;
  
  const string &
  get_alt_name(void) const;

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

  // 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.
  virtual int
  connect(void);

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

  // Set the desired log mode:
  void
  set_log_mode(CardWords_CommClient::Log_Mode lm);

  CardWords_CommClient::Log_Mode
  get_log_mode(void) const;

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

  const CardWords_ClientPile *
  get_pile(void) const;

  const CardWords_CardTable *
  get_table(void) const;
  
  const CardWords_BonusTable *
  get_bonus(void) const;

  const CardWords_Hand *
  get_hand(void) const;

  CardWords_Hand *
  get_hand(void);
  
  // Has the server send the "LOG_AS" message?
  bool
  has_received_log_as_message(void) const;

  // Has the server accepted the login?
  bool
  is_login_accepted(void) const;

  // Has the server rejected the login?
  bool
  is_login_rejected(void) const;

  // If yes, for what reason?
  bool
  is_server_full(void) const;
  bool
  is_non_unique_name(void) const;

  // Callback to a complete network message:
  virtual void
  received_complete_message(char message_type_indicator,
                                // see cardwords_server.hh,
                                // MESSAGE_*_INDICATOR,

                            const string & message);

  // enums for all valid changes of the state of the connection, the game etc:
  enum State_Change {
    // The Connection is established and the server is waiting for our login:
    SC_SERVER_RESPONDED_1ST_TIME,
    // The default name is already in use, we try to use the alt_name:
    SC_TRYING_ALT_NAME,
    // We received the welcome message and have thus logged in:
    SC_LOGGED_IN,
    // We drew a new card:
    SC_NEW_CARD,
    // Some info about the logged players may have changed:
    SC_PLAYER_INFO,
    // The server told us the number of cards left in the pile:
    SC_REMAINING_CARDS,
    // The server does not want us any more, we may have disregarded the
    // protocol or the server is full:
    SC_MESSAGE_BYE_RECEIVED,
    // The game started and we know the order of the players now:
    SC_GAME_STARTED,
    // The game is over:
    SC_GAME_OVER,
    // It is the next player's go:
    SC_ACTIVE_PLAYER,
    // It's my go, the user must react now:
    SC_MY_GO,
    // My go is over:
    SC_MY_GO_FINISHED,
    // The active player made the move he wishes to lay public. The move is
    // now accessible through this->get_current_move(). We are now waiting
    // for the approval of the move by the cardwords server. This will take
    // only a very short time. Perhaps in future I add the option that the
    // owner of the server checks the move. Then it would be nice, when all
    // the other clients know by now which move is just being checked.
    SC_PLAYER_TRIES_MOVE,
    // We get told about the points that the active player got in this go:
    SC_PLAYER_GETS_POINTS,
    // The active player's move has been approved:
    SC_MOVE_DONE,
    // The active player (his name get_playerlist()->get_active_player_name())
    // passes:
    SC_PLAYER_PASSES,
    // The server accepted our trade and is working on it:
    SC_TRADE_ACCEPTED,
    // This move would create an unknown sideword (stored in data member
    // false_word) at the (data member) false_word_card_index'th card (of the
    // move):
    SC_MOVE_WOULD_CREATE_UNKNOWN_WORD_AT,
    // This move's main word would create an unknown main word (stored in data
    // member false_word):
    SC_MOVE_WOULD_CREATE_UNKNOWN_WORD,
    // This move 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:
    SC_MOVE_NOT_ALLOWED,
    // The server can decide thatr the active player must pass. This can have
    // the reason that he tried an invalid move or uses too much time to
    // think. Or some other reason:
    SC_PLAYER_MUST_PASS,
    // The owner has added a word to the dictionary:
    SC_NEW_WORD
  };

  // 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.
  virtual void
  state_changed(State_Change);

  // Test if the client is compatible with these charset, cardset, table
  // and bonus definitions:
  virtual bool
  test_client_compatible_with_server_settings();

  // 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.
  virtual void
  error (CardWords_CommClient::Error_Number);

  // set and read the home directory:
  void
  set_home_directory (const string &); // called by the constructor
  const string &
  get_home_directory (void) const;

  // info about the other players in the game and the game itself::
  const CardWords_PlayerList *
  get_playerlist(void) const;

  bool
  is_game_running(void) const;

  bool
  is_game_over(void) const;

  size_t
  get_no_of_participants(void) const
    { return get_playerlist() ? get_playerlist()->size() : 0; }


  // returns the move that a player tries to lay or lays currently:
  const CardWords_Move &
  get_current_move(void) const;
  // returns the points that a player got for his move or trade or pass:
  int
  get_current_points(void) const;

  bool
  is_my_go(void) const;

  bool
  am_i_trying_move(void) const;

  bool
  am_i_trying_trade(void) const;

  bool
  am_i_trying_pass(void) const;
  // easy functions for trying a move or a trade or pass:
  void
  try_move(const CardWords_Move &);

  void
  try_trade(const CardWords_CardSelection &);

  void
  try_pass(void);

  void
  add_word(const CardWords_String &);

  const CardWords_String &
  get_false_word(void) const;
  size_t
  get_false_word_card_index(void) const;

  const CardWords_CardOccurrenceMap *
  get_card_spec_map(void) const;

  const CardWords_CardOccurrenceMap *
  get_possible_remaining_cards_map(void) const;

  const CardWords_String &
  get_last_new_word(void) const throw();
  
protected:
  // 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 derived classes to update displays showing
  // the remaining possible cards:
  virtual void
  remaining_card_diminished(const CardWords_CardDescription &);
  // When the player trades cards, they get back to the pile:
  virtual void
  remaining_card_increased(const CardWords_CardDescription &);
  
private:
  // static function to compare the meaning of the message:
  // returns true if messages are equal.
  static bool
  message_compare (const string & test_message,
                   const string & known_message);

  // this function takes either name or altname as argument
  // and tries to log the user on:
  void
  log_on (const string & logname);

  // this method returns true if there is a matching card in the hand for
  // every card in the card descrtiption:
  bool
  having_all_cards(const CardWords_CardSelection &);

private:
  
  CardWords_ClientPile  * pile;
  CardWords_CardTable      * table;
  CardWords_BonusTable * bonus;
  CardWords_Hand      * hand;
  
  // The name to log on with:
  string name;
  // This name is used if the first can not log into the server because
  // anotherone uses it already:
  string alt_name;
  // this is just a pointer-copy of one of the two above:
  string used_name;
  

  // store the state of the connection here:

  // Has the server send the "LOG_AS" message?
  bool received_log_as_message;

  // Has the server accepted the login?
  bool login_accepted;

  // Has the server rejected the login?
  bool login_rejected;

  // If yes, for what reason?
  bool server_full, non_unique_name;

  // The desired log mode:
  CardWords_CommClient::Log_Mode log_mode;

  // The home directory is saved here:
  string home_directory;

  // Data about the players is collected in this class:
  class CardWords_PlayerList * playerlist;

  CardWords_Move current_move;
  int          current_points;

  CardWords_Move          current_trying_move;
  CardWords_CardSelection current_trying_trade;

  bool my_go;

  bool trying_move;

  bool trying_trade;

  bool trying_pass;

  CardWords_String false_word;
  size_t         false_word_card_index;

  const CardWords_CardOccurrenceMap * card_spec_map;
  CardWords_CardOccurrenceMap * possible_remaining_cards_map;

  CardWords_String last_new_word;

  bool game_over;
};

#endif // CARDWORDS_COMMCLIENT_HH

