/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2002-2004 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	<assert.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<time.h>

#include	<algorithm>
#include	<memory>

#include	"stdafx.h"

#include	"DNSServices.h"

#include	"Application.h"
#include	"AboutDialog.h"
#include	"LoginDialog.h"
#include	"Resource.h"

#include	"ChooserDialog.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#if 0
#pragma mark == Constants ==
#endif

//===========================================================================================================================
//	Constants
//===========================================================================================================================

// Menus

enum
{
	kChooserMenuIndexFile	= 0, 
	kChooserMenuIndexHelp	= 1 
};

// Domain List
	
#define kDomainListDefaultDomainColumnWidth		 		164 
	
// Service List

#define kServiceListDefaultServiceColumnTypeWidth		146
#define kServiceListDefaultServiceColumnDescWidth		230
	
// Chooser List
	
#define kChooserListDefaultNameColumnWidth				190
#define kChooserListDefaultIPColumnWidth				120

// Windows User Messages

#define	WM_USER_DOMAIN_ADD								( WM_USER + 0x100 )
#define	WM_USER_DOMAIN_REMOVE							( WM_USER + 0x101 )
#define	WM_USER_SERVICE_ADD								( WM_USER + 0x102 )
#define	WM_USER_SERVICE_REMOVE							( WM_USER + 0x103 )
#define	WM_USER_RESOLVE									( WM_USER + 0x104 )

#if 0
#pragma mark == Constants - Service Table ==
#endif

//===========================================================================================================================
//	Constants - Service Table
//===========================================================================================================================

struct	KnownServiceEntry
{
	const char *		serviceType;
	const char *		description;
	const char *		urlScheme;
	bool				useText;
};

