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

#include "cardwords_participant.hh"
#include "cardwords_game.hh"
#include "cardwords_server.hh"
#include "cardwords_stringconverters.hh"

CardWords_Participant::
CardWords_Participant (int set_fd, // the socket filedescriptor
                     //             -1 for robots.
                     CardWords_Selector * set_selector,
                     CardWords_Game * set_engin)
  :
  player            (set_fd == -1), // We will only know this by now when
  //                                   he is a robot. // bool
  owner             (false),        // bool

  password_required (false),        // bool
  password_approved (false),        // bool

  remote_participant(set_fd != -1), // bool
  robot             (set_fd == -1), // bool, always != remote_participant

  fd                (set_fd),       // the filedescriptor of the socket, int
  selector          (set_selector), // CardWords_Selector *
  
  registered_in_read_mode  (false), // bool
  registered_in_write_mode (false), // bool

// The numbers of received and answerded requests and the like
  received_requests (0U),            // size_t
  answered_requests (0U),            // size_t
  last_sent_info    (0U),            // size_t
  last_personal_info(0U),            // size_t
  
  dicbot_query_pending(false),       // bool

  receiving_request (false),         // bool
  receiving_header  (false),         // bool
  receiving_body    (false),         // bool
  bytes_expected    (0U),            // size_t

  // deque<Queue_Element> pending_messages_queue

  
  sending_request    (false),        // bool
  sending_MESSAGE_BYE(false),        // bool
  hand               (0),            // const CardWords_Hand *
  points             (0),            // int
  engin              (set_engin),    // CardWords_Game *
  my_go              (false)
{
  assert (selector != 0);
  assert (engin != 0);
  assert (fd >= -1);
  
  // tell the game engin about the new connection. It will send the first
  // message to the participant 
  if (engin->new_connection(this) == -1) {
    // don't wait for input, because this participant will only get personal
    // info that he can't log on
    // *this will autodestroy thereafter

    // indication of this state:
    engin = 0;
  }
  else if (is_remote_participant()) {
    // wait for input:
    selector->add(this, fd, READ_MODE, 0);
    registered_in_read_mode = true;
  }
}
    
CardWords_Participant::~CardWords_Participant()
{
  if (registered_in_read_mode) {
    selector->remove(this, fd, READ_MODE);
    registered_in_read_mode = false;
  }
  if (registered_in_write_mode) {
    selector->remove(this, fd, WRITE_MODE);
    registered_in_write_mode = false;
  }
  if (hand != 0) {
    hand->clear(engin->get_pile());
    delete hand;
  }
  if (engin != 0) {
    engin->deregister(this);
  }
  if (fd != -1) {
    close (fd);
  }
}

// This function gets called internally, after checking the queue or
// with a message from the queue:
void CardWords_Participant::
now_do_this_message (E_Message_Type message_type,
                     const string & new_message)
{
  assert (registered_in_write_mode == false);
  selector->add (this, fd, WRITE_MODE, time(0) + NETWORK_TIMEOUT);
  registered_in_write_mode = true;

  assert (new_message.length() != 0);
  try {
    switch (message_type) {
    case answer:
      message = ((char)MESSAGE_ANSWER_INDICATOR
                 + unsigned2string(++answered_requests,
                                   CSC_LEADING_ZEROES + CSC_STRICT_SIZE,
                                   MESSAGE_NUMBER_DIGITS)
                 );
      break;
    case info:
      message = ((char)MESSAGE_INFO_INDICATOR
                 + unsigned2string(++last_sent_info,
                                   CSC_LEADING_ZEROES + CSC_STRICT_SIZE,
                                   MESSAGE_NUMBER_DIGITS)
                 );
      break;
    case personal_info:
      message = ((char)MESSAGE_PERSONAL_INFO_INDICATOR  +
                 unsigned2string(++last_personal_info,
                                 CSC_LEADING_ZEROES + CSC_STRICT_SIZE,
                                 MESSAGE_NUMBER_DIGITS)
                 );
      // Check for the bye message.
      if (new_message == MESSAGE_BYE) {
        sending_MESSAGE_BYE = true;
        block_reading(); // Do not accept input any more.
      }
      break;
    default: assert (0);
    }
    message += (unsigned2string(new_message.length(),
                                CSC_LEADING_ZEROES + CSC_STRICT_SIZE,
                                MESSAGE_LENGTH_DIGITS)
                + new_message);
  }
  catch (CardWords_XOutOfRange xoor) {
    cerr << xoor << endl;
    throw
      CardWords_XInternal_Error (gettext("CardWords_Participant::"\
                                         "now_do_this_message: not enough "\
                                         "digits."));
  }
}

