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

#include "cardwords_selectiblechild.hh"
#include "cardwords_implication.hh"
#include "cardwords_server.hh"
#include "cardwords_dicbot.hh"
#include "cardwords_fixlennum.hh"

void CardWords_SelectibleChild::being_selected (int fd, int mode)
{
  assert (CardWords_ChildProcess::is_running());
  assert (locked == true);
  assert (implication(mode == READ_MODE,
                      receiving_answer
                      && fd == get_listen_fd()
                      && sending_question == false));
  assert (implication(mode == WRITE_MODE,
                      sending_question
                      && receiving_answer == false
                      && current_question != 0
                      && fd == get_talk_fd()
                      && (bytes_sent
                          < strlen((const char *)current_question)
                          )
                      )
          );
  switch (mode) {
  case READ_MODE:
    {
      int retval;
      if (receiving_header == false && receiving_body == false) {
        receiving_header = true;
        bytes_expected = DICBOT_ANSWER_DIGITS;
        bytes_received = 0;
        assert (current_answer == 0);
        current_answer = new Machine_Char [bytes_expected + 1];
        current_answer [bytes_expected] = (Machine_Char) '\0';
      }
      while((retval = read (fd,
                            current_answer + bytes_received,
                            bytes_expected - bytes_received)
             ) == -1
            && errno == EINTR);
      if (retval == -1) {
        switch (errno) {
        case EPIPE: // we lost the child ?
          assert (server_global.dicbot.is_running() == false);
          throw
            CardWords_XLowResources(gettext("CardWords_SelectibleChild: "\
                                          "being_selected(): "\
                                          "read() returned EPIPE"));
        case EAGAIN: // Would block ? Thats why we selected
        case EISDIR: case EBADF: case EINVAL: case EFAULT:
        default:
          throw
            CardWords_XInternal_Error(gettext("CardWords_SelectibleChild::"\
                                              "being_selected(): "\
                                              "Unable to read "\
                                              "from the child."));
        }
      }
      if (retval == 0) {
        // we lost the child
          assert (server_global.dicbot.is_running() == false);
          throw
            CardWords_XLowResources(gettext("CardWords_SelectibleChild: "\
                                          "being_selected(): "\
                                          "read() returned 0."));
        }
      bytes_received += retval;
    
      if ( bytes_received == bytes_expected ) {
        if (receiving_header) {
          assert (receiving_body == false);
          assert (bytes_expected == DICBOT_ANSWER_DIGITS);
          if (str2size(current_answer, & bytes_expected, DICBOT_ANSWER_DIGITS)
              == -1) {
            // Invalid DicBot answer header.
            throw
              CardWords_XInternal_Error(gettext("CardWords_SelectibleChild::"\
                                                "being_selected(): "\
                                                "Invalid DicBot "\
                                                "answer header."));
          }
          bytes_received = 0;
          delete [] current_answer;
          current_answer = new Machine_Char [bytes_expected + 1];
          current_answer [bytes_expected] = (Machine_Char) '\0';
          receiving_body = true;
          receiving_header = false;
        }
        else {
          assert (receiving_body == true);
          assert (current_answer != 0);
          receiving_body = false;
          bytes_received = bytes_expected = 0;

          locker->child_answers(current_answer);

          delete [] current_answer;
          current_answer = 0;
          release_lock(locker);
          receiving_body = false;
        }
      }
    }
    break;
  case WRITE_MODE:
    {
      size_t length = strlen((const char *)current_question);
      int retval =
	write(fd, current_question + bytes_sent, length - bytes_sent);
      if (retval == -1) {
	//*********error handling;
      }
      else {
	bytes_sent += retval;
	if (bytes_sent == length) {
	  selector->remove (this, get_talk_fd(), WRITE_MODE);
	  selector->add (this, get_listen_fd(), READ_MODE, DICBOT_TIMEOUT);
	  sending_question = false;
	  receiving_answer = true;
	}
      }
    }
    break;
  default:
    assert (0); // no exceptions requested
  }
}

void CardWords_SelectibleChild::timed_out (int fd, int mode)
{
  // timeout
  // Check if the child is still running, and if it is, update the timeout,
  // otherwise abort this program:
  
  if (is_running()) {
    cerr << gettext("Warning: Communication with DicBot timed out.\n");
    selector->add(this, fd, mode, time(0) + DICBOT_TIMEOUT);
  }
  else {
    throw CardWords_XLowResources(gettext("Childprocess DicBot died"));
  }
}

CardWords_SelectibleChild::
CardWords_SelectibleChild (CardWords_Selector * the_selector,
                         const Machine_Char * progname,
			 bool need_to_copy = false)
  : CardWords_Selectible(),
    CardWords_ChildProcess ((const char *)progname),
    locked(false), sending_question(false), receiving_answer(false),
    bytes_sent(0),
    current_question(0),
    bytes_expected(0),
    bytes_received(0),
    receiving_header(false),
    receiving_body(false),
    current_answer(0),
    locker(0),
    selector(the_selector)
{assert (selector != 0);}

void CardWords_SelectibleChild::
lock_and_ask_child (CardWords_SelectibleChildLocker * asking_object,
		    const Machine_Char * question)
{
  assert (CardWords_ChildProcess::is_running());
  if (locked == false) {
    assert(awaiting_lock_queue.empty());
    lock (asking_object, question);
  }
  else {
    awaiting_lock_queue.push_back(Queue_Element(asking_object, question));
  }
}

// release the lock:
void CardWords_SelectibleChild::
release_lock (CardWords_SelectibleChildLocker * asking_object)
{
  assert (CardWords_ChildProcess::is_running());
  assert (locked == true);
  assert (asking_object == locker);
  assert (sending_question == false);
  assert (receiving_answer == true);
  assert (current_question != 0);

  locked = false;
  free ((void *)current_question);
  current_question = 0;
  locker = 0;
  receiving_answer = false;

  selector->remove (this, get_listen_fd(), READ_MODE);

  if (awaiting_lock_queue.empty() == false) {
    lock(awaiting_lock_queue.front().selectible,
	 awaiting_lock_queue.front().question);
    awaiting_lock_queue.pop_front();
  }
}

// this function is called either directly by lock_and_ask_child if no other
// lock is present or by release_lock that gets the next lock from the queue:
void
CardWords_SelectibleChild::
lock (CardWords_SelectibleChildLocker * asking_object,
      const Machine_Char * question)
{
  assert (CardWords_ChildProcess::is_running());
  assert (locked == false);
  assert (asking_object != 0);
  assert (question != 0);
  locked = true;
  locker = asking_object;
  current_question = (const Machine_Char *)strdup((const char *)question);
  if (current_question == 0) {
    throw CardWords_XLowResources(gettext("CardWords_SelectibleChild::"\
					"lock: Out of memory"));
  } 
  receiving_answer = false;
  sending_question = true;
  bytes_sent = 0;
  selector->add((CardWords_Selectible *)this,
                get_talk_fd(),
                WRITE_MODE,
                time(0) + DICBOT_TIMEOUT);
}