static const KnownServiceEntry		kKnownServiceTable[] =
{
	{ "_accountedge._tcp.",	 		"MYOB AccountEdge", 										"", 			false },
	{ "_aecoretech._tcp.", 			"Apple Application Engineering Services",					"", 			false },
	{ "_afpovertcp._tcp.", 			"Apple File Sharing (AFP)", 								"afp://", 		false },
	{ "_airport._tcp.", 			"AirPort Base Station",										"", 			false }, 
	{ "_apple-sasl._tcp.", 			"Apple Password Server", 									"", 			false },
	{ "_aquamon._tcp.", 			"AquaMon", 													"", 			false },
	{ "_async._tcp", 				"address-o-sync", 											"", 			false },
	{ "_auth._tcp.", 				"Authentication Service",									"", 			false },
	{ "_bootps._tcp.", 				"Bootstrap Protocol Server",								"", 			false },
	{ "_bousg._tcp.", 				"Bag Of Unusual Strategy Games",							"", 			false },
	{ "_browse._udp.", 				"DNS Service Discovery",									"", 			false },
	{ "_cheat._tcp.", 				"The Cheat",												"", 			false },
	{ "_chess._tcp", 				"Project Gridlock", 										"", 			false },
	{ "_chfts._tcp", 				"Fluid Theme Server", 										"", 			false },
	{ "_clipboard._tcp", 			"Clipboard Sharing", 										"", 			false },
	{ "_contactserver._tcp.", 		"Now Up-to-Date & Contact",									"", 			false },
	{ "_cvspserver._tcp", 			"CVS PServer", 												"", 			false },
	{ "_cytv._tcp.", 				"CyTV Network streaming for Elgato EyeTV",					"", 			false },
	{ "_daap._tcp.", 				"Digital Audio Access Protocol (iTunes)",					"daap://",		false }, 
	{ "_distcc._tcp", 				"Distributed Compiler", 									"", 			false },
	{ "_dns-sd._udp", 				"DNS Service Discovery", 									"", 			false },
	{ "_dpap._tcp.", 				"Digital Picture Access Protocol (iPhoto)",					"", 			false },
	{ "_earphoria._tcp.", 			"Earphoria",												"", 			false },
	{ "_ecbyesfsgksc._tcp.", 		"Net Monitor Anti-Piracy Service",							"",				false },
	{ "_eheap._tcp.", 				"Interactive Room Software",								"",				false },
	{ "_embrace._tcp.", 			"DataEnvoy",												"",				false },
	{ "_eppc._tcp.", 				"Remote AppleEvents", 										"eppc://", 		false }, 
	{ "_exec._tcp.", 				"Remote Process Execution",									"",				false },
	{ "_facespan._tcp.", 			"FaceSpan",													"",				false },
	{ "_fjork._tcp.", 				"Fjork",													"",				false },
	{ "_ftp._tcp.", 				"File Transfer (FTP)", 										"ftp://", 		false }, 
	{ "_ftpcroco._tcp.", 			"Crocodile FTP Server",										"",				false },
	{ "_gbs-smp._tcp.", 			"SnapMail",													"",				false },
	{ "_gbs-stp._tcp.", 			"SnapTalk",													"",				false },
	{ "_grillezvous._tcp.", 		"Roxio ToastAnywhere(tm) Recorder Sharing",					"",				false },
	{ "_h323._tcp.", 				"H.323",													"",				false },
	{ "_hotwayd._tcp", 				"Hotwayd", 													"", 			false },
	{ "_http._tcp.", 				"Web Server (HTTP)", 										"http://", 		true  }, 
	{ "_hydra._tcp", 				"SubEthaEdit", 												"", 			false },
	{ "_ica-networking._tcp.", 		"Image Capture Networking",									"", 			false }, 
	{ "_ichalkboard._tcp.", 		"iChalk",													"", 			false }, 
	{ "_ichat._tcp.", 				"iChat",				 									"ichat://",		false }, 
	{ "_iconquer._tcp.",	 		"iConquer",													"", 			false }, 
	{ "_imap._tcp.", 				"Internet Message Access Protocol",							"",				false },
	{ "_imidi._tcp.", 				"iMidi",													"",				false },
	{ "_ipp._tcp.", 				"Printer (IPP)", 											"ipp://", 		false },
	{ "_ishare._tcp.", 				"iShare",													"",				false },
	{ "_isparx._tcp.", 				"iSparx",													"",				false },
	{ "_istorm._tcp", 				"iStorm", 													"", 			false },
	{ "_iwork._tcp.", 				"iWork Server",												"",				false },
	{ "_liaison._tcp.", 			"Liaison",													"",				false },
	{ "_login._tcp.", 				"Remote Login a la Telnet",									"",				false },
	{ "_lontalk._tcp.", 			"LonTalk over IP (ANSI 852)",								"",				false },
	{ "_lonworks._tcp.", 			"Echelon LNS Remote Client",								"",				false },
	{ "_macfoh-remote._tcp.", 		"MacFOH Remote",											"",				false },
	{ "_moneyworks._tcp.", 			"MoneyWorks",												"",				false },
	{ "_mp3sushi._tcp", 			"MP3 Sushi", 												"", 			false },
	{ "_mttp._tcp.", 				"MenuTunes Sharing",										"",				false },
	{ "_ncbroadcast._tcp.", 		"Network Clipboard Broadcasts",								"",				false },
	{ "_ncdirect._tcp.", 			"Network Clipboard Direct Transfers",						"",				false },
	{ "_ncsyncserver._tcp.", 		"Network Clipboard Sync Server",							"",				false },
	{ "_newton-dock._tcp.", 		"Escale",													"",				false },
	{ "_nfs._tcp", 					"NFS", 														"", 			false },
	{ "_nssocketport._tcp.", 		"DO over NSSocketPort",										"",				false },
	{ "_omni-bookmark._tcp.", 		"OmniWeb",													"",				false },
	{ "_openbase._tcp.", 			"OpenBase SQL",												"",				false },
	{ "_p2pchat._tcp.", 			"Peer-to-Peer Chat",										"",				false },
	{ "_pdl-datastream._tcp.", 		"Printer (PDL)", 											"pdl://", 		false }, 
	{ "_poch._tcp.", 				"Parallel OperatiOn and Control Heuristic",					"",				false },
	{ "_pop_2_ambrosia._tcp.",		"Pop-Pop",													"",				false },
	{ "_pop3._tcp", 				"POP3 Server", 												"", 			false },
	{ "_postgresql._tcp", 			"PostgreSQL Server", 										"", 			false },
	{ "_presence._tcp", 			"iChat AV", 												"", 			false },
	{ "_printer._tcp.", 			"Printer (LPR)", 											"lpr://", 		false }, 
	{ "_ptp._tcp.", 				"Picture Transfer (PTP)", 									"ptp://", 		false },
	{ "_register._tcp", 			"DNS Service Discovery", 									"", 			false },
	{ "_rfb._tcp.", 				"Remote Frame Buffer",										"",				false },
	{ "_riousbprint._tcp.", 		"Remote I/O USB Printer Protocol",							"",				false },
	{ "_rtsp._tcp.", 				"Real Time Stream Control Protocol",						"",				false },
	{ "_safarimenu._tcp", 			"Safari Menu", 												"", 			false },
	{ "_scone._tcp", 				"Scone", 													"", 			false },
	{ "_sdsharing._tcp.", 			"Speed Download",											"",				false },
	{ "_seeCard._tcp.", 			"seeCard",													"",				false },
	{ "_services._udp.", 			"DNS Service Discovery",									"",				false },
	{ "_shell._tcp.", 				"like exec, but automatic authentication",					"",				false },
	{ "_shout._tcp.", 				"Shout",													"",				false },
	{ "_shoutcast._tcp", 			"Nicecast", 												"", 			false },
	{ "_smb._tcp.", 				"Windows File Sharing (SMB)", 								"smb://", 		false }, 
	{ "_soap._tcp.", 				"Simple Object Access Protocol", 							"", 			false }, 
	{ "_spincrisis._tcp.", 			"Spin Crisis",												"",				false },
	{ "_spl-itunes._tcp.", 			"launchTunes",												"",				false },
	{ "_spr-itunes._tcp.", 			"netTunes",													"",				false },
	{ "_ssh._tcp.", 				"Secure Shell (SSH)", 										"ssh://", 		false }, 
	{ "_ssscreenshare._tcp", 		"Screen Sharing", 											"", 			false },
	{ "_sge-exec._tcp", 			"Sun Grid Engine (Execution Host)", 						"", 			false },
	{ "_sge-qmaster._tcp", 			"Sun Grid Engine (Master)", 								"", 			false },
	{ "_stickynotes._tcp", 			"Sticky Notes", 											"", 			false },
	{ "_strateges._tcp", 			"Strateges", 												"", 			false },
	{ "_sxqdea._tcp", 				"Synchronize! Pro X", 										"", 			false },
	{ "_sybase-tds._tcp", 			"Sybase Server", 											"", 			false },
	{ "_tce._tcp", 					"Power Card", 												"", 			false },
	{ "_teamlist._tcp", 			"ARTIS Team Task",											"", 			false },
	{ "_teleport._tcp", 			"teleport",													"", 			false },
	{ "_telnet._tcp.", 				"Telnet", 													"telnet://", 	false }, 
	{ "_tftp._tcp.", 				"Trivial File Transfer (TFTP)", 							"tftp://", 		false }, 
	{ "_tinavigator._tcp.", 		"TI Navigator", 											"", 			false }, 
	{ "_tivo_servemedia._tcp", 		"TiVo",														"", 			false },
	{ "_upnp._tcp.", 				"Universal Plug and Play", 									"", 			false }, 
	{ "_utest._tcp.", 				"uTest", 													"", 			false }, 
	{ "_vue4rendercow._tcp",		"VueProRenderCow",											"", 			false },
	{ "_webdav._tcp.", 				"WebDAV", 													"webdav://",	false }, 
	{ "_whamb._tcp.", 				"Whamb", 													"",				false }, 
	{ "_workstation._tcp", 			"Macintosh Manager",										"", 			false },
	{ "_ws._tcp", 					"Web Services",												"", 			false },
	{ "_xserveraid._tcp.", 			"Xserve RAID",												"xsr://", 		false }, 
	{ "_xsync._tcp.",	 			"Xserve RAID Synchronization",								"",		 		false }, 
	
	{ "",	 						"",															"",		 		false }, 
	
	// Unofficial and invalid service types that will be phased out:
	
	{ "_clipboardsharing._tcp.",			"ClipboardSharing",									"",		 		false }, 
	{ "_MacOSXDupSuppress._tcp.",			"Mac OS X Duplicate Suppression",					"",		 		false }, 
	{ "_netmonitorserver._tcp.",			"Net Monitor Server",								"",		 		false }, 
	{ "_networkclipboard._tcp.",			"Network Clipboard",								"",		 		false }, 
	{ "_slimdevices_slimp3_cli._tcp.",		"SliMP3 Server Command-Line Interface",				"",		 		false }, 
	{ "_slimdevices_slimp3_http._tcp.",		"SliMP3 Server Web Interface",						"",		 		false }, 
	{ "_tieducationalhandhelddevice._tcp.",	"TI Connect Manager",								"",		 		false }, 
	{ "_tivo_servemedia._tcp.",				"TiVo",												"",		 		false }, 
	
	{ NULL,							NULL,														NULL,			false }, 
};

