#ifndef n_mDNSServiceFinder_H
#define n_mDNSServiceFinder_H

/*
 *
 * nucleo/network/rdv/mDNSServiceFinder.H --
 *
 * Copyright (C) Nicolas Roussel
 *
 * See the file LICENSE for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

#include <nucleo/network/rdv/ServiceFinder.H>
#include <nucleo/core/FileKeeper.H>

#include <dns_sd.h>

#include <iostream>
#include <sstream>
#include <stdexcept>

namespace nucleo {

  class mDNSServiceFinder : public ServiceFinder {

  protected:

    DNSServiceRef sdRef ;
    FileKeeper *fk ;

    static void resolveCallBack(DNSServiceRef sdRef,
						  DNSServiceFlags flags,
						  uint32_t interfaceIndex,
						  DNSServiceErrorType errorCode,
						  const char *fullname,
						  const char *hosttarget,
						  uint16_t port,
						  uint16_t txtLen,
						  const char *txtRecord,
						  void *context) {
	 Event *e = (Event*)context ;
	 e->service.ipaddr = hosttarget ;
	 e->service.port = port ;

	 uint16_t start=0, eq=0 ;
	 while (start<txtLen) {
	   uint16_t recordLen = txtRecord[start] ;
	   if (recordLen>0) {
		std::string key, value ;
		for (eq=start+recordLen; eq>=start && txtRecord[eq]!='='; --eq) ;
		if (eq==start) {
		  key.assign(txtRecord, start+1, recordLen) ;
		} else if (eq==start+recordLen) {
		  key.assign(txtRecord, start+1, recordLen-1) ;
		} else {
		  key.assign(txtRecord, start+1, eq-start-1) ;
		  value.assign(txtRecord, eq+1, start+recordLen-eq) ;
		}
		e->service.textRecords[key] = value ;
	   }
	   start += recordLen+1 ;
	 }
    }

    static void browseCallBack(DNSServiceRef sdRef,
						 DNSServiceFlags flags,
						 uint32_t interfaceIndex,
						 DNSServiceErrorType errorCode,
						 const char *serviceName,
						 const char *replyType,
						 const char *replyDomain,
						 void *context) {
	 if (errorCode!=kDNSServiceErr_NoError) return ;

	 mDNSServiceFinder *finder = (mDNSServiceFinder*)context ;

	 Event *e = new Event ;
	 e->service.type = replyType ;
	 e->service.name = serviceName ;
	 e->service.domain = replyDomain ;

	 if (flags & kDNSServiceFlagsAdd) {
	   e->event = Event::FOUND ;
	   DNSServiceRef subref ; 	   
	   DNSServiceErrorType result = DNSServiceResolve(&subref, 0,
											interfaceIndex,
											serviceName,replyType,replyDomain,
											resolveCallBack,
											(void *)e) ;
	   if (result!=kDNSServiceErr_NoError)
		std::cerr << "mDNSServiceFinder::browseCallBack: DNSServiceResolve failed" << std::endl ;
	   else {
		result = DNSServiceProcessResult(subref) ;
		if (result!=kDNSServiceErr_NoError)
		  std::cerr << "mDNSServiceFinder::browseCallBack: DNSServiceProcessResult failed" << std::endl ;   
		DNSServiceRefDeallocate(subref) ;
	   }
	 } else {
	   e->event = Event::LOST ;
	 }

	 finder->queue->push(e) ;
	 finder->notifyObservers() ;
    }

    void react(Observable *obs) {
	 if (obs==fk && fk->getState()&FileKeeper::R) {
	   DNSServiceErrorType result = DNSServiceProcessResult(sdRef) ;
	   if (result!=kDNSServiceErr_NoError)
		std::cerr << "mDNSServiceFinder::react: DNSServiceProcessResult failed" << std::endl ;
	 }
    }

  public:

    mDNSServiceFinder(const char *regtype, EventQueue *queue, const char *domain=0)
    : ServiceFinder(queue) {

	 DNSServiceErrorType result = DNSServiceBrowse(&sdRef,
										  0, 0,
										  regtype, domain,
										  browseCallBack,
										  (void *)this); 
	 if (result!=kDNSServiceErr_NoError)
	   throw std::runtime_error("mDNSServiceFinder: DNSServiceBrowse failed") ;

	 fk = FileKeeper::create(DNSServiceRefSockFD(sdRef), FileKeeper::R) ;
	 subscribeTo(fk) ;
    }

    ~mDNSServiceFinder(void) {
	 unsubscribeFrom(fk) ;
	 delete fk ;
	 DNSServiceRefDeallocate(sdRef) ;
    }

  } ;

}

#endif
