/* fd.cc - function definitions for fd.hh   -*- C++ -*-
 * Copyright 2003-2005 Bas Wijnen <wijnen@debian.org>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "fd.hh"
#include "error.hh"
#include <unistd.h>
#include <fcntl.h>
#include "debug.hh"
#include <sys/poll.h>

#define hack_disconnect(x) do { if (x.rep_) x.rep_->call_ = 0; } while (0)

namespace shevek
{
  std::string fd::s_junkbuffer;

  void fd::read_custom (read_custom_t cb)
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    m_read_custom = cb;
    l_connect (m_iocondition | Glib::IO_IN | Glib::IO_PRI);
  }

  void fd::read_priority_custom (read_custom_t cb)
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    m_read_priority_custom = cb;
    l_connect (m_iocondition | Glib::IO_PRI);
  }

  void fd::read (read_t cb)
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    m_flush = flush_t ();
    hack_disconnect (m_flush);
    m_read_custom = read_custom_t ();
    hack_disconnect (m_read_custom);
    m_read = cb;
    if (m_readbuffer.empty ())
      l_connect (m_iocondition | Glib::IO_IN | Glib::IO_PRI);
    else
      {
	l_connect (m_iocondition | Glib::IO_PRI);
	if (!m_idle.connected ())
	  {
	    m_idle = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle));
	    m_keepalive_helper_idle = refptr_this <fd> ();
	  }
	return;
      }
  }

  void fd::read_priority (read_t cb)
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    m_flush = flush_t ();
    hack_disconnect (m_flush);
    m_read_priority_custom = read_custom_t ();
    hack_disconnect (m_read_priority_custom);
    m_read_priority = cb;
    l_connect (m_iocondition | Glib::IO_PRI);
  }

  void fd::read_lines (read_lines_t cb)
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    m_flush = flush_t ();
    hack_disconnect (m_flush);
    m_read_custom = read_custom_t ();
    hack_disconnect (m_read_custom);
    m_read = sigc::mem_fun (*this, &fd::l_read_lines);
    m_read_lines = cb;
    if (m_readbuffer.empty ())
      l_connect (m_iocondition | Glib::IO_IN | Glib::IO_PRI);
    else
      {
	l_connect (m_iocondition | Glib::IO_PRI);
	if (!m_idle.connected ())
	  {
	    m_idle = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle));
	    m_keepalive_helper_idle = refptr_this <fd> ();
	  }
	return;
      }
  }

  void fd::l_unread ()
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    m_read = read_t ();
    hack_disconnect (m_read);
    m_read_priority = read_t ();
    hack_disconnect (m_read_priority);
    m_read_custom = read_custom_t ();
    hack_disconnect (m_read_custom);
    m_read_priority_custom = read_custom_t ();
    hack_disconnect (m_read_priority_custom);
    if (m_idle.connected ())
      {
        m_idle.disconnect ();
	m_keepalive_helper_idle.reset ();
      }
    if (m_idle_priority.connected ())
      {
        m_idle_priority.disconnect ();
	m_keepalive_helper_idle_priority.reset ();
      }
    flush_t cb = m_flush;
    m_flush = flush_t ();
    hack_disconnect (m_flush);
    m_flushing = false;
    if (cb)
      cb ();
  }

  void fd::unread (bool flush_buffer, flush_t cb)
  {
    startfunc;
    l_connect (m_iocondition & ~(Glib::IO_IN | Glib::IO_PRI));
    if (!flush_buffer)
      {
	l_unread ();
	return;
      }
    m_flush = cb;
    if (!m_idle.connected () && !m_idle_priority.connected ())
      l_unread ();
    else
      m_flushing = true;
  }

  void fd::write (std::string const &data, write_done_t cb)
  {
    startfunc;
    // add an extra artificial reference to the object, to prevent
    // deletion from the filter (that would cause a crash otherwise)
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    std::string the_data (data);
    // first filter it, if there is a filter.  If the filter deletes the
    // object, deletion is delayed until the function exits.
    if (m_out_filter)
      m_out_filter (the_data);
    // add a new entry to the list only if we cannot use the last one
    if (m_writebuffer.empty () || !m_writebuffer.back ().done)
      {
	m_writebuffer.push_back (write_t ());
      }
    m_writebuffer.back ().data += the_data;
    l_connect (m_iocondition | Glib::IO_OUT);
    m_writebuffer.back ().done = cb;
  }

  void fd::write_raw (std::string const &data, write_done_t cb)
  {
    startfunc;
    // add an extra artificial reference to the object, to prevent
    // deletion from the filter (that would cause a crash otherwise)
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    std::string the_data (data);
    // add a new entry to the list only if we cannot use the last one
    if (m_writebuffer.empty () || !m_writebuffer.back ().done)
      {
	m_writebuffer.push_back (write_t ());
      }
    m_writebuffer.back ().data += the_data;
    l_connect (m_iocondition | Glib::IO_OUT);
    m_writebuffer.back ().done = cb;
  }

  bool fd::write_block (relative_time timeout)
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    relative_time zero;
    bool use_timeout = timeout > zero;
    int fixed_timeout_value = timeout == zero ? 0 : -1;
    // Use an explicit constructor to prevent a system call.
    // The system call is only needed if there is indeed a (nonzero) timeout.
    absolute_time t (0, 0);
    if (use_timeout)
      t = absolute_time () + timeout;
    while (!m_writebuffer.empty ())
      {
	int tm;
	if (use_timeout)
	  {
	    relative_time to = t - absolute_time ();
	    if (to < relative_time ())
	      {
		return false;
	      }
	    tm = to.total () * 1000 + to.nanoseconds () / 1000000;
	  }
	else
	  tm = fixed_timeout_value;
	struct pollfd pfd;
	pfd.events = POLLOUT;
	pfd.revents = 0;
	pfd.fd = m_fd;
	int ret = ::poll (&pfd, 1, tm);
	if (ret == -1 && errno == EAGAIN)
	  continue;
	if (ret != 1 || !(pfd.revents & POLLOUT))
	  {
	    return false;
	  }
	l_write ();
	if (m_fd < 0)
	  {
	    return false;
	  }
      }
    return true;
  }

  std::string &fd::read_block (relative_time timeout)
  {
    startfunc;
    if (!m_priority_readbuffer.empty ())
      {
	return m_priority_readbuffer;
      }
    if (!m_readbuffer.empty ())
      {
	return m_readbuffer;
      }
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    relative_time zero;
    bool use_timeout = timeout > zero;
    int fixed_timeout_value = timeout == zero ? 0 : -1;
    // Use an explicit constructor to prevent a system call.
    // The system call is only needed if there is indeed a (nonzero) timeout.
    absolute_time t (0, 0);
    if (use_timeout)
      t = absolute_time () + timeout;
    while (true)
      {
	int tm;
	if (use_timeout)
	  {
	    relative_time to = t - absolute_time ();
	    if (to < relative_time ())
	      {
		s_junkbuffer.clear ();
		return s_junkbuffer;
	      }
	    tm = to.total () * 1000 + to.nanoseconds () / 1000000;
	  }
	else
	  tm = fixed_timeout_value;
	struct pollfd pfd;
	pfd.events = POLLIN | POLLPRI;
	if (!m_writebuffer.empty ())
	  pfd.events |= POLLOUT;
	pfd.revents = 0;
	pfd.fd = m_fd;
	int ret = ::poll (&pfd, 1, tm);
	if (ret == -1 && errno == EAGAIN)
	  continue;
	if (ret < 1)
	  {
	    s_junkbuffer.clear ();
	    return s_junkbuffer;
	  }
	if (pfd.revents & POLLPRI)
	  {
	    if (m_read_priority)
	      l_read_priority (true);
	    else
	      l_read (true);
	    if (m_fd < 0)
	      {
		s_junkbuffer.clear ();
		return s_junkbuffer;
	      }
	    if (!m_priority_readbuffer.empty ())
	      {
		if (!m_idle_priority.connected ())
		  {
		    m_idle_priority = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle_priority));
		    m_keepalive_helper_idle_priority = refptr_this <fd> ();
		  }
		return m_priority_readbuffer;
	      }
	    if (!m_readbuffer.empty ())
	      {
		if (!m_idle.connected ())
		  {
		    m_idle = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle));
		    m_keepalive_helper_idle = refptr_this <fd> ();
		  }
		return m_readbuffer;
	      }
	  }
	if (pfd.revents & POLLOUT)
	  {
	    l_write ();
	    if (m_fd < 0)
	      {
		s_junkbuffer.clear ();
		return s_junkbuffer;
	      }
	  }
	if (pfd.revents & POLLIN)
	  {
	    l_read (true);
	    if (m_fd < 0)
	      {
		s_junkbuffer.clear ();
		return s_junkbuffer;
	      }
	    if (!m_readbuffer.empty ())
	      {
		// Set an idle callback to handle any data which might
		// be left over.
		if (!m_idle.connected ())
		  {
		    m_idle = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle));
		    m_keepalive_helper_idle = refptr_this <fd> ();
		  }
		return m_readbuffer;
	      }
	  }
	if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
	  {
	    s_junkbuffer.clear ();
	    return s_junkbuffer;
	  }
      }
  }

  std::string fd::read_line_block (shevek::relative_time timeout)
  {
    bool use_timeout = timeout >= shevek::relative_time ();
    shevek::absolute_time t (0, 0);
    if (use_timeout)
      t = absolute_time () + timeout;
    std::string sofar;
    std::string::size_type pos;
    std::string *buffer = 0;
    while ((pos = sofar.find ('\n')) == std::string::npos)
      {
	buffer = &read_block (timeout);
	if (buffer->empty ()
	    || (use_timeout
		&& buffer->find ('\n') == std::string::npos
		&& (timeout = t - absolute_time ()) < relative_time ()))
	  {
	    *buffer = sofar + *buffer;
	    return std::string ();
	  }
	sofar += *buffer;
	buffer->clear ();
      }
    if (buffer)
      *buffer = sofar.substr (pos + 1);
    return sofar.substr (0, pos);
  }

  void fd::set_fd (int the_fd)
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    if (m_fd >= 0)
      {
	if (m_handle.connected ())
	  {
	    m_handle.disconnect ();
	    m_keepalive_helper.reset ();
	  }
      }
    m_fd = the_fd;
    if (the_fd >= 0)
      {
	::fcntl (the_fd, F_SETFL, O_NONBLOCK);
	l_connect ();
      }
  }

  void fd::in_filter (filter_t cb)
  {
    startfunc;
    m_in_filter = cb;
  }

  void fd::out_filter (filter_t cb)
  {
    startfunc;
    m_out_filter = cb;
  }

  void fd::l_connect (Glib::IOCondition io)
  {
    startfunc;
    if (m_fd < 0)
      return;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    if (m_handle.connected ())
      {
	m_handle.disconnect ();
	m_keepalive_helper.reset ();
      }
    m_handle = m_main->signal_io ().connect (sigc::mem_fun (*this, &fd::l_check), m_fd, io);
    m_iocondition = io;
    m_keepalive_helper = refptr_this <fd> ();
  }

  Glib::RefPtr <fd> fd::create (int value,
				Glib::RefPtr <Glib::MainContext> main)
  {
    startfunc;
    return Glib::RefPtr <fd> (new fd (value, main));
  }

  fd::fd (int value, Glib::RefPtr <Glib::MainContext> main)
    : m_main (main)
  {
    startfunc;
    m_iocondition = Glib::IO_HUP | Glib::IO_ERR | Glib::IO_NVAL;
    m_fd = -1;
    m_flushing = false;
    set_fd (value);
  }

  fd::~fd ()
  {
    startfunc;
    set_fd (-1);
  }

  void fd::l_read (bool force_fill)
  {
    startfunc;
    ::ssize_t l;
    char buffer[1000];
    l = ::read (m_fd, buffer, sizeof (buffer));
    if (l == 0)
      {
	if (m_eof)
	  m_eof ();
	else if (m_error)
	  m_error ();
	else if (force_fill)
	  {
	    ::close (m_fd);
	    set_fd (-1);
	  }
	else
	  shevek_error ("unhandled EOF");
	return;
      }
    if (l < 0)
      {
	if (errno != EAGAIN)
	  {
	    if (m_rerror)
	      m_rerror ();
	    else if (m_error)
	      m_error ();
	    else if (force_fill)
	      {
		::close (m_fd);
		set_fd (-1);
	      }
	    else
	      shevek_error ("unhandled read error");
	    return;
	  }
      }
    else if (m_read || force_fill) // throw data away if there is no callback
      {
	std::string tmp (buffer, l);
	// filter the data
	if (m_in_filter)
	  m_in_filter (tmp);
	m_readbuffer += tmp;
	// when force filling, don't call a callback
	if (!force_fill)
	  {
	    // only connect to idle if unread was not called during callback
	    if (m_read && !m_read (m_readbuffer) && m_read)
	      {
		l_connect (m_iocondition & ~Glib::IO_IN);
		if (!m_idle.connected ())
		  {
		    m_idle = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle));
		    m_keepalive_helper_idle = refptr_this <fd> ();
		  }
	      }
	    else if (m_flushing && !m_idle_priority.connected ())
	      {
		l_unread ();
	      }
	  }
      }
  }

  void fd::l_read_priority (bool force_fill)
  {
    startfunc;
    ::ssize_t l;
    char buffer[1000];
    l = ::read (m_fd, buffer, sizeof (buffer));
    if (l == 0)
      {
	if (m_eof)
	  m_eof ();
	else if (m_error)
	  m_error ();
	else if (force_fill)
	  {
	    ::close (m_fd);
	    set_fd (-1);
	  }
	else
	  shevek_error ("unhandled EOF");
	return;
      }
    if (l < 0)
      {
	if (errno != EAGAIN)
	  {
	    if (m_rerror)
	      m_rerror ();
	    else if (m_error)
	      m_error ();
	    else if (force_fill)
	      {
		::close (m_fd);
		set_fd (-1);
	      }
	    else
	      shevek_error ("unhandled read error");
	    return;
	  }
      }
    // throw data away if there is no callback
    if (m_read_priority || m_read || force_fill)
      {
	std::string tmp (buffer, l);
	// filter the data
	if (m_in_filter)
	  m_in_filter (tmp);
	std::string *buffer;
	if (m_read_priority)
	  buffer = &m_priority_readbuffer;
	else
	  buffer = &m_readbuffer;
	(*buffer) += tmp;
	// when force filling, don't call a callback
	if (!force_fill)
	  {
	    // only connect to idle if unread was not called during callback
	    if (m_read_priority && !m_read_priority (*buffer)
		&& m_read_priority)
	      {
	        if (!m_idle_priority.connected ())
		  {
		    m_idle_priority = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle_priority));
		    m_keepalive_helper_idle_priority = refptr_this <fd> ();
		  }
		else if (m_flushing && !m_idle.connected ())
		  l_unread ();
	      }
	    // only connect to idle if unread was not called during callback
	    else if (m_read && !m_read (*buffer) && m_read)
	      {
	        if (!m_idle.connected ())
		  {
		    m_idle = m_main->signal_idle ().connect (sigc::mem_fun (*this, &fd::l_idle));
		    m_keepalive_helper_idle = refptr_this <fd> ();
		  }
		else if (m_flushing && !m_idle.connected ())
		  l_unread ();
	      }
	  }
      }
  }

  void fd::l_write ()
  {
    startfunc;
    if (m_writebuffer.empty ())
      return;
    ::ssize_t l;
    ::size_t todo = m_writebuffer.front ().data.size ();
    l = ::write (m_fd, m_writebuffer.front ().data.data (), todo);
    if (l < 0)
      {
	if (errno != EAGAIN)
	  {
	    if (m_werror)
	      m_werror ();
	    else if (m_error)
	      m_error ();
	    else
	      shevek_error ("unhandled write error");
	    if (m_fd < 0)
	      return;
	  }
	return;
      }
    if (todo == unsigned (l))
      {
	if (m_writebuffer.front ().done)
	  m_writebuffer.front ().done ();
	// the writebuffer can have been cleared by a write_reset
	// from the callback
	if (!m_writebuffer.empty ())
	  m_writebuffer.pop_front ();
	if (m_writebuffer.empty ())
	  {
	    l_connect (m_iocondition & ~Glib::IO_OUT);
	  }
      }
    else
      {
	m_writebuffer.front ().data.erase (0, l);
      }
  }

  bool fd::l_check (Glib::IOCondition result)
  {
    startfunc;
    // keep the object alive at least until this function exits
    // if this is not done, callbacks deleting the object may cause crashes
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    if ( (result & (Glib::IO_ERR | Glib::IO_NVAL)) != 0)
      {
	if (m_poll_error)
	  m_poll_error ();
	else if (m_error)
	  m_error ();
	else
	  {
	    shevek_error ("unhandled poll error");
	  }
	return true;
      }
    if ( (result & Glib::IO_PRI) != 0)
      {
	if (m_read_priority_custom)
	  m_read_priority_custom ();
	else if (m_read_priority)
	  l_read_priority (false);
	else if (m_read_custom)
	  m_read_custom ();
	else if (m_read)
	  l_read (false);
	if (m_fd < 0)
	  {
	    return true;
	  }
      }
    if ( (result & Glib::IO_OUT) != 0)
      {
	l_write ();
	if (m_fd < 0)
	  {
	    return true;
	  }
      }
    // treat hangup as read: it will be handled by the reader
    if ( (result & (Glib::IO_IN | Glib::IO_HUP)) != 0)
      {
	if (m_read_custom)
	  m_read_custom ();
	else
	  l_read (false);
      }
    return true;
  }

  bool fd::l_idle ()
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    if (!m_read || m_readbuffer.empty ())
      {
	if (m_idle.connected ())
	  {
	    m_idle.disconnect ();
	    m_keepalive_helper_idle.reset ();
	  }
	if (m_flushing)
	  l_unread ();
	return true;
      }
    if (m_read (m_readbuffer))
      {
	if (m_idle.connected ())
	  {
	    m_idle.disconnect ();
	    m_keepalive_helper_idle.reset ();
	  }
	if (m_flushing)
	  {
	    if (!m_idle_priority.connected ())
	      l_unread ();
	  }
	else if (m_read)
	  {
	    l_connect (m_iocondition | Glib::IO_IN | Glib::IO_PRI);
	  }
      }
    return true;
  }

  bool fd::l_idle_priority ()
  {
    startfunc;
    if (!m_read_priority || m_priority_readbuffer.empty ())
      return true;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    if (m_read_priority (m_priority_readbuffer))
      {
	if (m_idle_priority.connected ())
	  {
	    m_idle_priority.disconnect ();
	    m_keepalive_helper_idle_priority.reset ();
	  }
	if (m_flushing)
	  {
	    if (!m_idle.connected ())
	      l_unread ();
	  }
	else if (m_read_priority)
	  l_connect (m_iocondition | Glib::IO_PRI);
      }
    return true;
  }

  bool fd::l_read_lines (std::string &data)
  {
    startfunc;
    std::string::size_type pos = data.find ('\n');
    if (pos == std::string::npos)
      return true;
    std::string line = data.substr (0, pos); // no newline in here
    data = data.substr (pos + 1); // remove the newline as well
    m_read_lines (line);
    return data.find ('\n') == std::string::npos;
  }

  int fd::get_fd () const
  {
    startfunc;
    return m_fd;
  }

  Glib::RefPtr <Glib::MainContext> fd::get_main_context ()
  {
    startfunc;
    return m_main;
  }

  void fd::set_error (error_t cb)
  {
    startfunc;
    m_error = cb;
  }

  void fd::set_poll_error (error_t cb)
  {
    startfunc;
    m_poll_error = cb;
  }

  void fd::set_read_error (error_t cb)
  {
    startfunc;
    m_rerror = cb;
  }

  void fd::set_write_error (error_t cb)
  {
    startfunc;
    m_werror = cb;
  }

  void fd::set_eof (error_t cb)
  {
    startfunc;
    m_eof = cb;
  }

  void fd::read_reset ()
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    unread ();
    m_readbuffer.clear ();
    m_priority_readbuffer.clear ();
  }

  void fd::write_reset ()
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    m_writebuffer.clear ();
    l_connect (m_iocondition & ~Glib::IO_OUT);
  }

  void fd::reset ()
  {
    startfunc;
    Glib::RefPtr <fd> keep_object_alive = refptr_this <fd> ();
    (void)&keep_object_alive;
    read_reset ();
    write_reset ();
  }
}