#if 0
#pragma mark == Structures ==
#endif

//===========================================================================================================================
//	Structures
//===========================================================================================================================

struct	DomainEventInfo
{
	DNSBrowserEventType		eventType;
	CString					domain;
	DNSNetworkAddress		ifIP;
};

struct	ServiceEventInfo
{
	DNSBrowserEventType		eventType;
	std::string				name;
	std::string				type;
	std::string				domain;
	DNSNetworkAddress		ifIP;
};

#if 0
#pragma mark == Prototypes ==
#endif

//===========================================================================================================================
//	Prototypes
//===========================================================================================================================

static void
	BrowserCallBack( 
		void *					inContext, 
		DNSBrowserRef			inRef, 
		DNSStatus				inStatusCode,
		const DNSBrowserEvent *	inEvent );

static char *	DNSNetworkAddressToString( const DNSNetworkAddress *inAddr, char *outString );

static DWORD	UTF8StringToStringObject( const char *inUTF8, CString &inObject );
static DWORD	StringObjectToUTF8String( CString &inObject, std::string &outUTF8 );

#if 0
#pragma mark == Message Map ==
#endif

//===========================================================================================================================
//	Message Map
//===========================================================================================================================

BEGIN_MESSAGE_MAP(ChooserDialog, CDialog)
	//{{AFX_MSG_MAP(ChooserDialog)
	ON_WM_SYSCOMMAND()
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_DOMAIN_LIST, OnDomainListChanged)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_SERVICE_LIST, OnServiceListChanged)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_CHOOSER_LIST, OnChooserListChanged)
	ON_NOTIFY(NM_DBLCLK, IDC_CHOOSER_LIST, OnChooserListDoubleClick)
	ON_COMMAND(ID_HELP_ABOUT, OnAbout)
	ON_WM_INITMENUPOPUP()
	ON_WM_ACTIVATE()
	ON_COMMAND(ID_FILE_CLOSE, OnFileClose)
	ON_COMMAND(ID_FILE_EXIT, OnExit)
	ON_WM_CLOSE()
	ON_WM_NCDESTROY()
	//}}AFX_MSG_MAP
	ON_MESSAGE( WM_USER_DOMAIN_ADD, OnDomainAdd )
	ON_MESSAGE( WM_USER_DOMAIN_REMOVE, OnDomainRemove )
	ON_MESSAGE( WM_USER_SERVICE_ADD, OnServiceAdd )
	ON_MESSAGE( WM_USER_SERVICE_REMOVE, OnServiceRemove )
	ON_MESSAGE( WM_USER_RESOLVE, OnResolve )
END_MESSAGE_MAP()

#if 0
#pragma mark == Routines ==
#endif

//===========================================================================================================================
//	ChooserDialog
//===========================================================================================================================

ChooserDialog::ChooserDialog( CWnd *inParent )
	: CDialog( ChooserDialog::IDD, inParent)
{
	//{{AFX_DATA_INIT(ChooserDialog)
		// Note: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	
	// Load menu accelerator table.

	mMenuAcceleratorTable = ::LoadAccelerators( AfxGetInstanceHandle(), MAKEINTRESOURCE( IDR_CHOOSER_DIALOG_MENU_ACCELERATORS ) );
	assert( mMenuAcceleratorTable );
	
	mBrowser 			= NULL;
	mIsServiceBrowsing	= false;
}

//===========================================================================================================================
//	~ChooserDialog
//===========================================================================================================================

ChooserDialog::~ChooserDialog( void )
{
	if( mBrowser )
	{
		DNSStatus		err;
		
		err = DNSBrowserRelease( mBrowser, 0 );
		assert( err == kDNSNoErr );
	}
}

//===========================================================================================================================
//	DoDataExchange
//===========================================================================================================================

void ChooserDialog::DoDataExchange( CDataExchange *pDX )
{
	CDialog::DoDataExchange(pDX);

	//{{AFX_DATA_MAP(ChooserDialog)
	DDX_Control(pDX, IDC_SERVICE_LIST, mServiceList);
	DDX_Control(pDX, IDC_DOMAIN_LIST, mDomainList);
	DDX_Control(pDX, IDC_CHOOSER_LIST, mChooserList);
	//}}AFX_DATA_MAP
}

//===========================================================================================================================
//	OnInitDialog
//===========================================================================================================================

