//
//  gensio - A library for abstracting stream I/O
//  Copyright (C) 2022  Corey Minyard <minyard@acm.org>
//
//  SPDX-License-Identifier: LGPL-2.1-only

// This is a C++ wrapper for the gensio library MDNS interface.

#ifndef GENSIOMDNS_CPP_INCLUDE
#define GENSIOMDNS_CPP_INCLUDE

#if defined GENSIO_LINK_STATIC
  #define GENSIOMDNSCPP_DLL_PUBLIC
  #define GENSIOMDNSCPP_DLL_LOCAL
#elif defined _WIN32 || defined __CYGWIN__
  #ifdef BUILDING_GENSIOMDNSCPP_DLL
    #ifdef __GNUC__
      #define GENSIOMDNSCPP_DLL_PUBLIC __attribute__ ((dllexport))
    #else
      #define GENSIOMDNSCPP_DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #else
    #ifdef __GNUC__
      #define GENSIOMDNSCPP_DLL_PUBLIC __attribute__ ((dllimport))
    #else
      #define GENSIOMDNSCPP_DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #endif
  #define GENSIOMDNSCPP_DLL_LOCAL
#else
  #if __GNUC__ >= 4
    #define GENSIOMDNSCPP_DLL_PUBLIC __attribute__ ((visibility ("default")))
    #define GENSIOMDNSCPP_DLL_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define GENSIOMDNSCPP_DLL_PUBLIC
    #define GENSIOMDNSCPP_DLL_LOCAL
  #endif
#endif

namespace gensios {
#include <gensio/gensio_mdns.h>

    // Access to the MDNS code

    class MDNS;
    class MDNS_Service;
    class MDNS_Service_Event;
    class MDNS_Watch;
    class MDNS_Watch_Event;

    // This class is used to know when an MDNS object has finished the
    // shutdown operation and will not call any more callbacks.
    class GENSIOMDNSCPP_DLL_PUBLIC MDNS_Free_Done {
    public:
	// Called when the shutdown is complete.  The mdns object may be
	// freed when this returns.
	virtual void mdns_free_done() = 0;

	virtual ~MDNS_Free_Done() = default;
    protected:
	MDNS *m;
    private:
	friend void mdns_free_done(struct gensio_mdns *m, void *userdata);
	friend class MDNS;
    };

    class GENSIOMDNSCPP_DLL_PUBLIC Raw_MDNS_Event_Handler {
    public:
	virtual ~Raw_MDNS_Event_Handler() = default;

	virtual void handle(MDNS_Watch_Event *e,
			    enum gensio_mdns_data_state state,
			    int interfacenum, int ipdomain,
			    const char *name, const char *mtype,
			    const char *domain, const char *host,
			    const struct gensio_addr *addr,
			    const char * const *txt) = 0;
	virtual void set_parent(Raw_MDNS_Event_Handler *parent) { }
    };

    class GENSIOMDNSCPP_DLL_PUBLIC Raw_MDNS_Service_Event_Handler {
    public:
	virtual ~Raw_MDNS_Service_Event_Handler() = default;

	virtual void handle(MDNS_Service_Event *e,
			    enum gensio_mdns_service_event ev,
			    const char *info) = 0;
	virtual void set_parent(Raw_MDNS_Service_Event_Handler *parent) { }
    };

    class GENSIOMDNSCPP_DLL_PUBLIC MDNS {
    public:
	MDNS(Os_Funcs &o);

	inline Os_Funcs &get_os_funcs() { return go; }

	// Functions to allocate a service and a watch.  This is the
	// only way for a user to allocate them.  The event is not
	// required for services, but it makes no sense to have a
	// watch without an event handler, so it's required there.
	MDNS_Service *add_service(int interfacenum, int ipdomain,
				  const char *name, const char *mtype,
				  const char *domain, const char *host,
				  int port, const char * const *txt,
				  MDNS_Service_Event *event = NULL,
				  Raw_MDNS_Service_Event_Handler *evh = NULL);
	MDNS_Watch *add_watch(int interfacenum, int ipdomain,
			      const char *name, const char *mtype,
			      const char *domain, const char *host,
			      MDNS_Watch_Event *event,
			      Raw_MDNS_Event_Handler *evh = NULL);
	// If you have a raw event handler, you must supply it above.
	// This avoids race conditions between creating the watch and
	// installing the new raw event handler.  This is not a
	// problem for gensios because events aren't enabled at
	// startup, but they are for MDNS watches.

	// Like a gensio, you cannot directly delete an MDNS object.
	// It may be in callbacks.  You have to go through a special
	// free operation.  See the Gensio free() method for details.
	void free(MDNS_Free_Done *done = NULL);
    protected:
	virtual ~MDNS() { }
	Os_Funcs go;
    private:
	struct gensio_mdns *m;
	friend class MDNS_Service;
	friend class MDNS_Watch;
	friend void mdns_free_done(struct gensio_mdns *m, void *userdata);
    };

