/* Copyright (C) 2009 Jonathon Jongsma
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <glibmm/iochannel.h>
#include <glibmm/object.h>
#include <giomm/initable.h>
#include <giomm/credentials.h>
#include <giomm/socketconnectable.h>
#include <giomm/socketaddress.h>
#include <giomm/enums.h>
#include <giomm/inetaddress.h>

_DEFS(giomm,gio)
_PINCLUDE(glibmm/private/object_p.h)

namespace Gio
{
class GIOMM_API SocketSource;

_WRAP_ENUM(SocketType, GSocketType, decl_prefix GIOMM_API)
_WRAP_ENUM(SocketProtocol, GSocketProtocol, decl_prefix GIOMM_API)
_WRAP_ENUM(SocketMsgFlags, GSocketMsgFlags, decl_prefix GIOMM_API)

/** @defgroup NetworkIO Portable Network I/O Functionality
 */

/** Low-level socket object
 *
 * A Socket is a low-level networking primitive. It is a more or less direct
 * mapping of the BSD socket API in a portable GObject based API. It supports
 * both the UNIX socket implementations and winsock2 on Windows.
 *
 * Socket is the platform independent base upon which the higher level network
 * primitives are based. Applications are not typically meant to use it
 * directly, but rather through classes like SocketClient, SocketService and
 * SocketConnection. However there may be cases where direct use of Socket is
 * useful.
 *
 * Socket implements the Initable interface, and since initialization can fail,
 * the constructor may throw an exception.
 *
 * Sockets operate in two general modes, blocking or non-blocking. When in
 * blocking mode all operations block until the requested operation is finished
 * or there is an error. In non-blocking mode all calls that would block return
 * immediately with a Gio::Error::WOULD_BLOCK error. To know when a call would
 * successfully run you can call condition_check(), or condition_wait(). You can
 * also use create_source() and attach it to a Glib::MainContext to get
 * callbacks when I/O is possible. Note that all sockets are always set to non
 * blocking mode in the system, and blocking mode is emulated in Socket.
 *
 * When working in non-blocking mode applications should always be able to
 * handle getting a Gio::Error::WOULD_BLOCK error even when some other function
 * said that I/O was possible. This can easily happen in case of a race
 * condition in the application, but it can also happen for other reasons. For
 * instance, on Windows a socket is always seen as writable until a write
 * returns Gio::Error::WOULD_BLOCK.
 *
 * Sockets can be either connection oriented or datagram based. For connection
 * oriented types you must first establish a connection by either connecting to
 * an address or accepting a connection from another address. For connectionless
 * socket types the target/source address is specified or received in each I/O
 * operation.
 *
 * All socket file descriptors are set to be close-on-exec.
 *
 * Note that creating a Socket causes the signal SIGPIPE to be ignored for the
 * remainder of the program. If you are writing a command-line utility that uses
 * Socket, you may need to take into account the fact that your program will not
 * automatically be killed if it tries to write to stdout after it has been
 * closed.
 *
 * @newin{2,24}
 * @ingroup NetworkIO
 */
class GIOMM_API Socket : public Glib::Object, public Initable
{
  _CLASS_GOBJECT(Socket, GSocket, G_SOCKET, Glib::Object, GObject, , , GIOMM_API)
  _IMPLEMENTS_INTERFACE(Initable)

protected:
  Socket(SocketFamily family, SocketType type, SocketProtocol protocol,
         const Glib::RefPtr<Cancellable>& cancellable);

  Socket(int fd, const Glib::RefPtr<Cancellable>& cancellable);

public:
  _WRAP_METHOD_DOCS_ONLY(g_socket_new)

  /** @param cancellable A Cancellable object which can be used to cancel the operation.
   * @throw Glib::Error
   */
  static Glib::RefPtr<Socket>
  create(SocketFamily family, SocketType type, SocketProtocol protocol,
         const Glib::RefPtr<Cancellable>& cancellable = Glib::RefPtr<Cancellable>());

  // gmmproc thinks that this function should be wrapped in this class because
  // its only parameter is a GSocket.  In fact, it is wrapped in the
  // SocketConnection class.
  _IGNORE(g_socket_connection_factory_create_connection)