BOOL	ChooserDialog::OnInitDialog( void )
{
	HICON			icon;
	BOOL			result;
	CString			tempString;
	DNSStatus		err;
	
	// Initialize our parent.

	CDialog::OnInitDialog();
	
	// Set up the window icon.
	
	icon = AfxGetApp()->LoadIcon( IDR_MAIN_ICON );
	assert( icon );
	if( icon )
	{
		SetIcon( icon, TRUE );		// Set big icon
		SetIcon( icon, FALSE );		// Set small icon
	}
	
	// Set up the Domain List.
	
	result = tempString.LoadString( IDS_CHOOSER_DOMAIN_COLUMN_NAME );
	assert( result );
	mDomainList.InsertColumn( 0, tempString, LVCFMT_LEFT, kDomainListDefaultDomainColumnWidth );
	
	// Set up the Service List.
	
	result = tempString.LoadString( IDS_CHOOSER_SERVICE_COLUMN_TYPE );
	assert( result );
	mServiceList.InsertColumn( 0, tempString, LVCFMT_LEFT, kServiceListDefaultServiceColumnTypeWidth );
	
	result = tempString.LoadString( IDS_CHOOSER_SERVICE_COLUMN_DESC );
	assert( result );
	mServiceList.InsertColumn( 1, tempString, LVCFMT_LEFT, kServiceListDefaultServiceColumnDescWidth );
	
	PopulateServicesList();
	
	// Set up the Chooser List.
	
	result = tempString.LoadString( IDS_CHOOSER_CHOOSER_NAME_COLUMN_NAME );
	assert( result );
	mChooserList.InsertColumn( 0, tempString, LVCFMT_LEFT, kChooserListDefaultNameColumnWidth );
	
	result = tempString.LoadString( IDS_CHOOSER_CHOOSER_IP_COLUMN_NAME );
	assert( result );
	mChooserList.InsertColumn( 1, tempString, LVCFMT_LEFT, kChooserListDefaultIPColumnWidth );
	
	// Set up the other controls.
	
	UpdateInfoDisplay();
	
	// Start browsing for domains.
	
	err = DNSBrowserCreate( 0, BrowserCallBack, this, &mBrowser );
	assert( err == kDNSNoErr );
	
	err = DNSBrowserStartDomainSearch( mBrowser, 0 );
	assert( err == kDNSNoErr );
	
	return( true );
}

//===========================================================================================================================
//	OnFileClose
//===========================================================================================================================

void ChooserDialog::OnFileClose() 
{
	OnClose();
}

//===========================================================================================================================
//	OnActivate
//===========================================================================================================================

void ChooserDialog::OnActivate( UINT nState, CWnd* pWndOther, BOOL bMinimized )
{
	// Always make the active window the "main" window so modal dialogs work better and the app quits after closing 
	// the last window.

	gApp.m_pMainWnd = this;

	CDialog::OnActivate(nState, pWndOther, bMinimized);
}

//===========================================================================================================================
//	PostNcDestroy
//===========================================================================================================================

void	ChooserDialog::PostNcDestroy() 
{
	// Call the base class to do the normal cleanup.

	delete this;
}

//===========================================================================================================================
//	PreTranslateMessage
//===========================================================================================================================

BOOL	ChooserDialog::PreTranslateMessage(MSG* pMsg) 
{
	BOOL		result;
	
	result = false;
	assert( mMenuAcceleratorTable );
	if( mMenuAcceleratorTable )
	{
		result = ::TranslateAccelerator( m_hWnd, mMenuAcceleratorTable, pMsg );
	}
	if( !result )
	{
		result = CDialog::PreTranslateMessage( pMsg );
	}
	return( result );
}

//===========================================================================================================================
//	OnInitMenuPopup
//===========================================================================================================================

void	ChooserDialog::OnInitMenuPopup( CMenu *pPopupMenu, UINT nIndex, BOOL bSysMenu ) 
{
	CDialog::OnInitMenuPopup( pPopupMenu, nIndex, bSysMenu );

	switch( nIndex )
	{
		case kChooserMenuIndexFile:
			break;

		case kChooserMenuIndexHelp:
			break;

		default:
			break;
	}
}

//===========================================================================================================================
//	OnExit
//===========================================================================================================================

void ChooserDialog::OnExit() 
{
	OnClose();
}

//===========================================================================================================================
//	OnAbout
//===========================================================================================================================

void	ChooserDialog::OnAbout() 
{
	AboutDialog		dialog;
	
	dialog.DoModal();
}

//===========================================================================================================================
//	OnSysCommand
//===========================================================================================================================

void	ChooserDialog::OnSysCommand( UINT inID, LPARAM inParam ) 
{
	CDialog::OnSysCommand( inID, inParam );
}

//===========================================================================================================================
//	OnClose
//===========================================================================================================================

void ChooserDialog::OnClose() 
{
	StopBrowsing();
	
	gApp.m_pMainWnd = this;
	DestroyWindow();
}

//===========================================================================================================================
//	OnNcDestroy
//===========================================================================================================================

void ChooserDialog::OnNcDestroy() 
{
	gApp.m_pMainWnd = this;

	CDialog::OnNcDestroy();
}

//===========================================================================================================================
//	OnDomainListChanged
//===========================================================================================================================

void	ChooserDialog::OnDomainListChanged( NMHDR *pNMHDR, LRESULT *pResult ) 
{
	UNUSED_ALWAYS( pNMHDR );
	
	// Domain list changes have similar effects to service list changes so reuse that code path by calling it here.
	
	OnServiceListChanged( NULL, NULL );
	
	*pResult = 0;
}

//===========================================================================================================================
//	OnServiceListChanged
//===========================================================================================================================