    // This handles events from an mdns service informing you that it
    // has has finished registration, the name has changed, or that it
    // is removed.  Analogous to gensio_mdns_service_cb, see the
    // gensio_mdns_add_service2() man page for details.
    class GENSIOMDNSCPP_DLL_PUBLIC MDNS_Service_Event {
    public:
	virtual void event(enum gensio_mdns_service_event ev,
			   const char *info) = 0;
	virtual ~MDNS_Service_Event() = default;
    private:
	MDNS_Service *s = NULL;
	friend class MDNS_Service;
	friend void mdns_service_event(struct gensio_mdns_service *s,
				       enum gensio_mdns_service_event ev,
				       const char *info,  void *userdata);
    };

    // A class representing an MDNS service.  A wrapper around
    // gensio_mdns_add/remove_service(), see man pages on those
    // functions for details.
    class GENSIOMDNSCPP_DLL_PUBLIC MDNS_Service {
    public:
	inline Os_Funcs &get_os_funcs() { return m->get_os_funcs(); }

	// Like a gensio, you cannot directly delete an MDNS_Service
	// object.  It may be in callbacks.  You have to go through a
	// special free operation.  See the Gensio free() method for
	// details.  Unlike a watch, the done callback goes through
	// the event handler registered in MDNS_Service, if you
	// registered one.  That is unfortunate, but is that way due
	// to historical reasons.
	void free();

	// This allows the user to intercept raw events, it is primarily
	// used to help other language bindings tie in things they need.
	Raw_MDNS_Service_Event_Handler *raw_event_handler = NULL;

    protected:
	friend class MDNS;
	MDNS_Service(MDNS *m, int interfacenum, int ipdomain,
		     const char *name, const char *mtype,
		     const char *domain, const char *host,
		     int port, const char * const *txt,
		     MDNS_Service_Event *event = NULL,
		     Raw_MDNS_Service_Event_Handler *raw_event_handler = NULL);
	virtual ~MDNS_Service() {
	    if (raw_event_handler)
		delete raw_event_handler;
	}
    private:
        MDNS *m;
	struct gensio_mdns_service *s;
	MDNS_Service_Event *event = NULL;
	friend void mdns_service_event(struct gensio_mdns_service *s,
				       enum gensio_mdns_service_event ev,
				       const char *info,  void *userdata);
    };

    // This class is used to know when an MDNS_Watch object has
    // finished the shutdown operation and will not call any more
    // callbacks.
    class GENSIOMDNSCPP_DLL_PUBLIC MDNS_Watch_Free_Done {
    public:
	// Called when the shutdown is complete.  The mdns object may be
	// freed when this returns.
	virtual void mdns_watch_free_done() = 0;

	virtual ~MDNS_Watch_Free_Done() = default;
    private:
	MDNS_Watch *w = NULL;
        friend void mdns_watch_free_done(struct gensio_mdns_watch *w,
					 void *userdata);
	friend class MDNS_Watch;
    };

    // This handles events from an mdns watch informing you that it
    // has a new MDNS entry.  Analogous to gensio_mdns_watch_cb, see
    // the gensio_mdns_add_watch() man page for details.
    class GENSIOMDNSCPP_DLL_PUBLIC MDNS_Watch_Event {
    public:
	virtual void event(enum gensio_mdns_data_state state,
			   int interfacenum, int ipdomain,
			   const char *name, const char *mtype,
			   const char *domain, const char *host,
			   const Addr *addr, const char * const *txt) = 0;
	virtual ~MDNS_Watch_Event() = default;
    private:
	MDNS_Watch *w;
	friend class MDNS_Watch;
	friend void mdns_watch_event(struct gensio_mdns_watch *w,
				     enum gensio_mdns_data_state state,
				     int interfacenum, int ipdomain,
				     const char *name, const char *mtype,
				     const char *domain, const char *host,
				     const struct gensio_addr *addr,
				     const char * const *txt, void *userdata);
    };

    // A class representing an MDNS service.  A wrapper around
    // gensio_mdns_add/remove_watch(), see man pages on those
    // functions for details.
    class GENSIOMDNSCPP_DLL_PUBLIC MDNS_Watch {
    public:
	inline Os_Funcs &get_os_funcs() { return m->get_os_funcs(); }

	// Like a gensio, you cannot directly delete an MDNS_Watch object.
	// It may be in callbacks.  You have to go through a special
	// free operation.  See the Gensio free() method for details.
	void free(MDNS_Watch_Free_Done *done = NULL);

	// This allows the user to intercept raw events, it is primarily
	// used to help other language bindings tie in things they need.
	Raw_MDNS_Event_Handler *raw_event_handler = NULL;

    protected:
	friend class MDNS;
	MDNS_Watch(MDNS *m, int interfacenum, int ipdomain,
		   const char *name, const char *mtype,
		   const char *domain, const char *host,
		   MDNS_Watch_Event *event,
		   Raw_MDNS_Event_Handler *raw_event_handler = NULL);

	virtual ~MDNS_Watch() {
	    if (raw_event_handler)
		delete raw_event_handler;
	}
    private:
	MDNS *m;
	MDNS_Watch_Event *event;
	struct gensio_mdns_watch *w;
        friend void mdns_watch_free_done(struct gensio_mdns_watch *w,
					 void *userdata);
    };

}
#endif
