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

#include "cardwords_baseclient.hh"
#include <assert.h>
#include "cardwords_server.hh"
#include "cardwords_stringconverters.hh"

// The class CardWords_BaseClient can serve in any cardwords client as the
// communication unit. It sends messages to the cardwords_server over a tcp
// network and receives messages from the server.
// It is, however, an abstract base class. Every different client
// might have a different method of notifiyng the CardWords_BaseClient that
// their is data pending on the network connection or that it is possible to
// write data.
// Some widget sets for Xwindows have their own main loop that includes a
// select(2) call. These have their own function call interface to notify
// about the io possibility.
// Please look at CardWords_GtkClient for an example on how to use a widget
// set's main loop.

// Use the following aproach:
// The BaseClient will always be registered for reading from the socket fd
// from the moment of establishing the connection on. Every message from the
// server will be received this way.
// The BaseClient will be registered for writing only if it has a message to
// send. After sending the message is finished, it deregisters from writing.



CardWords_BaseClient::CardWords_BaseClient()
  :
  registered_for_reading (false),
  registered_for_writing (false),

  receiving_header       (false),
  receiving_body         (false),
  bytes_expected         (0)
{
}  

CardWords_BaseClient::~CardWords_BaseClient()
{
  if (is_connected()) {
    disconnect();
  }
}

// Functions for registering this object's fd in the main select(2) call.
void
CardWords_BaseClient::register_reading(void)
{
  assert(is_registered_for_reading() == false);
  assert(is_connected() == true);
  registered_for_reading = true;

  receiving_header = true;
  receiving_body   = false;
  bytes_expected   = MESSAGE_HEADER_LENGTH;
}

void
CardWords_BaseClient::register_writing(void)
{
  assert(is_registered_for_writing() == false);
  registered_for_writing = true;
}

void
CardWords_BaseClient::deregister_reading(void)
{
  assert(is_registered_for_reading() == true);
  registered_for_reading = false;
  assert(received_message.length() == 0);
}

void
CardWords_BaseClient::deregister_writing(void)
{
  assert(is_registered_for_writing() == true);

  // If a message is still to be sent, this is an error.
  // If you want to discard all pending messages, use discard_messages prior
  // to deregister_writing()
  assert(message_to_send.length() == 0);
  registered_for_writing = false;
}

// The current registered status:
bool
CardWords_BaseClient::is_registered_for_reading(void)
{
  return registered_for_reading;
}
bool
CardWords_BaseClient::is_registered_for_writing(void)
{
  return registered_for_writing;
}

// This function stops pending communication with the server immediately.
// Should only be used when the connection breaks.
void
CardWords_BaseClient::discard_messages(void)
{
  message_to_send.erase();
  received_message.erase();
  bytes_expected = 0;
}

void
CardWords_BaseClient::send_message(const string & message)
{
  assert ((message_to_send.length() != 0)
          ==
          (is_registered_for_writing() == true)
          );

  // Find out the length of the message. Generate the message header
  // and concatenate the header and the message. If another message is also
  // currently being sent, then concatenate these two messages. Register
  // for writing if not yet done.

  // insert the length of the new message as a number consisting of
  // REQUEST_HEADER_DIGITS digits:
  message_to_send +=
    unsigned2string(message.length(),
                    CSC_LEADING_ZEROES + CSC_STRICT_SIZE,
                    REQUEST_HEADER_DIGITS);

  // append the new message:
  message_to_send += message;

  if (is_registered_for_writing() == false) {
    register_writing();
  }
}

// Call this method when writing is possible:
void
CardWords_BaseClient::send_now(void)
{
  assert(is_registered_for_writing() == true);
  
  int bytes_sent = write (get_fd(),
                          message_to_send.c_str(),
                          message_to_send.length());
  
  if (bytes_sent == -1) {
    switch (errno) {
    case EINTR: // interupted, try again
      return;
    case EPIPE: // we lost the connection
      disconnect(); // close the connection
      return;
      
    case EAGAIN: // Would block ? Thats why we selected
    case EBADF: case EINVAL: case EFAULT: case ENOSPC: default:
      throw
        CardWords_XInternal_Error(gettext("CardWords_BaseClient::send_now(): "\
                                          "Unable to write to "\
                                          "the socked fd."));
        }
  }

  message_to_send.erase(0U, static_cast<size_t>(bytes_sent));
  if (message_to_send.length() == 0) {
    deregister_writing();
  }
}

// Call this method when reading is possible:
void
CardWords_BaseClient::read_now(void)
{
  assert (is_registered_for_reading() == true);
  assert (bytes_expected > received_message.length());

  char * read_buffer = new char [bytes_expected - received_message.length()];

  int bytes_read = read (get_fd(),
                         read_buffer,
                         bytes_expected - received_message.length());

  if (bytes_read == -1) {
    switch (errno) {
    case EINTR: // interupted, try again
      delete [] read_buffer;
      return;
    case EPIPE: // we lost the client
      disconnect();
      delete [] read_buffer;
      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_BaseClient::read_"\
                                          "now(): Unable to read from "\
                                          "the socked fd."));
    }
    // not reached
  }
  if (bytes_read == 0) {
    // we lost the client
    disconnect();
    return;
  }

  received_message.append(read_buffer, static_cast<size_t>(bytes_read));
  delete [] read_buffer;

  if ( received_message.length() == bytes_expected ) {
    if (receiving_header) {
      assert (receiving_body == false);
      assert (bytes_expected == MESSAGE_HEADER_LENGTH);
      message_type = received_message[0];

#ifdef DEBUG_NETWORK_TRAFFIC
      if (message_type != MESSAGE_ANSWER_INDICATOR
          && message_type != MESSAGE_INFO_INDICATOR
          && message_type != MESSAGE_PERSONAL_INFO_INDICATOR) {
        cerr << "ERROR: network message contains garbage. Last message was:"
             << endl
             << last_received_message
             << endl;
        assert(0); // abort
      }
#endif
      // read how long the next message is:
      size_t message_body_length = 
        string2unsigned(received_message.substr(1U+MESSAGE_NUMBER_DIGITS,
                                                MESSAGE_LENGTH_DIGITS));

      received_message.erase();

      bytes_expected   = message_body_length;

      receiving_body   = true;
      receiving_header = false;
    }
    else {
      assert (receiving_body ==true);
      
      received_complete_message(message_type, received_message);

#ifdef DEBUG_NETWORK_TRAFFIC
      last_received_message = received_message;
#endif

      receiving_body   = false;
      receiving_header = true;
      received_message.erase();

      bytes_expected   = MESSAGE_HEADER_LENGTH;
    }
  }
}
// This ^ will call the following virtual method when a complete
// message has been received:

// Overwrite this method with your callback to a complete network
// message:
void
CardWords_BaseClient::
received_complete_message(char message_type_indicator,
                                // see file cardwords_server.hh,
                                // MESSAGE_*_INDICATOR -defines.
                 
                          const string & message)
{
  cout << "Received message of type " << message_type_indicator << ":"
       << endl
       << message
       << endl;
}
void CardWords_BaseClient::disconnect(void)
{
  if (is_connected() == true) {
    discard_messages();
    if (is_registered_for_reading() == true) {
      deregister_reading();
    }
    if (is_registered_for_writing() == true) {
      deregister_writing();
    }
  }
  CardWords_TCPClient::disconnect();
}