  _WRAP_METHOD_DOCS_ONLY(g_socket_new_from_fd)
  /** @param cancellable A Cancellable object which can be used to cancel the operation.
   * @throw Glib::Error
   */
  static Glib::RefPtr<Socket> create_from_fd(int fd, const Glib::RefPtr<Cancellable>&
                                             cancellable = Glib::RefPtr<Cancellable>());

  /** When a socket is created it is attached to an address family, but it doesn't
   * have an address in this family. Socket::bind() assigns the address (sometimes
   * called name) of the socket.
   *
   * It is generally required to bind to a local address before you can receive
   * connections. (See Socket::listen() and Socket::accept()). In certain situations,
   * you may also want to bind a socket that will be used to initiate connections,
   * though this is not normally required.
   *
   * If socket is a TCP socket, then @a allow_reuse controls the setting of the SO_REUSEADDR
   * socket option; normally it should be <tt>true</tt> for server sockets (sockets that you
   * will eventually call Socket::accept() on), and <tt>false</tt> for client sockets.
   * (Failing to set this flag on a server socket may cause Socket::bind() to
   * throw Gio::Error with ADDRESS_IN_USE if the server program
   * is stopped and then immediately restarted.)
   *
   * If socket is a UDP socket, then @a allow_reuse determines whether or not
   * other UDP sockets can be bound to the same address at the same time. In particular,
   * you can have several UDP sockets bound to the same address, and they will all
   * receive all of the multicast and broadcast packets sent to that address.
   * (The behavior of unicast UDP packets to an address with multiple listeners is not defined.)
   *
   * @param address a SocketAddress specifying the local address.
   * @param allow_reuse whether to allow reusing this address
   *
   * @throw Gio::Error
   */
  _WRAP_METHOD(void bind(const Glib::RefPtr<SocketAddress>& address, bool allow_reuse), g_socket_bind, errthrow)

  /** Marks the socket as a server socket - a socket that is used to accept
   * incoming requests using Socket::accept().
   *
   * Before calling this the socket must be bound to a local address using Socket::bind().
   *
   * To set the maximum amount of outstanding clients, use Socket::set_listen_backlog().
   *
   * @throw Gio::Error
   */
  _WRAP_METHOD(void listen(), g_socket_listen, errthrow)

  /** Accept incoming connections on a connection-based socket. This removes the
   * first outstanding connection request from the listening socket and creates
   * a GSocket object for it.
   *
   * The socket must be bound to a local address with g_socket_bind() and must
   * be listening for incoming connections (Socket::listen()).
   *
   * If there are no outstanding connections then the operation will block or
   * throw Gio::Error with ERROR_WOULD_BLOCK if non-blocking
   * I/O is enabled. To be notified of an incoming connection, wait for the
   * Glib::IO_IN condition.
   *
   * @param cancellable A Cancellable object which can be used to cancel the operation.
   *
   * @return a Gio::Socket
   *
   * @throw Gio::Error
   */
  _WRAP_METHOD(Glib::RefPtr<Socket> accept(const Glib::RefPtr<Cancellable>& cancellable{?}), g_socket_accept, errthrow)

  /** Connect the socket to the specified remote address.
   *
   * For connection oriented socket this generally means we attempt to make a
   * connection to the address . For a connection-less socket it sets the default
   * address for Socket::send() and discards all incoming datagrams from other sources.
   *
   * Generally connection oriented sockets can only connect once, but
   * connection-less sockets can connect multiple times to change the default address.
   *
   * If the connect call needs to do network I/O it will block, unless non-blocking
   * I/O is enabled. Then Gio::Error with ERROR_PENDING is thrown
   * and the user can be notified of the connection finishing by waiting for the
   * Glib::IO_OUT condition. The result of the connection must then be checked
   * with Socket::check_connect_result().
   *
   * @param address	a SocketAddress specifying the remote address.
   * @param cancellable	A Cancellable object which can be used to cancel the operation.
   *
   * @throw Gio::Error
   */
  _WRAP_METHOD(void connect(const Glib::RefPtr<SocketAddress>& address, const Glib::RefPtr<Cancellable>& cancellable{?}), g_socket_connect, errthrow)

  // FIXME: it doesn't really seem like this is a proper use of exceptions...
  _WRAP_METHOD(void check_connect_result(), g_socket_check_connect_result, errthrow)

