// PortForwardView.cpp : implementation of the CPortForwardView class
//

#include "upnp_stdafx.h"
#include "upnp_PortForward.h"

//#include "PortForwardDoc.h"
#include "upnp_PortForwardView.h"
//#include "DlgPortMappingSettings.h"
//#include "DlgDeviceInformation.h"
//#include "DlgExternalIP.h"
//#include "DerivedPortForwardChangeCallbacks.h"

#include <iphlpapi.h>
#pragma comment ( lib, "iphlpapi" )
#include <shlwapi.h>
#pragma comment ( lib, "shlwapi" )

#include <vector>

extern const UINT UWM_PORT_FORWARD_ENGINE_THREAD_NOTIFICATION;  // defined in PortForwadEngine.cpp


extern CPortForwardApp theApp;


#include <htmlhelp.h>	// these two needed for HTML Help support
#pragma comment(lib, "htmlhelp.lib")


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPortForwardView

IMPLEMENT_DYNCREATE(CPortForwardView, CFormView)

BEGIN_MESSAGE_MAP(CPortForwardView, CFormView)
//{{AFX_MSG_MAP(CPortForwardView)
ON_WM_SIZE()
ON_WM_TIMER()
ON_REGISTERED_MESSAGE( UWM_PORT_FORWARD_ENGINE_THREAD_NOTIFICATION, OnMappingThreadNotificationMessage )
ON_BN_CLICKED(IDC_CHECK_OPEN_LISTENER, OnButtonCheckOpenListenerPort)
ON_BN_CLICKED(IDC_BUTTON_ADD_NEW_ENTRY, OnButtonAddNewEntry)
ON_BN_CLICKED(IDC_BUTTON_EDIT_SELECTED_ENTRY, OnButtonEditSelectedEntry)
ON_BN_CLICKED(IDC_BUTTON_RUN_PORT_TEST, OnButtonRunPortTest)
ON_BN_CLICKED(IDC_BUTTON_UPDATE_NOW, OnButtonUpdateNowUsingNewThread)
ON_BN_CLICKED(IDC_BUTTON_MORE_DEVICE_INFO, OnButtonMoreDeviceInfo)
ON_BN_CLICKED(IDC_BUTTON_DELETE_SELECTED_ENTRY, OnButtonDeleteSelectedEntry)
ON_BN_CLICKED(IDC_BUTTON_RUN_LOOPBACK_TEST, OnButtonRunLoopbackPortTest)
ON_BN_CLICKED(IDC_CHECK_LISTEN_FOR_CHANGE_EVENTS, OnButtonCheckListenForChangeEvents)
ON_BN_CLICKED(IDC_BUTTON_HELP, OnButtonHelp)
ON_NOTIFY(NM_DBLCLK, IDC_LIST_PORT_MAPPINGS, OnDblclkListOfPortMappings)
	//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPortForwardView construction/destruction

CPortForwardView::CPortForwardView()
: CFormView(CPortForwardView::IDD)
{
	//{{AFX_DATA_INIT(CPortForwardView)
	//}}AFX_DATA_INIT
	// TODO: add construction code here
	
	m_bPortTestingEnabled = FALSE;
	m_bListenForChangeEventsEnabled = TRUE;
	
}

CPortForwardView::~CPortForwardView()
{
}

void CPortForwardView::DoDataExchange(CDataExchange* pDX)
{
	CFormView::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CPortForwardView)
	DDX_Control(pDX, IDC_PROGRESS_IGD_DEVICE_INFO, m_ctlProgressIgdDeviceInfo);
	DDX_Control(pDX, IDC_PROGRESS_DELETE_UPDATE, m_ctlProgressDeleteUpdate);
	DDX_Control(pDX, IDC_PROGRESS_ADD_UPDATE, m_ctlProgressAddUpdate);
	DDX_Control(pDX, IDC_PROGRESS_EDIT_UPDATE, m_ctlProgressEditUpdate);
	DDX_Control(pDX, IDC_PROGRESS_COM_UPDATE, m_ctlProgressComUpdate);
	DDX_Control(pDX, IDC_LIST_PORT_MAPPINGS, m_ctlListPortMappings);
	DDX_Control(pDX, IDC_EDIT_PORT_TEST_TEXT, m_ctlEditPortTestTextWindow);
	DDX_Control(pDX, IDC_EDIT_LISTENER_PORT_NUM, m_ctlEditListenerPortNumber);
	DDX_Control(pDX, IDC_STATIC_GROUP_PORT_TEST, m_ctlStaticPortTestGroup);
	DDX_Control(pDX, IDC_STATIC_IGD_NAME, m_ctlStaticIgdName);
	DDX_Control(pDX, IDC_CHECK_OPEN_LISTENER, m_ctlCheckOpenListener);
	DDX_Control(pDX, IDC_CHECK_LISTEN_FOR_CHANGE_EVENTS, m_ctlCheckListenForChangeEvents);
	DDX_Control(pDX, IDC_BUTTON_DELETE_SELECTED_ENTRY, m_ctlBtnDeleteSelectedEntry);
	DDX_Control(pDX, IDC_BUTTON_MORE_DEVICE_INFO, m_ctlBtnMoreDeviceInfo);
	DDX_Control(pDX, IDC_BUTTON_RUN_LOOPBACK_TEST, m_ctlBtnRunLoopbackPortTest);
	DDX_Control(pDX, IDC_BUTTON_UPDATE_NOW, m_ctlBtnUpdateNow);
	DDX_Control(pDX, IDC_BUTTON_RUN_PORT_TEST, m_ctlBtnRunIncomingPortTest);
	DDX_Control(pDX, IDC_BUTTON_EDIT_SELECTED_ENTRY, m_ctlBtnEditSelectedEntry);
	DDX_Control(pDX, IDC_BUTTON_ADD_NEW_ENTRY, m_ctlBtnAddNewEntry);
	//}}AFX_DATA_MAP
}