void	ChooserDialog::OnServiceListChanged( NMHDR *pNMHDR, LRESULT *pResult ) 
{
	int				selectedType;
	int				selectedDomain;
	
	UNUSED_ALWAYS( pNMHDR );
	
	// Stop any existing service search.
	
	StopBrowsing();
	
	// If a domain and service type are selected, start searching for the service type on the domain.
	
	selectedType 	= mServiceList.GetNextItem( -1, LVNI_SELECTED );
	selectedDomain 	= mDomainList.GetNextItem( -1, LVNI_SELECTED );
	
	if( ( selectedType >= 0 ) && ( selectedDomain >= 0 ) )
	{
		CString				s;
		std::string			utf8;
		const char *		type;
		
		s = mDomainList.GetItemText( selectedDomain, 0 );
		StringObjectToUTF8String( s, utf8 );
		type = mServiceTypes[ selectedType ].serviceType.c_str();
		if( *type != '\0' )
		{
			StartBrowsing( type, utf8.c_str() );
		}
	}
	
	if( pResult )
	{
		*pResult = 0;
	}
}

//===========================================================================================================================
//	OnChooserListChanged
//===========================================================================================================================

void	ChooserDialog::OnChooserListChanged( NMHDR *pNMHDR, LRESULT *pResult ) 
{
	UNUSED_ALWAYS( pNMHDR );
	
	UpdateInfoDisplay();
	*pResult = 0;
}

//===========================================================================================================================
//	OnChooserListDoubleClick
//===========================================================================================================================

void	ChooserDialog::OnChooserListDoubleClick( NMHDR *pNMHDR, LRESULT *pResult )
{
	int		selectedItem;
	
	UNUSED_ALWAYS( pNMHDR );
	
	// Display the service instance if it is selected. Otherwise, clear all the info.
	
	selectedItem = mChooserList.GetNextItem( -1, LVNI_SELECTED );
	if( selectedItem >= 0 )
	{
		ServiceInstanceInfo *			p;
		CString							url;
		const KnownServiceEntry *		service;
		
		assert( selectedItem < (int) mServiceInstances.size() );
		p = &mServiceInstances[ selectedItem ];
		
		// Search for a known service type entry that matches.
		
		for( service = kKnownServiceTable; service->serviceType; ++service )
		{
			if( p->type == service->serviceType )
			{
				break;
			}
		}
		if( service->serviceType )
		{
			const char *		text;
			
			// Create a URL representing the service instance.
			
			if( strcmp( service->serviceType, "_smb._tcp." ) == 0 )
			{
				// Special case for SMB (no port number).
				
				url.Format( TEXT( "%s%s/" ), service->urlScheme, (const char *) p->ip.c_str() ); 
			}
			else if( strcmp( service->serviceType, "_ftp._tcp." ) == 0 )
			{
				// Special case for FTP to get login info.

				LoginDialog		dialog;
				CString			username;
				CString			password;
				
				if( !dialog.GetLogin( username, password ) )
				{
					goto exit;
				}
				
				// Build URL in the following format:
				//
				// ftp://[username[:password]@]<ip>
				
				url += service->urlScheme;
				if( username.GetLength() > 0 )
				{
					url += username;
					if( password.GetLength() > 0 )
					{
						url += ':';
						url += password;
					}
					url += '@';
				}
				url += p->ip.c_str();
			}
			else if( strcmp( service->serviceType, "_http._tcp." ) == 0 )
			{
				// Special case for HTTP to exclude "path=" if present.
				
				text = service->useText ? p->text.c_str() : "";
				if( strncmp( text, "path=", 5 ) == 0 )
				{
					text += 5;
				}
				if( *text != '/' )
				{
					url.Format( TEXT( "%s%s/%s" ), service->urlScheme, (const char *) p->ip.c_str(), text );
				}
				else
				{
					url.Format( TEXT( "%s%s%s" ), service->urlScheme, (const char *) p->ip.c_str(), text );
				}
			}
			else
			{
				text = service->useText ? p->text.c_str() : "";
				url.Format( TEXT( "%s%s/%s" ), service->urlScheme, (const char *) p->ip.c_str(), text ); 
			}
			
			// Let the system open the URL in the correct app.
			
			{
				CWaitCursor		waitCursor;
				
				ShellExecute( NULL, TEXT( "open" ), url, TEXT( "" ), TEXT( "c:\\" ), SW_SHOWNORMAL );
			}
		}
	}

exit:
	*pResult = 0;
}

//===========================================================================================================================
//	OnCancel
//===========================================================================================================================

void ChooserDialog::OnCancel() 
{
	// Do nothing.
}

//===========================================================================================================================
//	PopulateServicesList
//===========================================================================================================================

