#ifndef _MDNS_MDNS_h
#define _MDNS_MDNS_h

#include "UdpSocket.h"
#include "GetIfAddrs.h"

class MDNSService
{
	friend class MDNSClass;
	
	private:
	
		String _hostName;
		IPV4Address _ip;
		String _name;
		String _proto;
		uint16_t _port;
		uint32_t _ttl;
		uint32_t _creationTime;
		VectorMap<String, String> _texts;
	
	protected:
	
	public:
	
		// constructor
		MDNSService();
		
		// destructor
		~MDNSService();
		
		// copy and pick constructors
		MDNSService(MDNSService const &s);
		MDNSService(MDNSService &&s);
		
		// copy / pick
		MDNSService const &operator=(MDNSService const &s);
		MDNSService &operator=(MDNSService &&s);
		
		// get texts, packed in a string
		String GetTextsPacked(void) const;
		
		// get/set service ip
		IPV4Address const &GetIP(void) const { return _ip; }
		MDNSService &SetIP(IPV4Address const &ip) { _ip = ip; return *this; }
		
		// get/set service host name
		String const &GetHostName(void) const { return _hostName; }
		MDNSService &SetHostName(String const &hostName) { _hostName = hostName; return *this; }
		
		// get/set service name
		String const &GetName(void) const { return _name; }
		MDNSService &SetName(String const &name) { _name = name; return *this; }
		
		// get/set protocol
		String const &GetProtocol(void) const { return _proto; }
		MDNSService &SetProtocol(String const &proto) { _proto = proto; return *this; }
		
		// get/set port
		uint16_t GetPort(void) const { return _port; }
		MDNSService &SetPort(uint16_t port) { _port = port; return *this; }
		
		// get/set ttl
		uint16_t GetTTL(void) const { return _ttl; }
		MDNSService &SetTTL(uint16_t ttl) { _ttl = ttl; return *this; }
		
		// get/set creation time
		uint32_t GetCreationTime(void) const { return _creationTime; }
		MDNSService &SetCreationTime(uint32_t creationTime) { _creationTime = creationTime; return *this; }
		
		// check if expired
		bool IsExpired(void) const { return (uint32_t)msecs() >= _creationTime + _ttl; }
		
		// get the texts
		VectorMap<String, String> const &GetTexts(void) const { return _texts; }
		VectorMap<String, String> &GetTexts(void) { return _texts; }
		
		// add a text
		MDNSService &AddText(String const &key, String const &val);
		
		// remove a text
		MDNSService &RemoveText(String const &key);
		
		// get a text value
		String GetText(String const &key) const;
		
		// debugger helper
		String ToString(void) const;
};

class MDNSClass
{
	friend MDNSClass &__GetMDNS(void);
	
	private:
	
		UdpSocket udp;
	
		// our host name
		String _hostName;
		
		// our instance name
		String _instanceName;
		
		// provided services
		ArrayMap<String, MDNSService> _providedServices;
		
		// services on which we're interested
		Index<String> _interestingServices;
		
		// gathered services from remote peers
		ArrayMap<String, MDNSService> _discoveredServices;
		
		// find a discovered service inside the discovered list
		// by service, protocol and ip; if not found, just add one
		// record at end of list and return its pointer
		MDNSService &FindAdd(String const &name, String const &proto, IPV4Address const &ip);
		
		// check if we're interested in a service/protocol
		bool IsInteresting(String const &service, String const &protocol) const;
		
		// parse answer packet
		bool ParseAnswer(uint16_t const *hdr, StringStream &ss);
		
		// parse query packet
		bool ParseQuery(IPV4Address const &fromIP, uint16_t const *hdr, StringStream &ss);
		
		// parse incoming packet
		bool ParsePacket(void);
		
		// reply (either to a request or to the multicast address
		bool Reply(uint8_t mask, MDNSService const &service, IPV4Address const &ourIP, IPV4Address const &destIP);

		// constructor
		MDNSClass();
		
