/* fd.hh - use file descriptors with Glib
 * 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/>.
 */

#ifndef SHEVEK_FD_HH
#define SHEVEK_FD_HH

#include <sigc++/sigc++.h>
#include <glibmm.h>
#include "refbase.hh"
#include "time.hh"

namespace shevek
{
  /// The fd class is a generic wrapper for a file descriptor to use it in the Glib event loop.
  class fd : virtual public refbase
  {
  public:
    /* types */
    /// Function pointer to call when data is read from fd
    typedef sigc::slot0 <void> read_custom_t;
    /// Function pointer to call when fd is ready for reading
    typedef sigc::slot1 <bool, std::string &> read_t;
    /// Function pointer to call when a complete line has arrived
    typedef sigc::slot1 <void, std::string const &> read_lines_t;
    /// Function pointer to call when an error occurs
    typedef sigc::slot0 <void> error_t;
    /// Function pointer to call when data has been written
    typedef sigc::slot0 <void> write_done_t;
    /// Function pointer to filter in and outgoing data
    typedef sigc::slot1 <void, std::string &> filter_t;
    /// Function pointer to signal that all data is flushed after unread ()
    typedef sigc::slot0 <void> flush_t;

    /* member functions */
    /// Poll for read with a custom callback to poll.
    /** If no callback is set for priority read, this callback is used for that as well.
     */
    void read_custom (read_custom_t cb);
    /// Poll for priority read with a custom callback to poll.
    void read_priority_custom (read_custom_t cb);
    /// Poll for read and set read callback (resets custom callback)
    /** If no callback is set for priority read, this callback is used for that as well.
     */
    void read (read_t cb);
    /// Poll for priority read and set read callback (resets custom callback)
    void read_priority (read_t cb);
    /// Poll for read and set read lines callback (resets custom and read callback).  Polls for priority read as well.
    void read_lines (read_lines_t cb);
    /// Stop polling for read (including priority read).
    void unread (bool flush_buffer = false, flush_t cb = flush_t () );
    /// Write data and set a callback (defaults to none).
    void write (std::string const &data, write_done_t cb = write_done_t () );
    /// Write data, ignoring the filter, and set a callback (defaults to none).
    void write_raw (std::string const &data, write_done_t cb = write_done_t () );
    /// Block until write buffer is empty.
    /** This will block at most until the timeout is reached, if it is positive.
     */
    bool write_block (relative_time timeout = relative_time (-1, 0) );
    /// Block until data is read, try writing if there is a write buffer.
    /** Return read data as reference to buffer.  (callback is not called)
     *  Returns immediately if buffer is not empty.
     *  Priority read buffer and normal read buffer are both checked.
     *  This will block at most until the timeout is reached, if it is
     *  positive.
     */
    std::string &read_block (relative_time timeout = relative_time (-1, 0) );
    /// Call read_block until a line has been read, or the timeout expires.
    std::string read_line_block (relative_time timeout = relative_time (-1, 0) );
    /// Change file descriptor.
    void set_fd (int fd);
    /// If set, incoming data is filtered through this callback before it is put into the buffer.
    void in_filter (filter_t cb);
    /// If set, outgoing data is filtered through this callback before it is sent to the file descriptor.
    void out_filter (filter_t cb);
    /// Create a new fd.
    static Glib::RefPtr <fd>
    create (int value = -1, Glib::RefPtr <Glib::MainContext> main
	    = Glib::MainContext::get_default () );
    /// Set a callback for all error types at once
    /** This is used for any error for which no other callback is set.
     */
    void set_error (error_t cb);
    /// Callback for errors from poll
    void set_poll_error (error_t cb);
    /// Callback for errors from read
    void set_read_error (error_t cb);
    /// Callback for errors from write
    void set_write_error (error_t cb);
    /// Callback for end of file
    void set_eof (error_t cb);
    /// Stop reading, delete the buffer.
    void read_reset ();
    /// Stop writing, delete the buffer.
    void write_reset ();
    /// Stop reading and writing, delete the buffers.
    void reset ();
    /// Get the fd.  This function should mostly be used by derived classes.
    int get_fd () const;
    /// Get the main context.  Also mostly used by derived classes.
    Glib::RefPtr <Glib::MainContext> get_main_context ();
  protected:
    /// Constructor
    fd (int value, Glib::RefPtr <Glib::MainContext> main);
    /// Destructor
    ~fd ();
  private:
    // not copyable
    fd (fd const &that); // NI
    fd &operator= (fd const &that); // NI
    /* internal functions */
    // callback, called when poll returns any event on the fd
    bool l_check (Glib::IOCondition result);
    // helper function for l_check and blocking functions
    void l_write ();
    // helper function for l_check and blocking functions
    void l_read (bool force_fill);
    void l_read_priority (bool force_fill);
    // callback for idle function
    bool l_idle ();
    bool l_idle_priority ();
    // finish up unread after read buffer is flushed
    void l_unread ();
    // disconnect if neccesary, and reconnect (with new fd and/or iocondition)
    void l_connect (Glib::IOCondition
		    io = Glib::IO_HUP | Glib::IO_ERR | Glib::IO_NVAL);
    // read lines at a time
    bool l_read_lines (std::string &data);
    /* types */
    // element for the write queue
    struct write_t
    {
      std::string data;
      write_done_t done;
    };
    /* data */
    // write queue
    std::list <write_t> m_writebuffer;
    // read buffer
    std::string m_readbuffer;
    std::string m_priority_readbuffer;
    // read callback
    read_t m_read;
    read_t m_read_priority;
    // read lines callback
    read_lines_t m_read_lines;
    // callback for custom reader
    read_custom_t m_read_custom;
    read_custom_t m_read_priority_custom;
    // flush callback
    bool m_flushing;
    flush_t m_flush;
    // file descriptor
    int m_fd;
    // data filters
    filter_t m_in_filter, m_out_filter;
    // read and idle callback
    sigc::connection m_handle, m_idle, m_idle_priority;
    // error callbacks
    error_t m_error, m_poll_error, m_rerror, m_werror, m_eof;
    // main context
    Glib::RefPtr <Glib::MainContext> m_main;
    // current io condition
    Glib::IOCondition m_iocondition;
    // objects to keep the object alive as long as it can be called.
    Glib::RefPtr <fd> m_keepalive_helper;
    Glib::RefPtr <fd> m_keepalive_helper_idle;
    Glib::RefPtr <fd> m_keepalive_helper_idle_priority;

    /* static members */
    // buffer to return from blocking functions when object is destroyed.
    static std::string s_junkbuffer;
  };
}

#endif