void	ChooserDialog::PopulateServicesList( void )
{
	ServiceTypeVector::iterator		i;
	CString							type;
	CString							desc;
	std::string						tmp;
	
	// Add a fixed list of known services.
	
	if( mServiceTypes.empty() )
	{
		const KnownServiceEntry *		service;
		
		for( service = kKnownServiceTable; service->serviceType; ++service )
		{
			ServiceTypeInfo		info;
			
			info.serviceType 	= service->serviceType;
			info.description 	= service->description;
			info.urlScheme 		= service->urlScheme;
			mServiceTypes.push_back( info );
		}
	}
	
	// Add each service to the list.
	
	for( i = mServiceTypes.begin(); i != mServiceTypes.end(); ++i )
	{
		const char *		p;
		const char *		q;
		
		p  = ( *i ).serviceType.c_str();
		if( *p == '_' ) ++p;							// Skip leading '_'.
		q  = strchr( p, '.' );							// Find first '.'.
		if( q )	tmp.assign( p, (size_t)( q - p ) );		// Use only up to the first '.'.
		else	tmp.assign( p );						// No '.' so use the entire string.
		UTF8StringToStringObject( tmp.c_str(), type );
		UTF8StringToStringObject( ( *i ).description.c_str(), desc );
		
		int		n;
		
		n = mServiceList.GetItemCount();
		mServiceList.InsertItem( n, type );
		mServiceList.SetItemText( n, 1, desc );
	}
	
	// Select the first service type by default.
	
	if( !mServiceTypes.empty() )
	{
		mServiceList.SetItemState( 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
	}
}

//===========================================================================================================================
//	UpdateInfoDisplay
//===========================================================================================================================

void	ChooserDialog::UpdateInfoDisplay( void )
{
	int							selectedItem;
	std::string					name;
	CString						s;
	std::string					ip;
	std::string					ifIP;
	std::string					text;
	std::string					textNewLines;
	std::string					hostName;
	CWnd *						item;
	std::string::iterator		i;
	
	// Display the service instance if it is selected. Otherwise, clear all the info.
	
	selectedItem = mChooserList.GetNextItem( -1, LVNI_SELECTED );
	if( selectedItem >= 0 )
	{
		ServiceInstanceInfo *		p;
		
		assert( selectedItem < (int) mServiceInstances.size() );
		p = &mServiceInstances[ selectedItem ];
		
		name 		= p->name;
		ip 			= p->ip;
		ifIP 		= p->ifIP;
		text 		= p->text;
		hostName	= p->hostName;

		// Sync up the list items with the actual data (IP address may change).
		
		UTF8StringToStringObject( ip.c_str(), s );
		mChooserList.SetItemText( selectedItem, 1, s );
	}
	
	// Name
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_NAME_TEXT );
	assert( item );
	UTF8StringToStringObject( name.c_str(), s );
	item->SetWindowText( s );
	
	// IP
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_IP_TEXT );
	assert( item );
	UTF8StringToStringObject( ip.c_str(), s );
	item->SetWindowText( s );
	
	// Interface
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_INTERFACE_TEXT );
	assert( item );
	UTF8StringToStringObject( ifIP.c_str(), s );
	item->SetWindowText( s );
	

	item = (CWnd *) this->GetDlgItem( IDC_INFO_HOST_NAME_TEXT );
	assert( item );
	UTF8StringToStringObject( hostName.c_str(), s );
	item->SetWindowText( s );

	// Text
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_TEXT_TEXT );
	assert( item );
	for( i = text.begin(); i != text.end(); ++i )
	{
		if( *i == '\1' )
		{
			textNewLines += "\r\n";
		}
		else
		{
			textNewLines += *i;
		}
	}
	UTF8StringToStringObject( textNewLines.c_str(), s );
	item->SetWindowText( s );
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	OnDomainAdd
//===========================================================================================================================

