/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2009 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "CDNSSDService.h"
#include "nsThreadUtils.h"
#include "nsIEventTarget.h"
#include "private/pprio.h"
#include <string>
#include <stdio.h>


NS_IMPL_ISUPPORTS2(CDNSSDService, IDNSSDService, nsIRunnable)

CDNSSDService::CDNSSDService()
:
	m_master( 1 ),
	m_threadPool( NULL ),
	m_mainRef( NULL ),
	m_subRef( NULL ),
	m_listener( NULL ),
	m_fileDesc( NULL ),
	m_job( NULL )
{
	nsresult err;

	if ( DNSServiceCreateConnection( &m_mainRef ) != kDNSServiceErr_NoError )
	{
		err = NS_ERROR_FAILURE;
		goto exit;
	}

	if ( ( m_fileDesc = PR_ImportTCPSocket( DNSServiceRefSockFD( m_mainRef ) ) ) == NULL )
	{
		err = NS_ERROR_FAILURE;
		goto exit;
	}

	if ( ( m_threadPool = PR_CreateThreadPool( 1, 1, 8192 ) ) == NULL )
	{
		err = NS_ERROR_FAILURE;
		goto exit;
	}
	
	err = SetupNotifications();

exit:

	if ( err != NS_OK )
	{
		Cleanup();
	}
}


CDNSSDService::CDNSSDService( DNSServiceRef ref, nsISupports * listener )
:
	m_master( 0 ),
	m_threadPool( NULL ),
	m_mainRef( ref ),
	m_subRef( ref ),
	m_listener( listener ),
	m_fileDesc( NULL ),
	m_job( NULL )
{
}


CDNSSDService::~CDNSSDService()
{
	Cleanup();
}


void
CDNSSDService::Cleanup()
{
	if ( m_master )
	{
		if ( m_job )
		{
			PR_CancelJob( m_job );
			m_job = NULL;
		}

		if ( m_threadPool != NULL )
		{	
			PR_ShutdownThreadPool( m_threadPool );
			m_threadPool = NULL;
		}
	
		if ( m_fileDesc != NULL )
		{
			PR_Close( m_fileDesc );
			m_fileDesc = NULL;
		}

		if ( m_mainRef )
		{
			DNSServiceRefDeallocate( m_mainRef );
			m_mainRef = NULL;
		}
	}
	else
	{
		if ( m_subRef )
		{
			DNSServiceRefDeallocate( m_subRef );
			m_subRef = NULL;
		}
	}
}


nsresult
CDNSSDService::SetupNotifications()
{
	NS_PRECONDITION( m_threadPool != NULL, "m_threadPool is NULL" );
	NS_PRECONDITION( m_fileDesc != NULL, "m_fileDesc is NULL" );
	NS_PRECONDITION( m_job == NULL, "m_job is not NULL" );

	m_iod.socket	= m_fileDesc;
	m_iod.timeout	= PR_INTERVAL_MAX;
	m_job			= PR_QueueJob_Read( m_threadPool, &m_iod, Read, this, PR_FALSE );	
	return ( m_job ) ? NS_OK : NS_ERROR_FAILURE;
}


/* IDNSSDService browse (in long interfaceIndex, in AString regtype, in AString domain, in IDNSSDBrowseListener listener); */
NS_IMETHODIMP
CDNSSDService::Browse(PRInt32 interfaceIndex, const nsAString & regtype, const nsAString & domain, IDNSSDBrowseListener *listener, IDNSSDService **_retval NS_OUTPARAM)
{
	CDNSSDService	*	service	= NULL;
	DNSServiceErrorType dnsErr	= 0;
	nsresult			err		= 0;

	*_retval = NULL;
	
	if ( !m_mainRef )
	{
		err = NS_ERROR_NOT_AVAILABLE;
		goto exit;
	}

	try
	{
		service = new CDNSSDService( m_mainRef, listener );
	}
	catch ( ... )
	{
		service = NULL;
	}
	
	if ( service == NULL )
	{
		err = NS_ERROR_FAILURE;
		goto exit;
	}
	
	dnsErr = DNSServiceBrowse( &service->m_subRef, kDNSServiceFlagsShareConnection, interfaceIndex, NS_ConvertUTF16toUTF8( regtype ).get(), NS_ConvertUTF16toUTF8( domain ).get(), ( DNSServiceBrowseReply ) BrowseReply, service );
	
	if ( dnsErr != kDNSServiceErr_NoError )
	{
		err = NS_ERROR_FAILURE;
		goto exit;
	}
	
	listener->AddRef();
	service->AddRef();
	*_retval = service;
	err = NS_OK;
	
exit:

	if ( err && service )
	{
		delete service;
		service = NULL;
	}
	
	return err;
}