BOOL CPortForwardView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs
	
	return CFormView::PreCreateWindow(cs);
}

void CPortForwardView::OnInitialUpdate()
{
	CFormView::OnInitialUpdate();
	GetParentFrame()->RecalcLayout();
	ResizeParentToFit();
	
	
	// initialize resize helper and anchor the sizes of the button controls
	
	m_resizeHelper.Init( m_hWnd );
	m_resizeHelper.Fix( IDC_BUTTON_MORE_DEVICE_INFO, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_BUTTON_UPDATE_NOW, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_BUTTON_UPDATE_NOW_NOTHREAD, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_BUTTON_ADD_NEW_ENTRY, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_BUTTON_EDIT_SELECTED_ENTRY, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_BUTTON_DELETE_SELECTED_ENTRY, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_EDIT_LISTENER_PORT_NUM, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_BUTTON_RUN_PORT_TEST, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_BUTTON_RUN_LOOPBACK_TEST, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_PROGRESS_COM_UPDATE, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );
	m_resizeHelper.Fix( IDC_PROGRESS_IGD_DEVICE_INFO, DlgResizeHelper::kWidth, DlgResizeHelper::kHeight );

	
	
	
	
	// set columns and column headers for the port-mapping list control
	
	m_ctlListPortMappings.SetExtendedStyle( LVS_EX_FULLROWSELECT );
	
	m_ctlListPortMappings.InsertColumn(0, _T("External IP") );
	m_ctlListPortMappings.InsertColumn(1, _T("External Port") );
	m_ctlListPortMappings.InsertColumn(2, _T("Protocol") );
	m_ctlListPortMappings.InsertColumn(3, _T("Destination IP") );
	m_ctlListPortMappings.InsertColumn(4, _T("Destination Port") );
	m_ctlListPortMappings.InsertColumn(5, _T("Enabled") );
	m_ctlListPortMappings.InsertColumn(6, _T("Description") );
	
	
	CRect rc;
	m_ctlListPortMappings.GetClientRect( &rc );
	
	int smallest = (int)( 0.10*rc.Width() );
	int smaller = (int)( 0.11*rc.Width() );
	int larger = (int)( 0.14*rc.Width() );
	
	m_ctlListPortMappings.SetColumnWidth(0, larger);
	m_ctlListPortMappings.SetColumnWidth(1, smaller);
	m_ctlListPortMappings.SetColumnWidth(2, smallest);
	m_ctlListPortMappings.SetColumnWidth(3, larger);
	m_ctlListPortMappings.SetColumnWidth(4, larger);
	m_ctlListPortMappings.SetColumnWidth(5, smallest);	
	m_ctlListPortMappings.SetColumnWidth(6, (rc.Width() - 3*larger - 1*smaller - 2*smallest) );  // remainder=27% or so
	

		
	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUT_SYS_MENU & 0xFFF0) == IDM_ABOUT_SYS_MENU);
	ASSERT(IDM_ABOUT_SYS_MENU < 0xF000);

	CMenu* pSysMenu = ::AfxGetMainWnd()->GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_STRING_ABOUT);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUT_SYS_MENU, strAboutMenu);
		}
	}

	
	
	// set enabled state of port-testing windows and the listen-port checkbox
	
	m_ctlCheckOpenListener.SetCheck( (m_bPortTestingEnabled==FALSE)?0:1 );
	EnablePortTestingWindows( m_bPortTestingEnabled );


	// configure the PortForwardEngine to listen for changes in the router

	m_bListenForChangeEventsEnabled = theApp.m_Preferences.m_bAutoChangeNotifications;
	
	m_ctlCheckListenForChangeEvents.SetCheck( (m_bListenForChangeEventsEnabled==FALSE)?0:1 );

	if ( m_bListenForChangeEventsEnabled==TRUE )
	{
		m_PortForwardEngine.ListenForUpnpChanges( new CDerivedPortForwardChangeCallbacks( /* this */ ) );
	}
	else
	{
		m_PortForwardEngine.StopListeningForUpnpChanges( );
	}
	
	
	// set range of COM port-retrieval progress bar and make certain that it is not visible
	
	m_ctlProgressComUpdate.SetRange( 0, 10 );
	m_ctlProgressComUpdate.SetPos( 0 );
	m_ctlProgressComUpdate.ShowWindow( SW_HIDE );
	
	
	// set range of COM port-edit progress bar and make certain that it is not visible
	
	m_ctlProgressEditUpdate.SetRange( 0, 10 );
	m_ctlProgressEditUpdate.SetPos( 0 );
	m_ctlProgressEditUpdate.ShowWindow( SW_HIDE );
	
	
	// set range of COM Add-port progress bar and make certain that it is not visible
	
	m_ctlProgressAddUpdate.SetRange( 0, 10 );
	m_ctlProgressAddUpdate.SetPos( 0 );
	m_ctlProgressAddUpdate.ShowWindow( SW_HIDE );
	
	
	// set range of COM Delete-port progress bar and make certain that it is not visible
	
	m_ctlProgressDeleteUpdate.SetRange( 0, 10 );
	m_ctlProgressDeleteUpdate.SetPos( 0 );
	m_ctlProgressDeleteUpdate.ShowWindow( SW_HIDE );
	
	
	// set range of IGD device information progress bar and make certain that it IS visible,
	// hide the static control that contains the device name,
	// then, start up thread to get device info
	
	m_ctlProgressIgdDeviceInfo.SetRange( 0, 10 );
	m_ctlProgressIgdDeviceInfo.SetPos( 0 );
	m_ctlProgressIgdDeviceInfo.ShowWindow( SW_SHOW );
	
	m_ctlStaticIgdName.ShowWindow( SW_HIDE );
	
	m_PortForwardEngine.GetDeviceInformationUsingThread( m_hWnd );
	
	
	// set default value of listening port
	
	CString strTemp;
	strTemp.Format( _T("%d"), theApp.m_Preferences.m_DefaultListeningPort );
	
	m_ctlEditListenerPortNumber.SetWindowText( strTemp );
	
	
	// give the web server a pointer to the edit window for logging of messages,
	// and set timer for heartbeat
	
	m_WebServer.SetEditWindow( &m_ctlEditPortTestTextWindow );
	SetTimer( IDT_HEARTBEAT_TIMER, 1000  /* one second */  , NULL );
	
}

