/* 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_fixlennum.hh"
#include "cardwords_machinestring.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
  name              (0),            // Machine_Char *

  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
  bytes_received    (0U),            // size_t
  request           (0),             // Machine_Char *

  // deque<Queue_Element> pending_messages_queue

  
  sending_request   (false),         // bool
  bytes_to_send     (0U),            // size_t
  bytes_sent        (0U),            // size_t
  message           (0),             // Machine_Char *

  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 (request != 0) {
    delete [] request;
  }
  if (message != 0) {
    delete [] message;
  }
  if (fd != -1) {
    close (fd);
  }
  if (name != 0) {
    free (name);
  }
}

// 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 Machine_Char *new_message)
{
  assert (registered_in_write_mode == false);
  selector->add (this, fd, WRITE_MODE, time(0) + NETWORK_TIMEOUT);
  registered_in_write_mode = true;

  assert (message == 0);
  assert (new_message != 0);
  assert (bytes_to_send == 0);
  assert (bytes_sent == 0);
  bytes_to_send = MESSAGE_HEADER_LENGTH + strlen((const char *)new_message);
  message = new Machine_Char [bytes_to_send + 1];
  switch (message_type) {
  case answer:
    message[0] = MESSAGE_ANSWER_INDICATOR;
    if (size2str(++answered_requests, message + 1, MESSAGE_NUMBER_DIGITS)
        == -1) {
      throw
        CardWords_XInternal_Error (gettext("CardWords_Participant::"\
                                         "now_do_this_message: not enough "\
                                         "MESSAGE_NUMBER_DIGITS for "\
                                         "answered_requests."));
    }
    break;
  case info:
    message[0] = MESSAGE_INFO_INDICATOR;
    if (size2str(++last_sent_info, message + 1, MESSAGE_NUMBER_DIGITS)
        == -1) {
      throw
        CardWords_XInternal_Error (gettext("CardWords_Participant::"\
                                         "now_do_this_message: not enough "\
                                         "MESSAGE_NUMBER_DIGITS for "\
                                         "last_sent_info."));
    }
    break;
  case personal_info:
    message[0] = MESSAGE_PERSONAL_INFO_INDICATOR;
    if (size2str(++last_personal_info, message + 1, MESSAGE_NUMBER_DIGITS)
        == -1) {
      throw
        CardWords_XInternal_Error (gettext("CardWords_Participant::"\
                                         "now_do_this_message: not enough "\
                                         "MESSAGE_NUMBER_DIGITS for "\
                                         "last_personal_info."));
    }
    break;
  default: assert (0);
  }
  size2str(bytes_to_send - MESSAGE_HEADER_LENGTH,
              message + 1 + MESSAGE_NUMBER_DIGITS,
              MESSAGE_LENGTH_DIGITS);
  strcpy ((char *)(message + MESSAGE_HEADER_LENGTH), (char *)new_message);
}

void  CardWords_Participant::send_message (E_Message_Type message_type,
                   const Machine_Char *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 (bytes_to_send > 0);
      assert (message != 0);
      int retval = write (fd,
                          message + bytes_sent,
                          bytes_to_send - bytes_sent);
      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."));
        }
      }
      bytes_sent += retval;
      if (bytes_sent == bytes_to_send) {
        bytes_sent = bytes_to_send = 0;
        selector->remove (this, fd, WRITE_MODE);
        registered_in_write_mode = false;
        bool finish = (message[0] == MESSAGE_PERSONAL_INFO_INDICATOR
                       &&
                       strcmp ((const char *)MESSAGE_BYE,
                               (const char *)(message +
                                              MESSAGE_HEADER_LENGTH)) == 0);
        delete [] message;
        message = 0;
        if (finish == true) {
          delete this; // selfdestruct. The game said bye, so no metamorphose.
          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 == 0);
      receiving_request = true;
      receiving_header = true;
      receiving_body = false;
      bytes_expected = REQUEST_HEADER_DIGITS;
      bytes_received = 0;
      request = new Machine_Char [REQUEST_HEADER_DIGITS + 1];
      request [REQUEST_HEADER_DIGITS] = (Machine_Char) '\0';
    }
    {
      int retval = read (fd,
                         request + bytes_received,
                         bytes_expected - bytes_received);
      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 EISDIR: case EBADF: case EINVAL: case EFAULT:
        default:
          throw
            CardWords_XInternal_Error(gettext("CardWords_Participant::being_"\
                                              "selected(): Unable to read "\
                                              "from the socket fd."));
        }
      }
      if (retval == 0) {
        // we lost the client
        robotize(); // Metamorphose.
        return;
      }
      bytes_received += retval;
    }
    if ( bytes_received == bytes_expected ) {
      if (receiving_header) {
        assert (receiving_body == false);
        assert (bytes_expected == REQUEST_HEADER_DIGITS);
        if (str2size(request, & bytes_expected, REQUEST_HEADER_DIGITS)
            == -1
            || bytes_expected > REQUEST_MAX_LEN) {
          // Invalid request header. Close this connection.
          robotize(); // Metamorphose.
          return;
        }
        bytes_received = 0;
        delete [] request;
        request = new Machine_Char [bytes_expected + 1];
        request [bytes_expected] = (Machine_Char) '\0';
        receiving_body = true;
        receiving_header = false;
      }
      else {
        assert (receiving_body == true);
        assert (request != 0);
        receiving_body = false;
        receiving_request = false;
        bytes_received = bytes_expected = 0;
        if (engin->request_from(request, this) == -1) {
          return;
        }
        delete [] request;
        request = 0;
      }
    }
    break;
  default:
    assert (0);
  }
}

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

  istrstream answer_stream ((const char *)answer);
  Machine_String a1, a2;
  answer_stream >> a1 >> a2;
  if (strcmp(a1,"ERROR:") == 0) {
    if (strcmp (a2,"NO") == 0) {
      // 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 (strcmp(a1,"NO") == 0) {
    if (strcmp(a2, "MOVE") == 0) {
      engin->pass(this);
      return;
    }
    else {
      goto UNKNOWN_ANSWER;
    }
  }
  if (strcmp(a1,"BEST") == 0 && strcmp(a2,"MOVE") == 0) {
    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);
  }
  delete [] request;
  request = 0;
  if (registered_in_write_mode) {
    selector->remove(this, fd, WRITE_MODE);
  }
  delete [] message;
  message = 0;
  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 Machine_Char * newname)
{
  if ( name != 0) {
    free (name);
  }
  name = (Machine_Char *) strdup ((const char *) 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,
                   (const Machine_Char *)new_card_message.str());
    }
  }
}

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,
                           (const Machine_Char *) 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());
    }
  }
}