LONG	ChooserDialog::OnDomainAdd( WPARAM inWParam, LPARAM inLParam )
{
	DomainEventInfo *						p;
	std::auto_ptr < DomainEventInfo >		pAutoPtr;
	int										n;
	int										i;
	CString									domain;
	CString									s;
	bool									found;
	
	UNUSED_ALWAYS( inWParam );
	
	assert( inLParam );
	p = reinterpret_cast <DomainEventInfo *> ( inLParam );
	pAutoPtr.reset( p );
	
	// Search to see if we already know about this domain. If not, add it to the list.
	
	found = false;
	domain = p->domain;
	n = mDomainList.GetItemCount();
	for( i = 0; i < n; ++i )
	{
		s = mDomainList.GetItemText( i, 0 );
		if( s == domain )
		{
			found = true;
			break;
		}
	}
	if( !found )
	{
		int		selectedItem;
		
		mDomainList.InsertItem( n, domain );
		
		// If no domains are selected and the domain being added is a default domain, select it.
		
		selectedItem = mDomainList.GetNextItem( -1, LVNI_SELECTED );
		if( ( selectedItem < 0 ) && ( p->eventType == kDNSBrowserEventTypeAddDefaultDomain ) )
		{
			mDomainList.SetItemState( n, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
		}
	}
	return( 0 );
}

//===========================================================================================================================
//	OnDomainRemove
//===========================================================================================================================

LONG	ChooserDialog::OnDomainRemove( WPARAM inWParam, LPARAM inLParam )
{
	DomainEventInfo *						p;
	std::auto_ptr < DomainEventInfo >		pAutoPtr;
	int										n;
	int										i;
	CString									domain;
	CString									s;
	bool									found;
	
	UNUSED_ALWAYS( inWParam );
	
	assert( inLParam );
	p = reinterpret_cast <DomainEventInfo *> ( inLParam );
	pAutoPtr.reset( p );
	
	// Search to see if we know about this domain. If so, remove it from the list.
	
	found = false;
	domain = p->domain;
	n = mDomainList.GetItemCount();
	for( i = 0; i < n; ++i )
	{
		s = mDomainList.GetItemText( i, 0 );
		if( s == domain )
		{
			found = true;
			break;
		}
	}
	if( found )
	{
		mDomainList.DeleteItem( i );
	}
	return( 0 );
}

//===========================================================================================================================
//	OnServiceAdd
//===========================================================================================================================

LONG	ChooserDialog::OnServiceAdd( WPARAM inWParam, LPARAM inLParam )
{
	ServiceEventInfo *						p;
	std::auto_ptr < ServiceEventInfo >		pAutoPtr;
	
	UNUSED_ALWAYS( inWParam );
	
	assert( inLParam );
	p = reinterpret_cast <ServiceEventInfo *> ( inLParam );
	pAutoPtr.reset( p );
	
	return( 0 );
}

//===========================================================================================================================
//	OnServiceRemove
//===========================================================================================================================

LONG	ChooserDialog::OnServiceRemove( WPARAM inWParam, LPARAM inLParam )
{
	ServiceEventInfo *						p;
	std::auto_ptr < ServiceEventInfo >		pAutoPtr;
	bool									found;
	int										n;
	int										i;
	
	UNUSED_ALWAYS( inWParam );
	
	assert( inLParam );
	p = reinterpret_cast <ServiceEventInfo *> ( inLParam );
	pAutoPtr.reset( p );
	
	// Search to see if we know about this service instance. If so, remove it from the list.
	
	found = false;
	n = (int) mServiceInstances.size();
	for( i = 0; i < n; ++i )
	{
		ServiceInstanceInfo *		q;
		
		// If the name, type, domain, and interface match, treat it as the same service instance.
		
		q = &mServiceInstances[ i ];
		if( ( p->name 	== q->name ) 	&& 
			( p->type 	== q->type ) 	&& 
			( p->domain	== q->domain ) )
		{
			found = true;
			break;
		}
	}
	if( found )
	{
		mChooserList.DeleteItem( i );
		assert( i < (int) mServiceInstances.size() );
		mServiceInstances.erase( mServiceInstances.begin() + i );
	}
	return( 0 );
}

//===========================================================================================================================
//	OnResolve
//===========================================================================================================================

LONG	ChooserDialog::OnResolve( WPARAM inWParam, LPARAM inLParam )
{
	ServiceInstanceInfo *						p;
	std::auto_ptr < ServiceInstanceInfo >		pAutoPtr;
	int											selectedType;
	int											n;
	int											i;
	bool										found;
	
	UNUSED_ALWAYS( inWParam );
	
	assert( inLParam );
	p = reinterpret_cast <ServiceInstanceInfo *> ( inLParam );
	pAutoPtr.reset( p );
	
	// Make sure it is for an item of the correct type. This handles any resolves that may have been queued up.
	
	selectedType = mServiceList.GetNextItem( -1, LVNI_SELECTED );
	assert( selectedType >= 0 );
	if( selectedType >= 0 )
	{
		assert( selectedType <= (int) mServiceTypes.size() );
		if( p->type != mServiceTypes[ selectedType ].serviceType )
		{
			goto exit;
		}
	}
	
	// Search to see if we know about this service instance. If so, update its info. Otherwise, add it to the list.
	
	found = false;
	n = (int) mServiceInstances.size();
	for( i = 0; i < n; ++i )
	{
		ServiceInstanceInfo *		q;
		
		// If the name, type, domain, and interface matches, treat it as the same service instance.
				
		q = &mServiceInstances[ i ];
		if( ( p->name 	== q->name ) 	&& 
			( p->type 	== q->type ) 	&& 
			( p->domain	== q->domain ) 	&& 
			( p->ifIP 	== q->ifIP ) )
		{
			found = true;
			break;
		}
	}
	if( found )
	{
		mServiceInstances[ i ] = *p;
	}
	else
	{
		CString		s;
		
		mServiceInstances.push_back( *p );
		UTF8StringToStringObject( p->name.c_str(), s );
		mChooserList.InsertItem( n, s );
		
		UTF8StringToStringObject( p->ip.c_str(), s );
		mChooserList.SetItemText( n, 1, s );
		
		// If this is the only item, select it.
		
		if( n == 0 )
		{
			mChooserList.SetItemState( n, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
		}
	}
	UpdateInfoDisplay();

exit:
	return( 0 );
}

//===========================================================================================================================
//	StartBrowsing
//===========================================================================================================================

void	ChooserDialog::StartBrowsing( const char *inType, const char *inDomain )
{
	DNSStatus		err;
	
	assert( mServiceInstances.empty() );
	assert( mChooserList.GetItemCount() == 0 );
	assert( !mIsServiceBrowsing );
	
	mChooserList.DeleteAllItems();
	mServiceInstances.clear();
	
	mIsServiceBrowsing = true;
	err = DNSBrowserStartServiceSearch( mBrowser, kDNSBrowserFlagAutoResolve, inType, inDomain );
	assert( err == kDNSNoErr );
}

//===========================================================================================================================
//	StopBrowsing
//===========================================================================================================================

void	ChooserDialog::StopBrowsing( void )
{
	// If searching, stop.
	
	if( mIsServiceBrowsing )
	{
		DNSStatus		err;
		
		mIsServiceBrowsing = false;
		err = DNSBrowserStopServiceSearch( mBrowser, 0 );
		assert( err == kDNSNoErr );
	}
	
	// Remove all service instances.
	
	mChooserList.DeleteAllItems();
	assert( mChooserList.GetItemCount() == 0 );
	mServiceInstances.clear();
	assert( mServiceInstances.empty() );
	UpdateInfoDisplay();
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	BrowserCallBack
//===========================================================================================================================

static void
	BrowserCallBack( 
		void *					inContext, 
		DNSBrowserRef			inRef, 
		DNSStatus				inStatusCode,
		const DNSBrowserEvent *	inEvent )
{
	ChooserDialog *		dialog;
	UINT 				message;
	BOOL				posted;
	
	UNUSED_ALWAYS( inStatusCode );
	UNUSED_ALWAYS( inRef );
	
	// Check parameters.
	
	assert( inContext );
	dialog = reinterpret_cast <ChooserDialog *> ( inContext );
	
	try
	{
		switch( inEvent->type )
		{
			case kDNSBrowserEventTypeRelease:
				break;
			
			// Domains
			
			case kDNSBrowserEventTypeAddDomain:
			case kDNSBrowserEventTypeAddDefaultDomain:
			case kDNSBrowserEventTypeRemoveDomain:
			{
				DomainEventInfo *						domain;
				std::auto_ptr < DomainEventInfo >		domainAutoPtr;
				
				domain = new DomainEventInfo;
				domainAutoPtr.reset( domain );
				
				domain->eventType 	= inEvent->type;
				domain->domain 		= inEvent->data.addDomain.domain;
				domain->ifIP		= inEvent->data.addDomain.interfaceIP;
				
				message = ( inEvent->type == kDNSBrowserEventTypeRemoveDomain ) ? WM_USER_DOMAIN_REMOVE : WM_USER_DOMAIN_ADD;
				posted = ::PostMessage( dialog->GetSafeHwnd(), message, 0, (LPARAM) domain );
				assert( posted );
				if( posted )
				{
					domainAutoPtr.release();
				}
				break;
			}
			
			// Services
			
			case kDNSBrowserEventTypeAddService:
			case kDNSBrowserEventTypeRemoveService:
			{
				ServiceEventInfo *						service;
				std::auto_ptr < ServiceEventInfo >		serviceAutoPtr;
				
				service = new ServiceEventInfo;
				serviceAutoPtr.reset( service );
				
				service->eventType 	= inEvent->type;
				service->name 		= inEvent->data.addService.name;
				service->type 		= inEvent->data.addService.type;
				service->domain		= inEvent->data.addService.domain;
				service->ifIP		= inEvent->data.addService.interfaceIP;
				
				message = ( inEvent->type == kDNSBrowserEventTypeAddService ) ? WM_USER_SERVICE_ADD : WM_USER_SERVICE_REMOVE;
				posted = ::PostMessage( dialog->GetSafeHwnd(), message, 0, (LPARAM) service );
				assert( posted );
				if( posted )
				{
					serviceAutoPtr.release();
				}
				break;
			}
			
			// Resolves
			
			case kDNSBrowserEventTypeResolved:
				if( inEvent->data.resolved->address.addressType == kDNSNetworkAddressTypeIPv4  )
				{
					ServiceInstanceInfo *						serviceInstance;
					std::auto_ptr < ServiceInstanceInfo >		serviceInstanceAutoPtr;
					char										s[ 32 ];
					
					serviceInstance = new ServiceInstanceInfo;
					serviceInstanceAutoPtr.reset( serviceInstance );
					
					serviceInstance->name 		= inEvent->data.resolved->name;
					serviceInstance->type 		= inEvent->data.resolved->type;
					serviceInstance->domain		= inEvent->data.resolved->domain;
					serviceInstance->ip			= DNSNetworkAddressToString( &inEvent->data.resolved->address, s );
					serviceInstance->ifIP		= DNSNetworkAddressToString( &inEvent->data.resolved->interfaceIP, s );
					serviceInstance->text 		= inEvent->data.resolved->textRecord;
					serviceInstance->hostName	= inEvent->data.resolved->hostName;

					posted = ::PostMessage( dialog->GetSafeHwnd(), WM_USER_RESOLVE, 0, (LPARAM) serviceInstance );
					assert( posted );
					if( posted )
					{
						serviceInstanceAutoPtr.release();
					}
				}
				break;
			
			default:
				break;
		}
	}
	catch( ... )
	{
		// Don't let exceptions escape.
	}
}

//===========================================================================================================================
//	DNSNetworkAddressToString
//
//	Note: Currently only supports IPv4 network addresses.
//===========================================================================================================================

static char *	DNSNetworkAddressToString( const DNSNetworkAddress *inAddr, char *outString )
{
	const DNSUInt8 *		p;
	DNSUInt16				port;
	
	p = inAddr->u.ipv4.addr.v8;
	port = ntohs( inAddr->u.ipv4.port.v16 );
	if( port != kDNSPortInvalid )
	{
		sprintf( outString, "%u.%u.%u.%u:%u", p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ], port );
	}
	else
	{
		sprintf( outString, "%u.%u.%u.%u", p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] );
	}
	return( outString );
}

//===========================================================================================================================
//	UTF8StringToStringObject
//===========================================================================================================================

static DWORD	UTF8StringToStringObject( const char *inUTF8, CString &inObject )
{
	DWORD		err;
	int			n;
	BSTR		unicode;
	
	unicode = NULL;
	
	n = MultiByteToWideChar( CP_UTF8, 0, inUTF8, -1, NULL, 0 );
	if( n > 0 )
	{
		unicode = (BSTR) malloc( (size_t)( n * sizeof( wchar_t ) ) );
		if( !unicode )
		{
			err = ERROR_INSUFFICIENT_BUFFER;
			goto exit;
		}

		n = MultiByteToWideChar( CP_UTF8, 0, inUTF8, -1, unicode, n );
		try
		{
			inObject = unicode;
		}
		catch( ... )
		{
			err = ERROR_NO_UNICODE_TRANSLATION;
			goto exit;
		}
	}
	else
	{
		inObject = "";
	}
	err = 0;
	
exit:
	if( unicode )
	{
		free( unicode );
	}
	return( err );
}

//===========================================================================================================================
//	StringObjectToUTF8String
//===========================================================================================================================

static DWORD	StringObjectToUTF8String( CString &inObject, std::string &outUTF8 )
{
	DWORD		err;
	BSTR		unicode;
	int			nUnicode;
	int			n;
	char *		utf8;
	
	unicode = NULL;
	utf8	= NULL;
	
	nUnicode = inObject.GetLength();
	if( nUnicode > 0 )
	{
		unicode = inObject.AllocSysString();
		n = WideCharToMultiByte( CP_UTF8, 0, unicode, nUnicode, NULL, 0, NULL, NULL );
		assert( n > 0 );
		
		utf8 = (char *) malloc( (size_t) n );
		assert( utf8 );
		if( !utf8 ) { err = ERROR_INSUFFICIENT_BUFFER; goto exit; }
		
		n = WideCharToMultiByte( CP_UTF8, 0, unicode, nUnicode, utf8, n, NULL, NULL );
		assert( n > 0 );
		
		try
		{
			outUTF8.assign( utf8, n );
		}
		catch( ... )
		{
			err = ERROR_NO_UNICODE_TRANSLATION;
			goto exit;
		}
	}
	else
	{
		outUTF8.clear();
	}
	err = 0;
	
exit:
	if( unicode )
	{
		SysFreeString( unicode );
	}
	if( utf8 )
	{
		free( utf8 );
	}
	return( err );
}