/* IDNSSDService resolve (in long interfaceIndex, in AString name, in AString regtype, in AString domain, in IDNSSDResolveListener listener); */
NS_IMETHODIMP
CDNSSDService::Resolve(PRInt32 interfaceIndex, const nsAString & name, const nsAString & regtype, const nsAString & domain, IDNSSDResolveListener *listener, IDNSSDService **_retval NS_OUTPARAM)
{
    CDNSSDService	*	service;
	DNSServiceErrorType dnsErr;
	nsresult			err;

	*_retval = NULL;
	
	if ( !m_mainRef )
	{
		err = NS_ERROR_NOT_AVAILABLE;
		goto exit;
	}

	try
	{
		service = new CDNSSDService( m_mainRef, listener );
	}
	catch ( ... )
	{
		service = NULL;
	}
	
	if ( service == NULL )
	{
		err = NS_ERROR_FAILURE;
		goto exit;
	}

	dnsErr = DNSServiceResolve( &service->m_subRef, kDNSServiceFlagsShareConnection, interfaceIndex, NS_ConvertUTF16toUTF8( name ).get(), NS_ConvertUTF16toUTF8( regtype ).get(), NS_ConvertUTF16toUTF8( domain ).get(), ( DNSServiceResolveReply ) ResolveReply, service );
	
	if ( dnsErr != kDNSServiceErr_NoError )
	{
		err = NS_ERROR_FAILURE;
		goto exit;
	}
	
	listener->AddRef();
	service->AddRef();
	*_retval = service;
	err = NS_OK;
	
exit:
	
	if ( err && service )
	{
		delete service;
		service = NULL;
	}
	
	return err;
}


/* void stop (); */
NS_IMETHODIMP
CDNSSDService::Stop()
{
    if ( m_subRef )
	{
		DNSServiceRefDeallocate( m_subRef );
		m_subRef = NULL;
	}
	
	return NS_OK;
}


void
CDNSSDService::Read( void * arg )
{
	NS_PRECONDITION( arg != NULL, "arg is NULL" );
	
	NS_DispatchToMainThread( ( CDNSSDService* ) arg );
}


NS_IMETHODIMP
CDNSSDService::Run()
{
	nsresult err = NS_OK;
	
	NS_PRECONDITION( m_mainRef != NULL, "m_mainRef is NULL" );

	m_job = NULL;

	if ( PR_Available( m_fileDesc ) > 0 )
	{
		if ( DNSServiceProcessResult( m_mainRef ) != kDNSServiceErr_NoError )
		{
			err = NS_ERROR_FAILURE;
		}
	}

	if ( !err )
	{
		err = SetupNotifications();
	}
	
	return err;
}


void DNSSD_API
CDNSSDService::BrowseReply
		(
		DNSServiceRef		sdRef,
		DNSServiceFlags		flags,
		uint32_t			interfaceIndex,
		DNSServiceErrorType	errorCode,
		const char		*	serviceName,
		const char		*	regtype,
		const char		*	replyDomain,
		void			*	context
		)
{
	CDNSSDService * self = ( CDNSSDService* ) context;

	// This should never be NULL, but let's be defensive.
	
	if ( self != NULL )
	{
		IDNSSDBrowseListener * listener = ( IDNSSDBrowseListener* ) self->m_listener;

		// Same for this

		if ( listener != NULL )
		{
			listener->OnBrowse( self, ( flags & kDNSServiceFlagsAdd ) ? PR_TRUE : PR_FALSE, interfaceIndex, errorCode, NS_ConvertUTF8toUTF16( serviceName ), NS_ConvertUTF8toUTF16( regtype ), NS_ConvertUTF8toUTF16( replyDomain ) );
		}
	}
}


void DNSSD_API
CDNSSDService::ResolveReply
		(
		DNSServiceRef			sdRef,
		DNSServiceFlags			flags,
		uint32_t				interfaceIndex,
		DNSServiceErrorType		errorCode,
		const char			*	fullname,
		const char			*	hosttarget,
		uint16_t				port,
		uint16_t				txtLen,
		const unsigned char	*	txtRecord,
		void				*	context
		)
{
	CDNSSDService * self = ( CDNSSDService* ) context;
	
	// This should never be NULL, but let's be defensive.
	
	if ( self != NULL )
	{
		IDNSSDResolveListener * listener = ( IDNSSDResolveListener* ) self->m_listener;
		
		// Same for this

		if ( listener != NULL )
		{
			std::string		path = "";
			const void	*	value = NULL;
			uint8_t			valueLen = 0;

			value = TXTRecordGetValuePtr( txtLen, txtRecord, "path", &valueLen );
			
			if ( value && valueLen )
			{
				char * temp;
				
				temp = new char[ valueLen + 2 ];
				
				if ( temp )
				{
					char * dst = temp;

					memset( temp, 0, valueLen + 2 );

					if ( ( ( char* ) value )[ 0 ] != '/' )
					{
						*dst++ = '/';
					}

					memcpy( dst, value, valueLen );
					path = temp;
					delete [] temp;
				}
			}

			listener->OnResolve( self, interfaceIndex, errorCode, NS_ConvertUTF8toUTF16( fullname ), NS_ConvertUTF8toUTF16( hosttarget ) , ntohs( port ), NS_ConvertUTF8toUTF16( path.c_str() ) );
		}
	}
}

