/*
 * $Id: asynchat.cxx,v 4.1 2003/05/13 14:55:31 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  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, USA.
 * */

// -*- Mode: C++; tab-width: 4 -*-

#include "imview.hxx"

#ifdef HAVE_PTHREADS

#include "asynchat.hxx"

// string allocation issues:
//
// as much as possible, I want to leave this up to the string class
// itself.
//
// in general, when a user passes in a string object, it belongs to
// the user.  when the library gives a string to the user, the user
// should copy it.
//
// the only reasonable exception seems to be with the more() method -
// these are understood to be temporary strings, and are deleted by
// the library.

// wait, we want the buffer sizes to be overridable.
const unsigned int    async_chat::ac_out_buffer_size = 512;
const unsigned int    async_chat::ac_in_buffer_size = 512;
const string async_chat::null_terminator = string("");

void async_chat::set_terminator (const string & t)
{
  terminator = t;
}

string& async_chat::get_terminator (void)
{
  return terminator;
}

inline int async_chat::find_prefix_at_end (string haystack, string needle)
{
  int hl = haystack.length();
  int nl = needle.length();

  for (int i = trivmax (nl-hl, 0); i < nl; i++) {
#if (defined(__GNUC__) && (__GNUC__ < 3)) // quirky old gcc
	  if (haystack.compare(needle, hl-(nl-i), nl-i) == 0) {
		  return (nl-i);
	  }
#else //most C++ compilers these days
	  if (haystack.compare (0, string::npos, needle, hl-(nl-i), nl-i) == 0) {
		  return (nl-i);
	  }
#endif
  }
  return 0;
}


void async_chat::handle_read (void)
{
  char buffer[ac_in_buffer_size];
  int result = recv (buffer, ac_in_buffer_size);

  if (result > 0) {
	ac_in_buffer.append (buffer, result);

	// Continue to search for self.terminator in self.ac_in_buffer,
	// while calling self.collect_incoming_data.  The while loop is
	// necessary because we might read several data+terminator combos
	// with a single recv().
	
	while (ac_in_buffer.length()) {
	  string terminator = get_terminator();

	  // special case where we're not using a terminator
	  if (terminator == null_terminator) {
		collect_incoming_data (ac_in_buffer);
		ac_in_buffer.erase ();
		return;
	  }

	  int terminator_len = terminator.length();
	  
	  int index = ac_in_buffer.find (terminator);
	  
	  // 3 cases:
	  // 1) end of buffer matches terminator exactly:
	  //    collect data, transition
	  // 2) end of buffer matches some prefix:
	  //    collect data to the prefix
	  // 3) end of buffer does not match any prefix:
	  //    collect data
	  
	  if (index != -1) {
		// we found the terminator
		collect_incoming_data (ac_in_buffer.substr (0, index));
		ac_in_buffer.erase (0, index + terminator_len);
		found_terminator();
	  } else {
		// check for a prefix of the terminator
		int num = find_prefix_at_end (ac_in_buffer, terminator);
		if (num) {
		  int bl = ac_in_buffer.length();
		  // we found a prefix, collect up to the prefix
		  collect_incoming_data (ac_in_buffer.substr (0, bl - num));
		  ac_in_buffer.erase (0, bl - num);
		  break;
		} else {
		  // no prefix, collect it all
		  collect_incoming_data (ac_in_buffer);
		  ac_in_buffer.erase();
		}
	  }
	}
  }
}

void async_chat::handle_write (void)
{
  srv_internal_dbgprintf("%d: handle_write", fileno);
  initiate_send();
}

void async_chat::send (const string& data)
{
  producer_fifo.push ((producer *) new simple_producer (data));
}

void async_chat::send (producer* p)
{
  producer_fifo.push (p);
}

bool async_chat::readable (void)
{
	srv_internal_dbgprintf("async_chat::readable() buffer.length(): %d", ac_in_buffer.length());
	return (dispatcher::readable() && (ac_in_buffer.length() < ac_in_buffer_size));
}

bool async_chat::writable (void)
{
  return (ac_out_buffer.length() || producer_fifo.size());
}

// refill the outgoing buffer by calling the more() method the first
// producer in the queue

void async_chat::refill_buffer (void)
{
	while (1) {
		if (producer_fifo.size()) {
			producer * p = producer_fifo.front();
			srv_internal_dbgprintf("popped producer %p\n", (void*) p);
			if (!p) {
				// a NULL producer is a sentinel, telling us to close the channel
				if (!ac_out_buffer.length()) {
					srv_internal_dbgprintf("%d: closing because of NULL in producer fifo\n", fileno);
					producer_fifo.pop();
					this->handle_close();
					close();
				} else {
				    delete p;  // << what does this do? delete a NULL pointer?
				}
				return;
			}
			string* data = p->more();
			if (data->length()) {
				ac_out_buffer.append (*data);
				delete data;
				return;
			} else {
				srv_internal_dbgprintf("popping fifo\n");
				producer_fifo.pop();
				delete p; // need to do that Hugues Talbot	 2 Mar 2001
				delete data;
			}
		} else {
			return;
		}
	}
}

// leaving out the 'more_to_send()' stuff, I think it is now superfluous.
void async_chat::initiate_send (void)
{
	if (ac_out_buffer.length() < ac_out_buffer_size) {
		// try to refill the buffer
		refill_buffer();
	}
	if (ac_out_buffer.length() && connected) {
		srv_internal_dbgprintf("%d: sending %d bytes\n", fileno, ac_out_buffer.length());
		int num_sent = dispatcher::send (ac_out_buffer.data(), ac_out_buffer.length());
		if (num_sent) {
			ac_out_buffer.erase (0, num_sent);
		}
	}
}


#endif // HAVE_PTHREADS
