/* avahi.hh - publish and browse network ports with avahi
 * Copyright 2009 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_AVAHI_HH
#define SHEVEK_AVAHI_HH

#include <map>
#include <set>
#include <glibmm.h>
#include "refbase.hh"

#include <avahi-client/client.h>
#include <avahi-client/lookup.h>
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
#include <avahi-common/error.h>
#include <avahi-common/simple-watch.h>
#include <avahi-glib/glib-watch.h>

namespace shevek
{
	/// Serve and browse the local network using avahi.
	/** Easy to use interface for serving and browsing avahi-supporting hosts.
	 *  Note that it is not as configurable as using libavahi directly.
	 */
	class avahi : public refbase
	{
	public:
		/// Publish a service on a port.
		/** The protocol is the name of the protocol, without leading underscore and without protocol specification. The published port is _NAME._tcp. It is not possible to publish over udp with this class. Note that you must separately set up a server to listen on the port.
		 */
		void publish (Glib::ustring const &protocol, int port);
		/// Class for browsing other hosts.
		class browser;
		/// Create a browser and populate it with a list of available hosts for the requested protocol.
		/** The browser must not contain a leading underscore, and must not contain the protocol specification. The actual requested port is _NAME._tcp. It is not possible to browse udp with this class.
		 */
		inline Glib::RefPtr <browser> create_browser (Glib::ustring const &protocol);
		/// Create an avahi object for serving and/or browsing.
		static Glib::RefPtr <avahi> create (Glib::ustring const &name = Glib::ustring ()) { return Glib::RefPtr <avahi> (new avahi (name)); }
		/// Unpublish all ports and free all structures associated with the object.
		~avahi ();
	private:
		avahi (Glib::ustring const &name, bool allow_restart = true, bool blocking_poller = false);
		std::map <Glib::ustring, int> m_ports;
		char *m_name;
		bool m_allow_restart;
		AvahiPoll const *m_poll_api;
		AvahiGLibPoll *m_glib_poll;
		AvahiEntryGroup *m_group;
		AvahiClient *m_client;
		AvahiSimplePoll *m_poller;
		void create_services (AvahiClient *client);
		void create_client ();
		void name_change (AvahiClient *client);
		static void group_callback (AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata);
		static void callback (AvahiClient *client, AvahiClientState state, void *userdata);
	};

	/// Class for browsing other hosts.
	class avahi::browser : public refbase
	{
	public:
		/// Details about a discovered service.  These are internally created and may be examined by the application.
		struct details
		{
			/// Network interface.
			AvahiIfIndex interface;
			/// Protocol. This is always tcp, because other protocols are never browsed by this class.
			AvahiProtocol protocol;
			/// Hostname or ip address of the server.
			Glib::ustring address;
			/// Some flags about the result. Usually not useful; see the avahi documentation for possible values and their meanings.
			AvahiLookupResultFlags flags;
			/// Create a details object.  The application has no use for this.
			details (AvahiIfIndex i, AvahiProtocol p) : interface (i), protocol (p) {}
			/// Create a details object.  The application has no use for this.
			details (AvahiIfIndex i, AvahiProtocol p, Glib::ustring a, AvahiLookupResultFlags f) : interface (i), protocol (p), address (a), flags (f) {}
			/// Create a details object.  The application has no use for this.
			details () {}
			/// Allow sorting so details can be put in a std::set.
			bool operator< (details const &that) const { return interface == that.interface ? protocol < that.protocol : interface < that.interface; }
		};
		/// Container class for a list of details.
		typedef std::set <details> details_list;
		/// Information about a discovered server.
		struct owner
		{
			/// Hostname or ip address of the server.
			Glib::ustring host;
			/// Port that is served by the server.
			int port;
			/// Create an owner object.  The application has no use for this.
			owner (Glib::ustring const &h, int p) : host (h), port (p) {}
			/// Create an owner object.  The application has no use for this.
			owner () : port (-1) {}
			/// Allow sorting so owners can be put in a std::map.
			bool operator< (owner const &that) const { return host == that.host ? port < that.port : host < that.host; }
			/// Services provided by this host.
			details_list details;
		};
		/// Container class for a list of owners.
		typedef std::map <Glib::ustring, owner> list;
		/// Access the list of owners.
		list const &get_list () { return m_list; }
		/// Signal to be notified when the list changes.
		sigc::signal1 <void, Glib::ustring const &> signal_changed () { return m_changed; }
		/// The destructor cleans everything up.
		~browser ();
		/// Create a browser class without an existing avahi object.  Don't use this if you have an avahi object; use avahi::create_browser instead.
		static Glib::RefPtr <browser> create (Glib::ustring const &protocol) { return Glib::RefPtr <browser> (new browser (avahi::create (), protocol)); }
		/// Synchronously get a list of owners.  When using this, the servers are not monitored, so you will not be notified of any changes.
		static list get_list_block (Glib::ustring const &protocol, Glib::ustring const &name = Glib::ustring ());
	private:
		Glib::RefPtr <avahi> m_parent;
		list m_list;
		AvahiServiceBrowser *m_sb;
		sigc::signal1 <void, Glib::ustring const &> m_changed;
		Glib::ustring m_filter;
		friend class avahi;
		browser (Glib::RefPtr <avahi> parent, Glib::ustring const &protocol);
		static void resolve_callback (AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, char const *name, char const *type, char const *domain, char const *host_name, AvahiAddress const *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void * userdata);
		static void browse_callback (AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, char const *name, char const *type, char const *domain, AvahiLookupResultFlags flags, void *userdata);
	};

	Glib::RefPtr <avahi::browser> avahi::create_browser (Glib::ustring const &protocol)
	{
		return Glib::RefPtr <browser> (new browser (refptr_this <avahi> (), protocol));
	}
}

#endif