void  CardWords_Participant::send_message (E_Message_Type message_type,
                                           const string & new_message)
{
  if (registered_in_write_mode == false) {
    assert (pending_messages_queue.empty());
    now_do_this_message (message_type, new_message);
  }
  else {
    pending_messages_queue.push_back(Queue_Element(message_type,
                                                   new_message));
  }
}

void CardWords_Participant::being_selected (int fd, int mode)
{
  assert (fd == this->fd);
  switch (mode) {
  case WRITE_MODE:
    {
      assert (registered_in_write_mode == true);
      assert (message.length() != 0);
      int retval = write (fd,
                          message.c_str(),
                          message.length());
      if (retval == -1) {
        switch (errno) {
        case EINTR: // interupted, try again
          return;
        case EPIPE: // we lost the client
          robotize(); // metamorphose
          return;
        case EAGAIN: // Would block ? Thats why we selected
        case EBADF: case EINVAL: case EFAULT: case ENOSPC: default:
          throw
            CardWords_XInternal_Error(gettext("CardWords_Participant::being_"\
                                              "selected(): Unable to write "\
                                              "to the socket fd."));
        }
      }
      message.erase(0, retval);

      if (message.length() == 0) {
        selector->remove (this, fd, WRITE_MODE);
        registered_in_write_mode = false;

        if (sending_MESSAGE_BYE) {
          assert(registered_in_read_mode == false);
          delete this;
          return;
        }
        if (pending_messages_queue.size() > 0) {
          now_do_this_message (pending_messages_queue.front().message_type,
                               pending_messages_queue.front().message);
          pending_messages_queue.pop_front();
        }
      }
    }
    break;
  case READ_MODE:
    if (receiving_request == false) {
      assert (request.length() == 0);
      receiving_request = true;
      receiving_header = true;
      receiving_body = false;
      bytes_expected = REQUEST_HEADER_DIGITS;
    }
    {
      char * read_buffer = new char [bytes_expected];
      int retval = read (fd,
                         read_buffer,
                         bytes_expected);
      if (retval == -1) {
        switch (errno) {
        case EINTR: // interupted, try again
          delete [] read_buffer;
          return;
        case EPIPE: // we lost the client
          delete [] read_buffer;
          robotize(); // Metamorphose.
          return;
        case EAGAIN: // Would block ? Thats why we selected
        case EISDIR: case EBADF: case EINVAL: case EFAULT:
        default:
          delete [] read_buffer;
          throw
            CardWords_XInternal_Error(gettext("CardWords_Participant::being_"\
                                              "selected(): Unable to read "\
                                              "from the socket fd."));
        }
      }
      if (retval == 0) {
        // we lost the client
        delete [] read_buffer;
        robotize(); // Metamorphose.
        return;
      }
      request.append(read_buffer, static_cast<size_t>(retval));
      delete [] read_buffer;
      bytes_expected -= retval;
    }
    if (bytes_expected == 0) {
      if (receiving_header) {
        assert (receiving_body == false);
        assert (request.length() == REQUEST_HEADER_DIGITS);
        bytes_expected = string2unsigned(request);
        if (bytes_expected > REQUEST_MAX_LEN) {
          // Invalid request header. Close this connection.
          robotize(); // Metamorphose.
          return;
        }
        request.erase();
        receiving_body = true;
        receiving_header = false;
      }
      else {
        assert (receiving_body == true);
        assert (request.length() != 0);
        receiving_body = false;
        receiving_request = false;
        if (engin->request_from(request, this) == -1) {
          return;
        }
        request.erase();
      }
    }
    break;
  default:
    assert (0);
  }
}

