#ifndef n_mDNSServiceAnnouncer_H
#define n_mDNSServiceAnnouncer_H

/*
 *
 * nucleo/network/rdv/mDNSServiceAnnouncer.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/ServiceAnnouncer.H>
#include <nucleo/core/ReactiveObject.H>
#include <nucleo/core/FileKeeper.H>

#include <dns_sd.h>

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

namespace nucleo {

  class mDNSServiceAnnouncer : public ServiceAnnouncer, public ReactiveObject {

  protected:

    DNSServiceRef sdRef ;
    FileKeeper *fk ;

    state status ;

    std::string name, regtype ;
    int port ;
    bool autorename ;
    std::string domain, host ;
    std::string textRecords ;

    static void registerCallback(DNSServiceRef sdRef,
						   DNSServiceFlags flags,
						   DNSServiceErrorType errorCode,
						   const char *name,
						   const char *regtype,
						   const char *domain,
						   void *context) {
	 mDNSServiceAnnouncer *obj = (mDNSServiceAnnouncer*)context ;
	 if (errorCode==kDNSServiceErr_NoError) {
	   obj->name = name ;
	   obj->status = ANNOUNCED ;
	 } else if (errorCode==kDNSServiceErr_NameConflict) {
	   obj->status = NAME_COLLISION ;
	 }
    }

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

    void start(void) {
	 uint16_t txtLen = textRecords.size() ;
	 const void *txtRecord = textRecords.c_str() ;
	 DNSServiceErrorType result = DNSServiceRegister(&sdRef,
										    autorename?kDNSServiceFlagsAutoRename:kDNSServiceFlagsNoAutoRename,
										    0,
										    name.c_str(), regtype.c_str(),
										    (domain==""?0:domain.c_str()),
										    (host==""?0:host.c_str()),
										    port,
										    txtLen, txtRecord,
										    registerCallback,
										    (void *)this) ;
	 if (result!=kDNSServiceErr_NoError)
	   throw std::runtime_error("mDNSServiceAnnouncer: DNSServiceRegister failed") ;

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

    void stop(void) {
	 unsubscribeFrom(fk) ;
	 delete fk ;
	 DNSServiceRefDeallocate(sdRef) ;
    }

  public:

    mDNSServiceAnnouncer(const char *n, const char *t, int p,
					TextRecords *tr=0,
					bool ar=true,
					const char *d=0, const char *h=0) {
	 status = UNKNOWN ;
	 name = n ; regtype = t ;
	 port = p ;
	 autorename = ar ;
	 if (d) domain = d ; if (h) host = h ;

	 if (tr) {
	   std::stringstream formatted ;
	   for (TextRecords::iterator pair=tr->begin();
		   pair!=tr->end();
		   ++pair) {
		std::string key = (*pair).first ;
		std::string value = (*pair).second ;
		unsigned char len = (unsigned char)(key.size()+1+value.size()) ;
		formatted << len << key << "=" << value ;
	   }
	   textRecords = formatted.str() ;
	 }

	 start() ;
    }

    ~mDNSServiceAnnouncer(void) {
	 stop() ;
    }

    state getState(void) {
	 return status ;
    }

    bool changeName(const char *newname) {
	 stop() ;

	 if (newname) {
	   name = newname ;
	 } else {
	   if (name[name.size()-1]!='+') name.append(" ") ;
	   name.append("+") ;
	 }
	 start() ;

	 return true ;
    }

    std::string getName(void) {
	 return name ;
    }

  } ;

}

#endif
