/* server.hh - a line-protocol network server
 * 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_SERVER_HH
#define SHEVEK_SERVER_HH

#include "refbase.hh"
#include "telnet.hh"
#include <list>
#include <signal.h>

namespace
{
  sighandler_t ignore_broken_pipes = ::signal (SIGPIPE, SIG_IGN);
}

namespace shevek
{
/// Set up a network server using shevek::telnet.
/**
 * New connections are accepted, and callbacks to the program are made
 * when a line of data is received.
 *
 * An example file showing how to use the server:
<CODE><BR>
// \#include &lt;shevek/server.hh&gt;<BR>
// \#include &lt;shevek/mainloop.hh&gt;<BR>
// <BR>
// // per-server data structure<BR>
// struct serverdata<BR>
// {<BR>
//   // Put in here whatever you like, possibly nothing.  There is one<BR>
//   // instantiation of this class per server.<BR>
//   // In most cases, global variables can be used instead of this class, but<BR>
//   // this way allows programs to run more than one server of the same kind,<BR>
//   // each with its own settings.  That is a Good Thing.<BR>
//   // It is available from server.data (), and therefore from<BR>
//   // client-&gt;get_server ()-&gt;data ()<BR>
// };<BR>
// <BR>
// // Each object of this class is a connection.  Add any members that are<BR>
// // needed on a per-connection basis in this class.  Below is a very minimal<BR>
// // example, which implements an echo server (everything you write to it is<BR>
// // echoed back).<BR>
// class client : public shevek::server &lt;client, serverdata&gt;::connection<BR>
// {<BR>
//   // Allow the server to call pickup and read.<BR>
//   friend class shevek::server &lt;client, serverdata&gt;;<BR>
//   void pickup (bool is_stdio)<BR>
//   {<BR>
//     out-&gt;write ("Welcome to the echo server.\\n");<BR>
//   }<BR>
//   void read (std::string const &line)<BR>
//   {<BR>
//     // An echo server should echo.<BR>
//     out-&gt;write (line + '\\n');<BR>
//   }<BR>
//   static Glib::RefPtr &lt;client&gt; create ()<BR>
//   { return Glib::RefPtr &lt;client&gt; (new client () ); }<BR>
//   // Make sure it cannot be constructed other than with create.<BR>
//   // Don't use the constructor for initialising, use pickup () instead.<BR>
//   // The connection is not initialised when the constructor is called.<BR>
//   client () {}<BR>
// };<BR>
// <BR>
// int main ()<BR>
// {<BR>
//   Glib::RefPtr &lt;shevek::server &lt;client, serverdata&gt; &gt;<BR>
//     s = shevek::server &lt;client, serverdata&gt;::create ();<BR>
//   // "1234" is the port to listen on.  It may be a name from<BR>
//   // /etc/services, such as "telnet" (although you need to be root to claim<BR>
//   // that one).  It can also be a filename, which will be created as a unix<BR>
//   // domain socket.  Debugging is a little harder then, as netcat cannot<BR>
//   // connect to such sockets.<BR>
//   s-&gt;open ("1234");<BR>
//   shevek::loop ();<BR>
//   return 0;<BR>
// }<BR>
</CODE>
*/
  template <typename client, typename serverdata>
  class server : virtual public shevek::refbase
  {
    static inline fd::read_lines_t get_connection_cb_from_friend (client *who)
    { return sigc::mem_fun (who, &client::read); }
  public:
    /// Iterator for looping over all current connections.
    typedef typename std::list <Glib::RefPtr <client> >::iterator iterator;
    /// Iterator for looping over all current connections.
    typedef typename std::list <Glib::RefPtr <client> >::const_iterator
    const_iterator;
    /// Base of the client class which is implemented by the calling program.
    /** A client object is created for every connection which is accepted.
     *  This class handles server administration and provides access to members from the client class.
     */
    struct connection : virtual public refbase
    {
      /// The input socket.  The client can stop reading from this connection by calling in->unread ().
      Glib::RefPtr <shevek::fd> in;
      /// The output socket.  This is used to send data to the connection.
      Glib::RefPtr <shevek::fd> out;
      /// This is called after in->unread (), to resume accepting data from this connection.
      void continue_reading ()
      {
	in->read_lines (server <client, serverdata>
			::get_connection_cb_from_friend
			(dynamic_cast <client *> (this) ) );
      }
      /// Destructor.
      ~connection () {}
    protected:
      /// The client class can construct this object with its create function.
      connection () {}
      /// Access to the server object which hosts this client.
      Glib::RefPtr <server <client, serverdata> > get_server () { return m_server; }
      /// This can be called by the client object to close this connection.
      void disconnect () { m_server->l_error (m_self); }
    private:
      friend class server <client, serverdata>;
      Glib::RefPtr <server <client, serverdata> > m_server;
      iterator m_self;
    };
  private:
    std::list <Glib::RefPtr <client> > m_connections;
    Glib::RefPtr <socket> m_listener;
    serverdata m_data;
    server ();
    void l_read (std::string const &line, Glib::RefPtr <client> conn);
    void l_connect (Glib::RefPtr <fd> in, Glib::RefPtr <fd> out,
		    bool is_stdio);
    void l_pickup ();
    void l_delayed_disconnect (typename std::list <Glib::RefPtr <client> >::iterator conn);
    void l_error (typename std::list <Glib::RefPtr <client> >::iterator conn);
  public:
    /// Create a new server object.
    static Glib::RefPtr <server> create ();
    /// Open a port and start running.
    /** If use_stdio is true, there will be an initial connection from standard input/standard output.
     */
    void open (std::string const &port, bool use_stdio = true);
    /// Get the corresponding serverdata structure.
    serverdata &data () { return m_data; }
    /// Get the corresponding serverdata structure.
    serverdata const &data () const { return m_data;}
    /// Loop over all current connections.
    iterator begin () { return m_connections.begin (); }
    /// Loop over all current connections.
    iterator end () { return m_connections.end (); }
    /// Loop over all current connections.
    const_iterator begin () const { return m_connections.begin (); }
    /// Loop over all current connections.
    const_iterator end () const { return m_connections.end (); }
    /// Stop running this server, closing all current connections.
    void shutdown ();
    /// The destructor shuts down the server.
    ~server () { shutdown (); }
  };

  template <typename client, typename serverdata>
  void server <client, serverdata>::shutdown ()
  {
    if (m_listener)
      {
	m_listener->disconnect ();
	m_listener = Glib::RefPtr <socket> ();
      }
    while (!m_connections.empty () )
      m_connections.front ()->disconnect ();
  }

  template <typename client, typename serverdata>
  void server <client, serverdata>::l_read (std::string const &line,
			      Glib::RefPtr <client> conn)
  {
    conn->read (line);
  }

  template <typename client, typename serverdata>
  void server <client, serverdata>::l_delayed_disconnect (typename std::list <Glib::RefPtr <client> >::iterator conn)
  {
    Glib::RefPtr <telnet> s = Glib::RefPtr <telnet>::cast_dynamic ( (*conn)->in);
    if (s)
      s->disconnect ();
    m_connections.erase (conn);
  }

  template <typename client, typename serverdata>
  void server <client, serverdata>::l_error (typename std::list <Glib::RefPtr <client> >::iterator conn)
  {
    Glib::RefPtr <telnet> s = Glib::RefPtr <telnet>::cast_dynamic ( (*conn)->in);
    if (s)
      {
	s->signal_disconnect ().connect (sigc::bind (sigc::mem_fun (*this, &server <client, serverdata>::l_delayed_disconnect), conn) );
	s->disconnect ();
      }
    else
      {
	(*conn)->in->unread ();
	m_connections.erase (conn);
      }
  }

  template <typename client, typename serverdata>
  void server <client, serverdata>::l_connect (Glib::RefPtr <fd> in,
					     Glib::RefPtr <fd> out,
					     bool is_stdio)
  {
    m_connections.push_back (client::create () );
    m_connections.back ()->in = in;
    m_connections.back ()->out = out;
    m_connections.back ()->m_server = refptr_this <server <client, serverdata> > ();
    m_connections.back ()->m_self = --m_connections.end ();
    in->set_error (sigc::bind (sigc::mem_fun (*this, &server <client, serverdata>::l_error),
			       --m_connections.end () ) );
    in->set_eof (sigc::bind (sigc::mem_fun (*this, &server <client, serverdata>::l_error),
			       --m_connections.end () ) );
    m_connections.back ()->continue_reading ();
    out->set_error (sigc::bind (sigc::mem_fun (*this, &server <client, serverdata>::l_error),
				--m_connections.end () ) );
    m_connections.back ()->pickup (is_stdio);
  }

  template <typename client, typename serverdata>
  void server <client, serverdata>::l_pickup ()
  {
    Glib::RefPtr <telnet> newfd = telnet::create ();
    m_listener->accept (newfd);
    l_connect (newfd, newfd, false);
  }

  template <typename client, typename serverdata>
  server <client, serverdata>::server ()
  {
  }

  template <typename client, typename serverdata>
  Glib::RefPtr <server <client, serverdata> > server <client, serverdata>::create ()
  {
    return Glib::RefPtr <server <client, serverdata> > (new server <client, serverdata> () );
  }

  template <typename client, typename serverdata>
  void server <client, serverdata>::open (std::string const &port,
		  bool use_stdio)
  {
    if (m_listener)
      m_listener->disconnect ();
    if (!port.empty () )
      {
	m_listener = socket::create ();
	m_listener->listen (port, sigc::mem_fun (*this, &server <client, serverdata>::l_pickup) );
      }
    if (use_stdio)
      {
	l_connect
		(fd::create (STDIN_FILENO), fd::create (STDOUT_FILENO), true);
      }
  }
}

#endif
