/*
  This file is part of CardWords.
  (C) 1999 Tobias Peters
  
  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_playerlist.cc

#include "cardwords_playerlist.hh"
#include "cardwords_server.hh" // for the message type strings...
#include <string> // replaces "cardwords_machinestring.hh"

CardWords_PlayerList::
CardWords_PlayerList (const string & logged_players_message)
  : active_player_pos(UINT_MAX), game_running(false)
{
  istrstream message (logged_players_message.c_str());

  // These 3 will contain the strings MESSAGE_LOGGED_PLAYERS, STRING_ROBOTS
  // and STRING_REMOTE_PARTICIPANTS as defined in cardwords_server.hh:
  string message_type;
  string robots;
  string remotes;

  // These will comtain the number of players of a given type:
  string number;
  size_t number_of_robots;
  size_t number_of_remotes;

  // The name of a player and how many cards and points he has:
  string         name;
  size_t         number_of_cards;
  string         q_cards; // contains STRING_CARDS
  int            points;
  string         q_points; // contains STRING_POINTS
    

  message >> message_type;
  assert(message_type == MESSAGE_LOGGED_PLAYERS);
  // should have been checked before calling this constructor.

  message >> number_of_robots >> robots;
  no_of_robots = number_of_robots;

  if (robots != STRING_ROBOTS) {
    // Bad message from the server
    throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                       STRING_ROBOTS);
  }

  size_t index;
  for (index = 0; index < number_of_robots; ++index) {
    message >> name >> number_of_cards >> q_cards >> points >> q_points;
    if (q_cards != STRING_CARDS) {
      // Bad message from the server
      throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                         STRING_CARDS);
    }
    if (q_points !=  STRING_POINTS) {
      // Bad message from the server
      throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                         STRING_POINTS);
    }
    players.push_back(Player(name.c_str(), true, number_of_cards, points));
  }
  assert (players.size() == no_of_robots);

  message >> number_of_remotes >> remotes;

  if (remotes != STRING_REMOTE_PLAYERS) {
    // Bad message from the server
    throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                       STRING_REMOTE_PLAYERS);
  }

  for (index = 0; index < number_of_remotes; ++index) {
    message >> name >> number_of_cards >> q_cards >> points >> q_points;
    if (q_cards != STRING_CARDS) {
      // Bad message from the server
      throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                         STRING_CARDS);
    }
    if (q_points != STRING_POINTS) {
      // Bad message from the server
      throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                         STRING_POINTS);
    }
    players.push_back(Player(name.c_str(), false, number_of_cards, points));
  }
  assert (players.size() == no_of_robots + number_of_remotes);
}


const string &
CardWords_PlayerList::get_name(size_t pos) const
{
  return players[pos].name;
}
  
size_t
CardWords_PlayerList::get_pos(const string & name) const
{
  size_t index;
  for (index = 0; index < players.size(); ++index){
    if (name == players[index].name) {
      return index;
    }
  }
  return UINT_MAX;
}

bool
CardWords_PlayerList::is_robot(size_t pos) const
{
  return players[pos].robot;
}

size_t
CardWords_PlayerList::get_no_of_cards_of(size_t pos) const
{
  return players[pos].no_of_cards;
}

bool
CardWords_PlayerList::is_game_running(void) const
{
  return game_running;
}

void
CardWords_PlayerList::game_starts(const string & message_game_starts)
{
  istrstream message (message_game_starts.c_str());

  // This will contain the string MESSAGE_GAME_STARTS:
  string message_type;
  
  // This will contain the names of the players in the order of the game:
  string name;
  
  size_t game_pos = 0;
  size_t old_pos = 0;

  message >> message_type;
  assert(message_type == MESSAGE_GAME_STARTS);
  // should have been checked before calling this constructor.

  if (is_game_running() == true) {
    // Game is already running, server tells jokes
    throw CardWords_XBadNetworkMessage(message_game_starts.c_str(),
                                       gettext("nothing, game is "\
                                               "already running"));
  }

  // read the names
  for (game_pos = 0; game_pos < players.size(); ++game_pos) {
    message >> name;
    old_pos = get_pos(name);
    if (old_pos > players.size()) {
      throw CardWords_XBadNetworkMessage(message_game_starts.c_str(),
                                         gettext("only valid player names"));
    }

    if (old_pos != game_pos) {
      // swap it to the right pos:
      assert (game_pos < old_pos);
      Player temp(players[game_pos]);
      players[game_pos] = players[old_pos];
      players[old_pos] = temp;
    }
  }
  game_running = true;
}

void
CardWords_PlayerList::update_info(const string & logged_players_message)
{
  // First use the constructor to parse the message:
  CardWords_PlayerList * new_info =
    new CardWords_PlayerList(logged_players_message);

  size_t new_index;
  size_t old_index;
  int    old_last_points;

  if (is_game_running() == true) {
    // When the next player becomes active, a logged_players_message is
    // send from the server to update the points and to tell if
    // a Player turned into a robot or the number of cards of a player
    // changed:
    if (size() != new_info->size()) {
      throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                         gettext("exactly the same number "\
                                                 "of names as before "\
                                                 "because the "\
                                                 "game is running"));
    }
    bool found_new_robot = false; // unused?
    // Check all players:
    for(new_index = 0; new_index < size(); ++new_index) {
      old_index = get_pos(new_info->get_name(new_index));
      if (old_index >= size()) {
        throw CardWords_XBadNetworkMessage(logged_players_message.c_str(),
                                           gettext("exactly the same "\
                                                   "names as before because "\
                                                   "the game is running"));
      }
      if (is_robot(old_index) == false
          && new_info->is_robot(new_index) == true) {
        assert (found_new_robot
                == false); // only one new robot at a time //FIXME!
        found_new_robot = true;
      }
      // update all infos, but preserve last_points which is not contained in
      // a logged players message:
      old_last_points = players[old_index].last_points;
      players[old_index] = new_info->players[new_index];
      players[old_index].last_points = old_last_points;
    }
  }
  else {
    // The game is not running yet.
    // Someone logged on or off.

    // Find new players:
    for (new_index = 0; new_index < new_info->size(); ++new_index) {
      old_index = get_pos(new_info->get_name(new_index));
      if (old_index < size()) {
        // We know this player already. However, update the information,
        // it may have changed:
        players[old_index] = new_info->players[new_index];
      }
      else {
        // This is a new player. Add it to the list:
        players.push_back(new_info->players[new_index]);
      }
    }
    // Remove players who have gone:
    for (old_index = 0; old_index < size(); ++old_index) {
      new_index = new_info->get_pos(get_name(old_index));
      if (new_index > new_info->size()) {
        // This player has gone: overwrite with the last player
        players[old_index] = players.back();
        players.pop_back();

        // Next, check the element just copied from the end to old_index:
        --old_index;
      }
    }
  }
  
  assert(size() == new_info->size());
  no_of_robots = new_info->get_no_of_robots();
  
  // Delete the CardWords_PlayerList that helped us:
  delete new_info;
}
    
size_t
CardWords_PlayerList::size(void) const // the number of players
{
  return players.size();
}

size_t
CardWords_PlayerList::get_no_of_robots(void) const
{
  return no_of_robots;
}

size_t
CardWords_PlayerList::get_active_player_pos(void) const
{
  return active_player_pos;
}

void
CardWords_PlayerList::set_active_player(const string & message_go_of)
{
  istrstream message(message_go_of.c_str());

  string message_type; // for the MESSAGE_GO_OF string

  string name; // name of active player

  message >> message_type >> name;
  assert (message_type == MESSAGE_GO_OF);

  active_player_pos = get_pos(name);
}

int
CardWords_PlayerList::get_points_of(size_t pos) const
{
  return players[pos].points;
}
    
ostream &
operator << (ostream &ostr, const CardWords_PlayerList &kpl)
{
  size_t index;
  for (index = 0; index < kpl.size(); ++index) {
    if (kpl.is_robot(index) == true) {
      ostr << gettext("Robot \"");
    }
    else {
      ostr << gettext("Remote Player \"");
    }
    ostr << kpl.get_name(index)
         << gettext("\" has ")
         << kpl.get_no_of_cards_of(index)
         << gettext(" cards and ")
         << kpl.get_points_of(index)
         << gettext(" points");
    if (index < kpl.size()-1) {
      ostr << "; ";
    }
  }
  return ostr;
}

void
CardWords_PlayerList::set_last_points_of(size_t pos, int last_points)
{
  assert(is_game_running() == true);
  players[pos].last_points = last_points;
}
int
CardWords_PlayerList::get_last_points_of(size_t pos) const
{
  return players[pos].last_points;
}