		// get reply ip from source one
		IPV4Address GetReplyIP(IPV4Address const &sourceIP) const;

	protected:

	public:

		// destructor
		~MDNSClass();
		
		// get host name
		String const &GetHostName(void) const { return _hostName; }
		
		// get instance name
		String const &GetInstanceName(void) const { return _instanceName; }
		
		// listen for a service
		MDNSClass &ListenFor(String const &name, String const &proto);
		
		// un-listen for a service
		MDNSClass &NoListenFor(String const &name, String const &proto);
		
		// enable/disable listening for services
		MDNSClass &Listen(bool l = true);
		MDNSClass &NoListen(void) { return Listen(false); }
		
		// check if listening
		bool IsListening(void) const { return udp.Listening(); }
		
		// get discovered services
		Array<MDNSService> GetDiscoveredServices(String const &name, String const &proto);
		
		// add a provided service
		MDNSClass &AddService(MDNSService const &s);
		
		// remove a provided service
		MDNSClass &RemoveService(String const &name, String const &proto);
		
		// advertise provided services
		MDNSClass &AdvertiseServices(void);
		
		// run a query for interesting services
		MDNSClass &Query(void);
		
		// loop function -- to be called periodically, if not in MT mode
		void Loop(void);
		
		// callback called when a service gets added/updated
		Event<> WhenService;

};

extern MDNSClass &__GetMDNS(void);
#define MDNS	__GetMDNS()

/*

class UdpContext;

struct MDNSService;
struct MDNSTxt;
struct MDNSAnswer;

class MDNSResponder
{
	public:
		MDNSResponder();
		~MDNSResponder();
		bool begin(const char* hostName);
		//for compatibility
		bool begin(const char* hostName, IPAddress ip, uint32_t ttl = 120)
		{
			return begin(hostName);
		}
		void update();

		void addService(char *service, char *proto, uint16_t port);
		void addService(const char *service, const char *proto, uint16_t port)
		{
			addService((char *)service, (char *)proto, port);
		}
		void addService(String service, String proto, uint16_t port)
		{
			addService(service.c_str(), proto.c_str(), port);
		}

		bool addServiceTxt(char *name, char *proto, char * key, char * value);
		void addServiceTxt(const char *name, const char *proto, const char *key, const char * value)
		{
			addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value);
		}
		void addServiceTxt(String name, String proto, String key, String value)
		{
			addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str());
		}

		int queryService(char *service, char *proto);
		int queryService(const char *service, const char *proto)
		{
			return queryService((char *)service, (char *)proto);
		}
		int queryService(String service, String proto)
		{
			return queryService(service.c_str(), proto.c_str());
		}
		String hostname(int idx);
		IPAddress IP(int idx);
		uint16_t port(int idx);

		void enableArduino(uint16_t port, bool auth = false);

		void setInstanceName(String name);
		void setInstanceName(const char * name)
		{
			setInstanceName(String(name));
		}
		void setInstanceName(char * name)
		{
			setInstanceName(String(name));
		}

	private:
		struct MDNSService * _services;
		UdpContext* _conn;
		String _hostName;
		String _instanceName;
		struct MDNSAnswer * _answers;
		struct MDNSQuery * _query;
		bool _newQuery;
		bool _waitingForAnswers;
		WiFiEventHandler _disconnectedHandler;
		WiFiEventHandler _gotIPHandler;


		uint32_t _getOurIp();
		uint16_t _getServicePort(char *service, char *proto);
		MDNSTxt * _getServiceTxt(char *name, char *proto);
		uint16_t _getServiceTxtLen(char *name, char *proto);
		void _parsePacket();
		void _reply(uint8_t replyMask, char * service, char *proto, uint16_t port);
		size_t advertiseServices(); // advertise all hosted services
		MDNSAnswer* _getAnswerFromIdx(int idx);
		int _getNumAnswers();
		bool _listen();
		void _restart();
};
*/

#endif