/////////////////////////////////////////////////////////////////////////////
// CPortForwardView printing

BOOL CPortForwardView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// default preparation
	return DoPreparePrinting(pInfo);
}

void CPortForwardView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: add extra initialization before printing
}

void CPortForwardView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: add cleanup after printing
}

void CPortForwardView::OnPrint(CDC* pDC, CPrintInfo* /*pInfo*/)
{
	// TODO: add customized printing code here
}

/////////////////////////////////////////////////////////////////////////////
// CPortForwardView diagnostics

#ifdef _DEBUG
void CPortForwardView::AssertValid() const
{
	CFormView::AssertValid();
}

void CPortForwardView::Dump(CDumpContext& dc) const
{
	CFormView::Dump(dc);
}

CPortForwardDoc* CPortForwardView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CPortForwardDoc)));
	return (CPortForwardDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CPortForwardView message handlers

void CPortForwardView::OnSize(UINT nType, int cx, int cy) 
{
	CFormView::OnSize(nType, cx, cy);
	
	// TODO: Add your message handler code here
	
	m_resizeHelper.OnSize();
	
}

void CPortForwardView::OnButtonCheckOpenListenerPort() 
{
	// check validity of listener port number
	
	CString tempStr;
	m_ctlEditListenerPortNumber.GetWindowText( tempStr );
	int iPort = _ttoi( tempStr );
	
	if ( iPort>0 && iPort<65536 )
	{
		// valid port number
		
		m_bPortTestingEnabled = !m_bPortTestingEnabled;
		
		// start or shut-down server
		
		if ( m_bPortTestingEnabled == TRUE )
		{
			m_iListeningPort = iPort;
			m_WebServer.StartUpServer( iPort );
		}
		else
		{
			m_WebServer.ShutDownServer();
		}
	}
	else
	{
		::MessageBox( m_hWnd, _T("Invalid port number \n")
			_T("Enter a number between 1 and 65535"),
			_T("Invalid Port Number"),
			MB_OK | MB_ICONEXCLAMATION );
	}
	
	// set enabled state of port-testing windows and the listen-port checkbox
	
	m_ctlCheckOpenListener.SetCheck( (m_bPortTestingEnabled==FALSE)?0:1 );
	EnablePortTestingWindows( m_bPortTestingEnabled );
	
}

void CPortForwardView::OnButtonAddNewEntry() 
{
	// first, get this machine's IP address (or, at least, the IP address
	// of the first adapter on the machine)
	
	// use the IP Helper function GetAdaptersInfo()
	// See http://msdn.microsoft.com/library/en-us/iphlp/iphlp/getadaptersinfo.asp
	
	CString strLocalIP;
	
	PIP_ADAPTER_INFO pAdapterInfo = NULL;
	DWORD dwRetVal = 0;
	
	pAdapterInfo = (IP_ADAPTER_INFO *) malloc( sizeof(IP_ADAPTER_INFO) );
	ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
	
	// Make an initial call to GetAdaptersInfo to get
	// the necessary size into the ulOutBufLen variable
	
	if (GetAdaptersInfo( pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) 
	{
		free(pAdapterInfo);
		pAdapterInfo = (IP_ADAPTER_INFO *) malloc (ulOutBufLen); 
	}
	
	if ((dwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen)) == NO_ERROR)
	{
		strLocalIP = CString( (LPCSTR)(pAdapterInfo->IpAddressList.IpAddress.String) );  // note: not LPCTSTR
	}
	else 
	{
		strLocalIP.Empty();
	}
	
	free(pAdapterInfo);
	
	
	// display dialog for adding new mapping
	
	CDlgPortMappingSettings dlg;
	
	dlg.m_iExternalPort = 0;
	dlg.m_iTcp = 0;  // 0 == TCP, 1==UDP
	dlg.m_strInternalIP = strLocalIP;
	dlg.m_iInternalPort = 0;
	dlg.m_iEnabled = 0;  // 0 == Enabled, 1 == Disabled
	dlg.m_strFriendlyName.Empty();
	
	int iRet = dlg.DoModal();
	
	if ( iRet == IDOK )
	{
		CPortForwardEngine::PortMappingContainer newMapping;
		
		newMapping.ExternalPort.Format( _T("%d"), dlg.m_iExternalPort );
		newMapping.Protocol = ( dlg.m_iTcp==0 ) ? _T("TCP") : _T("UDP");
		newMapping.InternalClient = dlg.m_strInternalIP;
		newMapping.InternalPort.Format( _T("%d"), dlg.m_iInternalPort );
		newMapping.Enabled = ( dlg.m_iEnabled==0 ) ? _T("Yes") : _T("No");
		newMapping.Description = dlg.m_strFriendlyName;
		
		if ( m_PortForwardEngine.AddMappingUsingThread( newMapping, m_hWnd ) != FALSE )
		{
			m_ctlProgressAddUpdate.SetPos( 0 );
			m_ctlProgressAddUpdate.ShowWindow( SW_SHOW );
		}
		else
		{
			// port engine could not start thread
			
			::MessageBox( m_hWnd, _T("The port mapping engine is busy \n")
				_T("Please try again in a few seconds"), 
				_T("Busy"), MB_OK|MB_ICONEXCLAMATION );
		}
	}
}


void CPortForwardView::OnButtonEditSelectedEntry() 
{
	// find selected item
	
	int iSelected = m_ctlListPortMappings.GetNextItem( -1, LVNI_SELECTED );
	
	if ( iSelected == -1 )
		return;  // no item is selected
	
	
	// display dialog for editing the selected mapping
	
	CDlgPortMappingSettings dlg;
	
	dlg.m_iExternalPort = _ttol( m_ctlListPortMappings.GetItemText( iSelected, 1 ) );
	dlg.m_iTcp = (m_ctlListPortMappings.GetItemText( iSelected, 2 ) == _T("TCP")) ? 0 : 1 ;  // 0 == TCP, 1==UDP
	dlg.m_strInternalIP = m_ctlListPortMappings.GetItemText( iSelected, 3 );
	dlg.m_iInternalPort = _ttol( m_ctlListPortMappings.GetItemText( iSelected, 4 ) );
	dlg.m_iEnabled = (m_ctlListPortMappings.GetItemText( iSelected, 5 ) == _T("Yes")) ? 0 : 1;  // 0 == Enabled, 1 == disabled
	dlg.m_strFriendlyName = m_ctlListPortMappings.GetItemText( iSelected, 6 );
	
	dlg.DisableNonEditableFields();
	
	
	// remember the identity of the old mapping in case user actually edits it
	// we only need the external port and protocol for COM's identification of the old mapping
	
	CPortForwardEngine::PortMappingContainer oldMapping, newMapping;
	oldMapping.ExternalPort = m_ctlListPortMappings.GetItemText( iSelected, 1 );
	oldMapping.Protocol = m_ctlListPortMappings.GetItemText( iSelected, 2 );
	
	int iRet = dlg.DoModal();
	
	if ( iRet == IDOK )
	{
		newMapping.ExternalPort.Format( _T("%d"), dlg.m_iExternalPort );
		newMapping.Protocol = ( dlg.m_iTcp==0 ) ? _T("TCP") : _T("UDP");
		newMapping.InternalClient = dlg.m_strInternalIP;
		newMapping.InternalPort.Format( _T("%d"), dlg.m_iInternalPort );
		newMapping.Enabled = ( dlg.m_iEnabled==0 ) ? _T("Yes") : _T("No");
		newMapping.Description = dlg.m_strFriendlyName;
		
		if ( m_PortForwardEngine.EditMappingUsingThread( oldMapping, newMapping, m_hWnd ) != FALSE )
		{
			m_ctlProgressEditUpdate.SetPos( 0 );
			m_ctlProgressEditUpdate.ShowWindow( SW_SHOW );
		}
		else
		{
			// port engine could not start thread
			
			::MessageBox( m_hWnd, _T("The port mapping engine is busy \n")
				_T("Please try again in a few seconds"), 
				_T("Busy"), MB_OK|MB_ICONEXCLAMATION );
		}
	}
	
}


void CPortForwardView::OnButtonDeleteSelectedEntry() 
{
	// find selected item
	
	int iSelected = m_ctlListPortMappings.GetNextItem( -1, LVNI_SELECTED );
	
	if ( iSelected == -1 )
		return;  // no item is selected
	
	int iRet = ::MessageBox( m_hWnd, _T("Click OK to confirm deletion\n ")
		_T("Click \"Cancel\" to cancel deletion"), 
		_T("Confirmation"), MB_OKCANCEL | MB_ICONEXCLAMATION );
	
	if ( iRet == IDOK )
	{
		// store the identity of the old mapping to be deleted
		// we only need the external port and protocol for COM's identification of the old mapping
		
		CPortForwardEngine::PortMappingContainer oldMapping;
		
		oldMapping.ExternalPort = m_ctlListPortMappings.GetItemText( iSelected, 1 );
		oldMapping.Protocol = m_ctlListPortMappings.GetItemText( iSelected, 2 );
		
		if ( m_PortForwardEngine.DeleteMappingUsingThread( oldMapping, m_hWnd ) != FALSE )
		{
			m_ctlProgressDeleteUpdate.SetPos( 0 );
			m_ctlProgressDeleteUpdate.ShowWindow( SW_SHOW );
		}
		else
		{
			// port engine could not start thread
			
			::MessageBox( m_hWnd, _T("The port mapping engine is busy \n")
				_T("Please try again in a few seconds"), 
				_T("Busy"), MB_OK|MB_ICONEXCLAMATION );
		}
	}	
}


void CPortForwardView::OnButtonMoreDeviceInfo() 
{
	CDlgDeviceInformation dlg;
	
	CPortForwardEngine::DeviceInformationContainer const& devInfo = m_PortForwardEngine.GetDeviceInformationContainer( );
	
	dlg.m_strChildren.Format( _T("Children: %s"), devInfo.Children );
	dlg.m_strDescription.Format( _T("Description: %s"), devInfo.Description );
	dlg.m_strFriendlyName.Format( _T("Friendly Name: %s"), devInfo.FriendlyName );
	dlg.m_strHasChildren.Format( _T("Has Children: %s"), devInfo.HasChildren );
	dlg.m_strIconUrl.Format( _T("Icon URL: %s"), devInfo.IconURL );
	dlg.m_strIsRootDevice.Format( _T("Is Root Device: %s"), devInfo.IsRootDevice );
	dlg.m_strManufacturerName.Format( _T("Manufacturer Name: %s"), devInfo.ManufacturerName );
	dlg.m_strManufacturerUrl.Format( _T("Manufacturer URL: %s"), devInfo.ManufacturerURL );
	dlg.m_strModelName.Format( _T("Model Name: %s"), devInfo.ModelName );
	dlg.m_strModelNumber.Format( _T("Model Number: %s"), devInfo.ModelNumber );
	dlg.m_strModelUrl.Format( _T("Model URL: %s"), devInfo.ModelURL );
	dlg.m_strParentDevice.Format( _T("Parent Device: %s"), devInfo.ParentDevice );
	dlg.m_strPresentationUrl.Format( _T("Presentation URL: %s"), devInfo.PresentationURL );
	dlg.m_strRootDevice.Format( _T("Root Device: %s"), devInfo.RootDevice );
	dlg.m_strSerialNumber.Format( _T("Serial Number: %s"), devInfo.SerialNumber );
	dlg.m_strServices.Format( _T("Services: %s"), devInfo.Services );
	dlg.m_strType.Format( _T("Type: %s"), devInfo.Type );
	dlg.m_strUniqueDeviceName.Format( _T("Unique Device Name: %s"), devInfo.UniqueDeviceName );
	dlg.m_strUpc.Format( _T("UPC: %s"), devInfo.UPC );
	
	dlg.DoModal();
}


void CPortForwardView::OnButtonRunPortTest() 
{
	
	// Run port test using proxy so that request goes out over the Internet to a proxy and comes 
	// back in from the proxy
	
	RunPortTest( TRUE );  // TRUE == full Internet test
}

void CPortForwardView::OnButtonRunLoopbackPortTest() 
{
	// Run port test using loopback address
	
	RunPortTest( FALSE );  // FALSE == loopback test
}

void CPortForwardView::RunPortTest(BOOL bFullInternet)
{
	// opens a new browser window with URL of this very machine, on the current
	// listening port.
	
	// If bFullInternet == FALSE, then we insert the word "loopback" into the
	// requested URL, which signifies to the web server that a loopback test was
	// requested, and in this case we open the browser with the loopback ("127.0.0.1")
	// address.  On the other hand, if bFullInternet == TRUE, then we ask for the
	// root URL in the request, and open the browser through a proxy to this very machine,
	// so that the request goes out over the Internet and comes back in from the proxy
	
	CString strURL;
	
	if ( bFullInternet == FALSE )
	{
		// easy case, a loopback test
		// the URL is the loopback URL of 127.0.0.1, followed by the listening port, and
		// then followed by "loopback" which signifies to the web server that a loopback
		// test is being requested
		
		strURL.Format( _T("http://127.0.0.1:%d/loopback"), m_iListeningPort );
		
	}
	else
	{
		// more complicated case, a full test over the Internet.
		// The URL is the proxy's address, followed by the extermnal IP address of
		// this machine, followed by the listening port, followed by a single
		// forward slash.  In the absence of the word "loopback" in the URL, the
		// web server will think that this is a full Internet test
		
		// First, get the external IP address
		// if there are items in the mapping list, get the external IP address
		// from there.  Otherwise, ask the user for the external IP address
		
		CString strExternalIP;
		
		int nCount = m_ctlListPortMappings.GetItemCount();
		
		if ( nCount < 1 )
		{
			// no items in the list, get the IP address from the user
			
			CDlgExternalIP dlg;
			int iRet = dlg.DoModal();
			
			if ( iRet != IDOK )
				return;
			
			strExternalIP = dlg.m_strExternalIP;
		}
		else
		{
			int nFirst = m_ctlListPortMappings.GetNextItem( -1, LVNI_ALL );
			strExternalIP = m_ctlListPortMappings.GetItemText( nFirst, 0 );
		}
		
		CString strProxy = theApp.GetWebProxy();
		
		strURL.Format( _T("%shttp://%s:%d/"), strProxy, strExternalIP, m_iListeningPort );
		
	}
	
	// find default browser and open the URL
	// This code is based on code found at 
	// http://www.codeguru.com/forum/showthread.php?s=&threadid=161588
	
	DWORD dwBufferSize = 200;
	CString szExecutable;
	HRESULT hr = AssocQueryString(0, ASSOCSTR_EXECUTABLE, _T(".htm"), _T("open"), szExecutable.GetBuffer(200), &dwBufferSize);
	szExecutable.ReleaseBuffer();
		  
	if(FAILED(hr))
	{
		::MessageBox( m_hWnd, _T("Could not find default browser"), _T("Error"), MB_OK|MB_ICONEXCLAMATION );
		return;
	} 
	
	// open the URL
	
	::ShellExecute(NULL, _T("open"), szExecutable, strURL, NULL, SW_SHOWNORMAL );
	
}


void CPortForwardView::OnButtonUpdateNowUsingNewThread() 
{
	if ( m_PortForwardEngine.GetMappingsUsingThread( m_hWnd ) != FALSE )
	{
		m_ctlProgressComUpdate.SetPos( 0 );
		m_ctlProgressComUpdate.ShowWindow( SW_SHOW );
	}
	else
	{
		// port engine could not start thread
		
		::MessageBox( m_hWnd, _T("The port mapping engine is busy \n")
			_T("Please try again in a few seconds"), 
			_T("Busy"), MB_OK|MB_ICONEXCLAMATION );
	}
}

static const int msgPortRetrieve	= 0x00F0 & CPortForwardEngine::EnumPortRetrieveDone;
static const int msgDeviceInfo		= 0x00F0 & CPortForwardEngine::EnumDeviceInfoDone;
static const int msgAddMapping		= 0x00F0 & CPortForwardEngine::EnumAddMappingDone;
static const int msgEditMapping		= 0x00F0 & CPortForwardEngine::EnumEditMappingDone;
static const int msgDeleteMapping	= 0x00F0 & CPortForwardEngine::EnumDeleteMappingDone;

afx_msg LRESULT CPortForwardView::OnMappingThreadNotificationMessage(WPARAM wParam, LPARAM lParam)
{
	switch ( wParam & 0xFFF0 )
	{
	case msgPortRetrieve:
		
		if ( wParam == CPortForwardEngine::EnumPortRetrieveInterval )
		{
			// this is a periodic notification message; update the progress control
			
			m_ctlProgressComUpdate.SetPos( lParam );
		}
		else if ( wParam == CPortForwardEngine::EnumPortRetrieveDone )
		{
			// the thread is finished
			
			if ( !SUCCEEDED(lParam) )
			{
				// error: display message only
				
				::MessageBox( m_hWnd, 
					_T("The COM thread was unable to retrieve port mappings from your router \n")
					_T("Possible reasons: \n")
					_T(" - Your OS might not be UPnP-capable (basically, you need \n")
					_T("    Win XP (any SP) or above, Win 2000 is not enough) or it \n")
					_T("    might not be turned on \n")
					_T(" - Your router might not have UPnP capability or it might not be enabled \n")
					_T(" - Your firewall might be blocking TCP port 2869 or UDP port 1900 \n")
					_T("    which are both needed for UPnP \n\n")
					_T("Try again after checking into all three items"),
					_T("Port Forward Mappings Not Found"),
					MB_OK | MB_ICONEXCLAMATION );
			}
			else
			{
				// finished with no error
				
				std::vector<CPortForwardEngine::PortMappingContainer> mappingContainer;
				mappingContainer = m_PortForwardEngine.GetPortMappingVector();
		
				std::vector<CPortForwardEngine::PortMappingContainer>::const_iterator it;
				int iii = 0;
				
				m_ctlListPortMappings.DeleteAllItems();
				
				for (it = mappingContainer.begin(); it < mappingContainer.end(); it++ )
				{
					int jjj = m_ctlListPortMappings.InsertItem( iii++, it->ExternalIPAddress );
					
					m_ctlListPortMappings.SetItemText( jjj, 1, it->ExternalPort );
					m_ctlListPortMappings.SetItemText( jjj, 2, it->Protocol );
					m_ctlListPortMappings.SetItemText( jjj, 3, it->InternalClient );
					m_ctlListPortMappings.SetItemText( jjj, 4, it->InternalPort );
					m_ctlListPortMappings.SetItemText( jjj, 5, it->Enabled );
					m_ctlListPortMappings.SetItemText( jjj, 6, it->Description );
					
				}
			}
			
			// regardless of whether thread finished with or without error, hide the progress bar
			
			m_ctlProgressComUpdate.SetPos( 0 );
			m_ctlProgressComUpdate.ShowWindow( SW_HIDE );
			
		}
		
		break;
		
		
		
	case msgDeviceInfo:
		
		if ( wParam == CPortForwardEngine::EnumDeviceInfoInterval )
		{
			// this is a periodic notification message; update the progress control
			
			m_ctlProgressIgdDeviceInfo.SetPos( lParam );
		}
		else if ( wParam == CPortForwardEngine::EnumDeviceInfoDone )
		{
			if ( SUCCEEDED(lParam) )
			{
				m_DeviceInfoContainer =  m_PortForwardEngine.GetDeviceInformationContainer( );
				if ( m_DeviceInfoContainer.ModelName.IsEmpty() == TRUE )  // sometimes nothing is found
				{
					m_DeviceInfoContainer.ModelName.Format( _T("<name not found>") );
				}
				m_ctlStaticIgdName.SetWindowText( m_DeviceInfoContainer.ModelName );
			}
			else
			{
				::MessageBox( m_hWnd, _T("Not able to retrieve device information from UPnP control point"),
					_T("Device Information Not Retrievable"), 
					MB_OK | MB_ICONEXCLAMATION );
				
				m_DeviceInfoContainer.ModelName.Format( _T("<name not found>") );
				m_ctlStaticIgdName.SetWindowText( m_DeviceInfoContainer.ModelName );
				
			}
			
			// regardless of whether thread finished with or without error, hide the progress bar
			// and show the staic control contianing the device name
			
			m_ctlProgressIgdDeviceInfo.SetPos( 0 );
			m_ctlProgressIgdDeviceInfo.ShowWindow( SW_HIDE );
			
			m_ctlStaticIgdName.ShowWindow( SW_SHOW );
		}
		
		break;
		
		
		
		
		
	case msgAddMapping:
		
		if ( wParam == CPortForwardEngine::EnumAddMappingInterval )
		{
			// this is a periodic notification message; update the progress control
			
			m_ctlProgressAddUpdate.SetPos( lParam );
		}
		else if ( wParam == CPortForwardEngine::EnumAddMappingDone )
		{
			// the thread is finished
			
			if ( !SUCCEEDED(lParam) )
			{
				// error: display message only
				
				::MessageBox( m_hWnd, _T("Unable to add mapping"),
					_T("Add Failed"),
					MB_OK | MB_ICONEXCLAMATION );
			}
			else
			{
				// finished with no error
			}
			
			// regardless of whether thread finished with or without error, hide the progress bar
			
			m_ctlProgressAddUpdate.SetPos( 0 );
			m_ctlProgressAddUpdate.ShowWindow( SW_HIDE );
			
		}
		
		
		break;
		
		
		
		
	case msgEditMapping:
		
		if ( wParam == CPortForwardEngine::EnumEditMappingInterval )
		{
			// this is a periodic notification message; update the progress control
			
			m_ctlProgressEditUpdate.SetPos( lParam );
		}
		else if ( wParam == CPortForwardEngine::EnumEditMappingDone )
		{
			// the thread is finished
			
			if ( !SUCCEEDED(lParam) )
			{
				// error: display message only
				
				::MessageBox( m_hWnd, _T("Unable to edit entry"),
					_T("Edit Failed"),
					MB_OK | MB_ICONEXCLAMATION );
			}
			else
			{
				// finished with no error
			}
			
			// regardless of whether thread finished with or without error, hide the progress bar
			
			m_ctlProgressEditUpdate.SetPos( 0 );
			m_ctlProgressEditUpdate.ShowWindow( SW_HIDE );
			
		}
		
		break;
		
		
		
		
		
	case msgDeleteMapping:
		
		if ( wParam == CPortForwardEngine::EnumDeleteMappingInterval )
		{
			// this is a periodic notification message; update the progress control
			
			m_ctlProgressDeleteUpdate.SetPos( lParam );
		}
		else if ( wParam == CPortForwardEngine::EnumDeleteMappingDone )
		{
			// the thread is finished
			
			if ( !SUCCEEDED(lParam) )
			{
				// error: display message only
				
				::MessageBox( m_hWnd, _T("Unable to delete entry"),
					_T("Delete Failed"),
					MB_OK | MB_ICONEXCLAMATION );
			}
			else
			{
				// finished with no error
			}
			
			// regardless of whether thread finished with or without error, hide the progress bar
			
			m_ctlProgressDeleteUpdate.SetPos( 0 );
			m_ctlProgressDeleteUpdate.ShowWindow( SW_HIDE );
			
		}
		
		break;
		
		
		
		
	default:
		ASSERT ( FALSE );  // should never get here
		
		}
		
		return 0L;
		
}


void CPortForwardView::EnablePortTestingWindows(BOOL bEnable)
{
	m_ctlBtnRunIncomingPortTest.EnableWindow( bEnable );
	m_ctlBtnRunLoopbackPortTest.EnableWindow( bEnable );
	m_ctlEditPortTestTextWindow.EnableWindow( bEnable );
	m_ctlStaticPortTestGroup.EnableWindow( bEnable );
	
	m_ctlEditListenerPortNumber.EnableWindow( !bEnable );
}




void CPortForwardView::OnDblclkListOfPortMappings(NMHDR* pNMHDR, LRESULT* pResult) 
{
	// TODO: Add your control notification handler code here
	
	OnButtonEditSelectedEntry();
	
	*pResult = 0;
}

void CPortForwardView::OnTimer(UINT nIDEvent) 
{
	// check if this is our one-second hearbeat timer (for the web server heartbeat)
	
	if ( nIDEvent == IDT_HEARTBEAT_TIMER )
	{
		m_WebServer.OnHeartBeat();
		
		return;  // eat the timer event so some other window (like a CListCtrl) doesn't munge it
	}
	
	CFormView::OnTimer(nIDEvent);
}



void CPortForwardView::OnButtonCheckListenForChangeEvents() 
{
	m_bListenForChangeEventsEnabled = !m_bListenForChangeEventsEnabled;
	m_ctlCheckListenForChangeEvents.SetCheck( (m_bListenForChangeEventsEnabled==FALSE)?0:1 );

	if ( m_bListenForChangeEventsEnabled==TRUE )
	{
		m_PortForwardEngine.ListenForUpnpChanges( new CDerivedPortForwardChangeCallbacks( /* this */ ) );
	}
	else
	{
		m_PortForwardEngine.StopListeningForUpnpChanges( );
	}
}





void CPortForwardView::OnButtonHelp() 
{
	// displays HTML Help file, which should be in the same directory as the executable
	
	CString cstr = theApp.m_sModulePath + _T("\\PortForward.chm");
	
	HWND hwnd = HtmlHelp(
		::GetDesktopWindow(),
		cstr,	// "c:\\Help.chm::/Intro.htm>Mainwin",
		HH_DISPLAY_TOPIC,
		NULL) ;	
	
	if ( hwnd == NULL )
	{
		// standard help file was not found
		// see if the stub file is present and open that instead if present
		
		cstr = theApp.m_sModulePath + _T("\\PortForwardStub.chm");
		
		HWND hwnd = HtmlHelp(
			::GetDesktopWindow(),
			cstr,	// "c:\\Help.chm::/Intro.htm>Mainwin",
			HH_DISPLAY_TOPIC,
			NULL) ;	
		
		if ( hwnd == NULL )
		{
			// no help files were found; display error message
			// failed to open help
			
			::MessageBox( ::AfxGetMainWnd()->m_hWnd,
				_T("Could not find Help file named \"PortForward.chm\" in this directory \n\n")
				_T("Re-run the installation package to re-install the help file"),
				_T("Could Not Open Help File"),
				MB_OK | MB_ICONEXCLAMATION );
		}
	}

}
	


void CPortForwardView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
	// In standard doc/view architecture, the document would call this function
	// whenever the document has modified its contents.  I'm using it in an entirely
	// different way here.
	
	// The document calls this function inside its override of CanCloseFrame(), which 
	// the main frame calls when the application is being closed (ie, inside the frame's
	// WM_CLOSE handler).  The doc passes a hint of WM_CLOSE.  This is the view's
	// opportunity to check if there are any active threads running on its behalf inside of
	// the port forward engine, and to delay shutdown of the app if there are

	if ( lHint == WM_CLOSE )
	{
		BOOL bContinueToWait = TRUE;
		while ( (bContinueToWait == TRUE) && (m_PortForwardEngine.IsAnyThreadRunning() == TRUE) )
		{
			int iRet = ::MessageBox( m_hWnd,
				_T("Active threads are still running in the background \n\n")
				_T("Click \"OK\" to wait for a few moments and to allow threads to terminate \n")
				_T("Click \"Cancel\" to exit without waiting for threads to terminate \n"),
				_T("Threads Still Active"),
				MB_OKCANCEL | MB_ICONEXCLAMATION );

			if ( iRet == IDCANCEL )
				bContinueToWait = FALSE;
		}
	}
	
}
