/* 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_stringconverters.hh"
#include "cardwords_game.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.length() != 0
                      && fd == get_talk_fd()
                      && bytes_sent < current_question.length()
                      )
          );
  assert (communication_stopped == false);

  switch (mode) {
  case READ_MODE:
    {
      int retval;
      if (receiving_header == false && receiving_body == false) {
        receiving_header = true;
        bytes_expected = DICBOT_ANSWER_DIGITS;
        assert (current_answer.length() == 0);
      }
      char * read_buffer =
        new char [bytes_expected - current_answer.length()];

      do {
        retval = read (fd,
                       read_buffer,
                       bytes_expected - current_answer.length());
      } while(retval == -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."));
      }
      current_answer.append(read_buffer, static_cast<size_t>(retval));
      delete [] read_buffer; read_buffer = 0;
      
      if ( current_answer.length() == bytes_expected ) {
        if (receiving_header) {
          assert (receiving_body == false);
          assert (bytes_expected == DICBOT_ANSWER_DIGITS);

          bytes_expected = string2unsigned(current_answer);
          current_answer.erase();

          receiving_body = true;
          receiving_header = false;
        }
        else {
          assert (receiving_body == true);

          locker->child_answers(current_answer);

          receiving_body = false;
          bytes_expected = 0;
          current_answer.erase();
          release_lock(locker);
        }
      }
    }
    break;
  case WRITE_MODE:
    {
      int retval =
	write(fd,
              current_question.c_str(),
              current_question.length());
      if (retval == -1) {
	//FIXME: error handling;
        assert (retval != -1);
      }
      else {
	current_question.erase(0U, static_cast<size_t>(retval));
	if (current_question.length() == 0) {
	  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 string & progname)
  : CardWords_Selectible(),
    CardWords_ChildProcess (progname),
    locked(false), sending_question(false), receiving_answer(false),
    communication_stopped(false),
    bytes_expected(0),
    receiving_header(false),
    receiving_body(false),
    locker(0),
    selector(the_selector)
{assert (selector != 0);}

void CardWords_SelectibleChild::
lock_and_ask_child (CardWords_SelectibleChildLocker * asking_object,
		    const string & question)
{
  assert (CardWords_ChildProcess::is_running());
  if (communication_stopped) {
    return;
  }

  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)
{
  if (communication_stopped) {
    return;
  }
  assert (CardWords_ChildProcess::is_running()
          || server_global.engin->is_game_over());
  assert (locked == true);
  assert (asking_object == locker);
  assert (sending_question == false);
  assert (receiving_answer == true);
  assert (current_question.length() == 0);

  locked = false;
  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 string & question)
{
  if (communication_stopped) {
    return;
  }
  assert (CardWords_ChildProcess::is_running());
  assert (locked == false);
  assert (asking_object != 0);
  assert (question.length() != 0);
  assert (current_question.length() == 0);

  locked = true;
  locker = asking_object;
  current_question = question;

  receiving_answer = false;
  sending_question = true;
  selector->add(this,
                get_talk_fd(),
                WRITE_MODE,
                time(0) + DICBOT_TIMEOUT);
}

void
CardWords_SelectibleChild::stop_communication(void)
  // Kill the child and deregister from the selector.
{
  if (locked == true) {
    if (sending_question == true) {
      selector->remove (this, get_talk_fd(), WRITE_MODE);
    }
    else {
      selector->remove (this, get_listen_fd(), READ_MODE);
    }
  }
  // don't close the fds here, or dicbot will die;

  //awaiting_lock_queue.erase(awaiting_lock_queue.begin(),
  //                          awaiting_lock_queue.end());
  awaiting_lock_queue.clear();
  communication_stopped = true;
}