  // TODO: std::string overload?
  _WRAP_METHOD(gssize receive(char* buffer, gsize size, const Glib::RefPtr<Cancellable>& cancellable{?}), g_socket_receive, errthrow)

  _IGNORE(g_socket_receive_from)
  gssize receive_from(Glib::RefPtr<SocketAddress>& address, char* buffer, gsize size, const Glib::RefPtr<Cancellable>& cancellable);
  gssize receive_from(Glib::RefPtr<SocketAddress>& address, char* buffer, gsize size);
  // TODO: wrap g_socket_receive_message -- figure out this GInputVector thing
  // TODO: std::string overload?

  _WRAP_METHOD(gssize send(const gchar* buffer, gsize size, const Glib::RefPtr<Cancellable>& cancellable{?}), g_socket_send, errthrow)

  // TODO: std::string overload?
  _WRAP_METHOD(gssize send_to(const Glib::RefPtr<SocketAddress>& address, const char* buffer, gsize size, const Glib::RefPtr<Cancellable>& cancellable{?}), g_socket_send_to, errthrow)

  // TODO: wrap g_socket_send_message -- figure out this GOutputVector thing
  _WRAP_METHOD(void close(), g_socket_close, errthrow)
  _WRAP_METHOD(bool is_closed(), g_socket_is_closed)

  /** Creates a SocketSource that can be attached to a Glib::MainContext to monitor
   * for the availability of the specified @a condition on the socket.
   *
   * Create a slot from a function to be called when @a condition is met
   * for the socket with sigc::ptr_fun() or sigc::mem_fun() and pass
   * it into the connect() function of the returned SocketSource object.
   * Polling of the socket will start when you attach a Glib::MainContext
   * object to the returned SocketSource object using its attach() function.
   *
   * It is meaningless to specify Glib::IO_ERR or Glib::IO_HUP in @a condition;
   * these conditions will always be reported output if they are true.
   *
   * @a cancellable can be used to cancel the source, which will
   * cause the source to trigger, reporting the current condition (which
   * is likely 0 unless cancellation happened at the same time as a
   * condition change). You can check for this in the callback using
   * Cancellable::is_cancelled().
   *
   * If the socket has a timeout set, and it is reached before @a condition
   * occurs, the source will then trigger anyway, reporting Glib::IO_IN or
   * Glib::IO_OUT depending on @a condition. However, the socket will have been
   * marked as having had a timeout, and so the next Socket I/O method
   * you call will then fail with a Gio::IO_ERROR_TIMED_OUT.
   *
   * Gio::signal_socket().connect() is a simpler interface to the same functionality.
   *
   * @newin{2,42}
   *
   * @param condition A Glib::IOCondition mask to monitor.
   * @param cancellable A Cancellable. The default value means the source is not cancellable.
   * @return A newly allocated SocketSource.
   */
  Glib::RefPtr<SocketSource> create_source(Glib::IOCondition condition, const Glib::RefPtr<Cancellable>& cancellable = Glib::RefPtr<Cancellable>());
  _IGNORE(g_socket_create_source)

  _WRAP_METHOD(void shutdown(bool shutdown_read, bool shutdown_write), g_socket_shutdown, errthrow)
  _WRAP_METHOD(bool is_connected(), g_socket_is_connected)

  _WRAP_METHOD(gssize get_available_bytes() const,  g_socket_get_available_bytes)

  _WRAP_METHOD(Glib::IOCondition condition_check(Glib::IOCondition condition), g_socket_condition_check)

  _WRAP_METHOD(void condition_wait(Glib::IOCondition condition, const Glib::RefPtr<Cancellable>& cancellable{?}), g_socket_condition_wait, errthrow)

  _WRAP_METHOD(void condition_timed_wait(Glib::IOCondition condition, gint64 timeout, const Glib::RefPtr<Cancellable>& cancellable{?}), g_socket_condition_timed_wait, errthrow)

