/* 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
*/

/* This file defines a class that inherits from CardWords_Selectible and
   CardWords_ChildProcess. Its purpose is to let the selector wait for
   communication possibilitywith the child.
*/

#ifndef CARDWORDS_SELECTIBLECHILD_HH
#define CARDWORDS_SELECTIBLECHILD_HH
#include "cardwords_childprocess.hh"
#include "cardwords_select.hh"
#include <deque>
#include "cardwords_exceptions.hh"

class CardWords_SelectibleChildLocker {
public:
  virtual void child_answers(const Machine_Char * answer) = 0;
};

class CardWords_SelectibleChild : public CardWords_Selectible,
                                  public CardWords_ChildProcess
{
public:
  CardWords_SelectibleChild (CardWords_Selector * selector,
                             const Machine_Char * progname,
                             bool need_to_copy = false);

  // ask_and_lock_child will try to lock this CardWords_SelectibleChild for
  // the access by a single CardWords_Selectible object. I will explain the
  // purpose for that locking:
  // First of all, this is *not* a multithreaded application, so we do not
  // need to bother with atomic locking mechanisms.
  // This CardWords_server receives however input from several different
  // network connections (sockets), and they all can request to ask dicbot a
  // question.
  // The requests are of course multiplexed via select(). But we have to give
  // a single requesting object the chance to ask dicbot a question and to
  // receive the *whole* answer from dicbot. This is not trivial as the byte
  // length of dicbot's answers are not predictable. So DicBot will send its
  // answers with a header stating how long this answer is, and the asking
  // object will have to read at least twice, one time the header, and the
  // second time the data of the answer as it knows now how long it is.
  // It is important that there is no other object trying to read the answer
  // to its request in the meantime, as it would only get the second part of
  // the answer to the first object.
  // lock_and_ask_child() takes a string to send and inserts the
  // CardWords_SelectibleChild object in the write queue of the selector.
  // After sending the string, it will insert itself in the read queue
  // and will forward the read events to the asking CardWords_Selectible
  // until this CardWords_Selectible object will release the lock on this
  // CardWords_Selectible_Child.
  // (The calling function does not know when the message will be send and
  // it does not need to know it.)
  void lock_and_ask_child (CardWords_SelectibleChildLocker * asking_object,
                           const Machine_Char * question);

private:
  // release the lock:
  void release_lock (CardWords_SelectibleChildLocker * asking_object);

  // Queueing requests that can not be executed immediately because another
  // Object already has a lock here:
  struct Queue_Element {
    
    CardWords_SelectibleChildLocker * selectible;
    
    const Machine_Char * question;
    
    Queue_Element(CardWords_SelectibleChildLocker * s,
                  const Machine_Char * q)
      {
        assert (s);assert(q);selectible = s;
        question = (const Machine_Char *) strdup((const char *)q);
        if (question == 0)
          throw CardWords_XLowResources(gettext("CardWords_SelectibleChild::"\
                                                "Queue_Element: "\
                                                "Out of memory"));
      }
    ~Queue_Element()
      {
        if (question != 0) free ((void *)question);
      }
    Queue_Element( const Queue_Element & qe)
      {
        selectible = qe.selectible;
        question = (const Machine_Char *) strdup((const char *)qe.question);
        if (question == 0)
          throw CardWords_XLowResources(gettext("CardWords_SelectibleChild::"\
                                                "Queue_Element: "\
                                                "Out of memory"));
    }
  };
  deque<Queue_Element> awaiting_lock_queue;
private:
  // 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 lock (CardWords_SelectibleChildLocker * asking_object,
             const Machine_Char * question);
  
public:
  // providing a method to be called by the selector:
  virtual void being_selected (int fd, int mode);

  virtual void timed_out (int fd, int mode);
  
private:
  // info about the current status  


  bool locked;
  bool sending_question; // Only valid if locked == true.
  bool receiving_answer; // Only valid if locked == true.
  size_t bytes_sent; // Only valid if locked == true and
  //                    sending_question == true. Used to determine how many
  //                    bytes of current_question have already been sent and 
  //                    how many are still waiting for transmission.
  const Machine_Char *current_question;

  size_t bytes_expected, bytes_received;
  bool receiving_header, receiving_body;
  Machine_Char *current_answer;
  

  CardWords_SelectibleChildLocker *locker; // The locking object.
  //                             Only valid if locked == true

  // The global selector:
  CardWords_Selector * selector;
};

#endif