void CardWords_Participant::child_answers(const string & answer)
{
  assert (this->is_robot());
  assert (my_go == true);
  assert (engin->whose_turn() == this);

  istrstream answer_stream (answer.c_str());
  string a1, a2;
  answer_stream >> a1 >> a2;
  if (a1 == "ERROR:") {
    if (a2 == "NO") {
      // ERROR: NO TABLE
      throw CardWords_XInternal_Error
        (gettext("CardWords_Participant::child_answers():: Dicbot returns "\
                 "ERROR indication as an anwer to a \"BEST MOVE\" question.")
         );
    }
    else {
      throw CardWords_XInternal_Error
        (gettext("CardWords_Participant::child_answers():: Dicbot returns "\
                 "unknown ERROR indication as an anwer to a \"BEST MOVE\" "\
                 "question.")
         );
    }
  }
  if (a1 == "NO") {
    if (a2 == "MOVE") {
      engin->pass(this);
      return;
    }
    else {
      goto UNKNOWN_ANSWER;
    }
  }
  if (a1 == "BEST" && a2 == "MOVE") {
    engin->move(this, answer_stream);
    return;
  }

 UNKNOWN_ANSWER:
  throw CardWords_XInternal_Error
    (gettext("CardWords_Participant::child_answers():: Dicbot returns "\
             "unknown answer to a \"BEST MOVE\" question.")
     );
}
      
void CardWords_Participant::timed_out (int fd, int mode)
{
  switch (mode) {
  case READ_MODE:
    registered_in_read_mode = false;
    break;
  case WRITE_MODE:
    registered_in_write_mode = false;
  default:
    assert (0);
  }
  if (mode == WRITE_MODE && is_player() && engin->is_game_running()) {
    robotize();
    return;
  }
  
  if (is_player() == false || engin->is_game_running() == false) {
    delete this;
    return;
  }
  if (fd == this->fd
      && is_player()
      && engin->whose_turn() == this
      && mode == READ_MODE) {
    engin->pass(this);
  }
  robotize();
}
void CardWords_Participant::robotize (void)
{
  assert (is_robot() == false);
  if (is_player() == false
      || engin->is_game_running() == false) {
    delete this;
    return;
  }
  
  robot = true;
  remote_participant = false;
  if (registered_in_read_mode) {
    receiving_request = receiving_header = receiving_body = false;
    selector->remove(this, fd, READ_MODE);
  }
  request.erase();
  if (registered_in_write_mode) {
    selector->remove(this, fd, WRITE_MODE);
  }
  message.erase();
  if (is_owner()) {
    unmake_owner();
  }
  engin->robotized(this);
}

void CardWords_Participant::unmake_owner (void)
{
  owner = false;
  // If the owner robotized, engin->robotized will set
  // engin->owner_is_present and owner accordingly.
}

void CardWords_Participant::set_name (const string & newname)
{
  name = newname;
}

// Don't listen to remote requests any more:
void  CardWords_Participant::block_reading (void)
{
  if (registered_in_read_mode) {
    selector->remove(this, fd, READ_MODE);
    registered_in_read_mode = false;
  }
}

// make_player: give him a hand
void CardWords_Participant::make_player(void)
{
  assert (engin->is_game_running() == false);
  assert (player == false || is_robot());
  assert (hand == 0);

  player = true;
  hand = new CardWords_Hand(engin->get_hand_capacity());
  refill_hand ();
}

void CardWords_Participant::refill_hand (void)
{
  CardWords_Card * card;
  while ((card = hand->draw_card(engin->get_pile())) != 0) {
    if (this->is_remote_participant()) {
      ostrstream new_card_message;
      new_card_message << MESSAGE_NEW_CARD << ' ' << card << ends;
      new_card_message.freeze(true);
      send_message(PERSONAL_MESSAGE,
                   new_card_message.str());
      new_card_message.freeze(false);
    }
  }
}

void CardWords_Participant::go (bool start_go = true)
{
  assert (this->is_player());
  if (start_go == true) {
    assert (my_go == false);
    my_go = true;
    assert (engin->whose_turn() == this);
    if (this->is_remote_participant()) {
      send_message(PERSONAL_MESSAGE, MESSAGE_YOUR_GO);
      // FIXME: set timeout for read
    }
    else { // robot: ask DicBot:
      assert (this->is_robot());
      ostrstream dicbot_question;
      dicbot_question << "GET BEST " << *hand << ends;
      dicbot_question.freeze(true);
      server_global.dicbot.
        lock_and_ask_child(this, dicbot_question.str());
      dicbot_question.freeze(false);
    }
  }
  else {
    assert (my_go == true);
    assert (engin->whose_turn() == this);
    my_go = false;
    if (this->is_remote_participant()) {
      send_message(PERSONAL_MESSAGE, MESSAGE_GO_FINISHED);
      // FIXME: if timeout set for read, unset it now.
    }
    else {
      //(nothing to do for a robot)
      assert (this->is_robot());
    }
  }
}