  _WRAP_METHOD(void set_listen_backlog(int backlog), g_socket_set_listen_backlog)
  _WRAP_METHOD(int get_listen_backlog() const, g_socket_get_listen_backlog)
  _WRAP_METHOD(void set_blocking(bool blocking), g_socket_set_blocking)
  _WRAP_METHOD(bool get_blocking() const, g_socket_get_blocking)
  _WRAP_METHOD(void set_keepalive(bool keepalive), g_socket_set_keepalive)
  _WRAP_METHOD(bool get_keepalive() const, g_socket_get_keepalive)
  _WRAP_METHOD(SocketFamily get_family() const, g_socket_get_family)
  _WRAP_METHOD(int get_fd() const, g_socket_get_fd)
  _WRAP_METHOD(Glib::RefPtr<SocketAddress> get_local_address() const, g_socket_get_local_address, errthrow)
  _WRAP_METHOD(Glib::RefPtr<SocketAddress> get_remote_address() const, g_socket_get_remote_address, errthrow)
  _WRAP_METHOD(SocketProtocol get_protocol() const, g_socket_get_protocol)
  _WRAP_METHOD(SocketType get_socket_type() const, g_socket_get_socket_type)
  _WRAP_METHOD(bool speaks_ipv4() const, g_socket_speaks_ipv4)

  _WRAP_METHOD(Glib::RefPtr<Credentials> get_credentials(), g_socket_get_credentials, errthrow)
  _WRAP_METHOD(Glib::RefPtr<const Credentials> get_credentials() const, g_socket_get_credentials, errthrow)

  _WRAP_METHOD(guint get_timeout() const, g_socket_get_timeout)
  _WRAP_METHOD(void set_timeout(guint timeout), g_socket_set_timeout)

  _WRAP_METHOD_DOCS_ONLY(g_socket_receive_with_blocking)
  gssize receive_with_blocking(gchar* buffer, gsize size, bool blocking, const Glib::RefPtr<Cancellable>& cancellable = Glib::RefPtr<Cancellable>());

  _WRAP_METHOD_DOCS_ONLY(g_socket_send_with_blocking)
  gssize send_with_blocking(gchar* buffer, gsize size, bool blocking, const Glib::RefPtr<Cancellable>& cancellable = Glib::RefPtr<Cancellable>());

  _WRAP_METHOD(bool get_option(int level, int optname, int& value) const, g_socket_get_option, errthrow)
  _WRAP_METHOD(bool set_option(int level, int optname, int value), g_socket_set_option, errthrow)

  _WRAP_METHOD(guint get_ttl() const, g_socket_get_ttl)
  _WRAP_METHOD(void set_ttl(guint ttl), g_socket_set_ttl)

  _WRAP_METHOD(bool get_broadcast() const, g_socket_get_broadcast )
  _WRAP_METHOD(void set_broadcast(bool broadcast), g_socket_set_broadcast)

  _WRAP_METHOD(bool get_multicast_loopback() const, g_socket_get_multicast_loopback)
  _WRAP_METHOD(void set_multicast_loopback(bool loopback), g_socket_set_multicast_loopback)
  _WRAP_METHOD(guint get_multicast_ttl() const, g_socket_get_multicast_ttl)
  _WRAP_METHOD(void set_multicast_ttl(guint ttl), g_socket_set_multicast_ttl)

  _WRAP_METHOD(bool join_multicast_group(const Glib::RefPtr<InetAddress>& group, bool source_specific, const std::string& iface{?}), g_socket_join_multicast_group, errthrow)
  _WRAP_METHOD(bool leave_multicast_group(const Glib::RefPtr<InetAddress>& group, bool source_specific, const std::string& iface{?}), g_socket_leave_multicast_group, errthrow)


  _WRAP_PROPERTY("blocking", bool)
  _WRAP_PROPERTY("family", SocketFamily)
  _WRAP_PROPERTY("fd", int)
  _WRAP_PROPERTY("keepalive", bool)
  _WRAP_PROPERTY("listen-backlog", int)
  _WRAP_PROPERTY("local-address", Glib::RefPtr<SocketAddress>)
  _WRAP_PROPERTY("remote-address", Glib::RefPtr<SocketAddress>)
  _WRAP_PROPERTY("timeout", guint)
  _WRAP_PROPERTY("protocol", SocketProtocol)
  _WRAP_PROPERTY("broadcast", bool)
  _WRAP_PROPERTY("type", SocketType)
  _WRAP_PROPERTY("ttl", guint)
  _WRAP_PROPERTY("multicast-loopback", bool)
  _WRAP_PROPERTY("multicast-ttl", guint)
};

} // namespace Gio
