//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// MessageWatchDlg.cpp : implementation file
//

#include "stdafx.h"
#include "MessageWatch.h"
#include "MessageWatchDlg.h"
#include "messagemgr.h"
#include "tier1/strtools.h"

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


#define WM_STARTIDLE	(WM_USER + 565)


// --------------------------------------------------------------------------- //
// CSender.
// --------------------------------------------------------------------------- //

CSender::CSender()
{
	m_pSocket = NULL;
	m_pConsoleWnd = NULL;
}

CSender::~CSender()
{
	if ( m_pSocket )
		m_pSocket->Release();
	
	if ( m_pConsoleWnd )
		m_pConsoleWnd->Release();
}


/////////////////////////////////////////////////////////////////////////////
// CMessageWatchDlg dialog

CMessageWatchDlg::CMessageWatchDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CMessageWatchDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CMessageWatchDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

	m_pListenSocket = NULL;
}

CMessageWatchDlg::~CMessageWatchDlg()
{
	// destroy the sender objects.

	if ( m_pListenSocket )
		m_pListenSocket->Release();
}

void CMessageWatchDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CMessageWatchDlg)
	DDX_Control(pDX, IDC_MACHINES, m_Machines);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CMessageWatchDlg, CDialog)
	//{{AFX_MSG_MAP(CMessageWatchDlg)
	ON_MESSAGE(WM_STARTIDLE, OnStartIdle)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_LBN_DBLCLK(IDC_MACHINES, OnDblclkMachines)
	ON_BN_CLICKED(IDSHOWALL, OnShowall)
	ON_BN_CLICKED(IDHIDEALL, OnHideall)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CMessageWatchDlg message handlers

BOOL CMessageWatchDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// Setup our listen socket and thread.	
	m_pListenSocket = CreateIPSocket();
	m_pListenSocket->BindToAny( MSGMGR_BROADCAST_PORT );

	m_cWinIdle.StartIdle( GetSafeHwnd(), WM_STARTIDLE, 0, 0, 100 );
	m_cWinIdle.NextIdle();
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}


LONG CMessageWatchDlg::OnStartIdle( UINT, LONG )
{
	MSG msg;
	if (!PeekMessage(&msg, GetSafeHwnd(), 0,0, PM_NOREMOVE))
		OnIdle();
	m_cWinIdle.NextIdle();
	return 0;
}


void CMessageWatchDlg::OnIdle()
{
	// Kill dead connections.
	int iNext;
	for ( int iSender=m_Senders.Head(); iSender != m_Senders.InvalidIndex(); iSender = iNext )
	{
		iNext = m_Senders.Next( iSender );

		CSender *pSender = m_Senders[iSender];
		if ( pSender->m_pSocket && !pSender->m_pSocket->IsConnected() )
		{
			// Just release the socket so the text stays there.
			pSender->m_pSocket->Release();
			pSender->m_pSocket = NULL;
		}
	}

	// Look for new connections.
	while ( 1 )
	{
		CIPAddr ipFrom;
		char data[16];
		int len = m_pListenSocket->RecvFrom( data, sizeof( data ), &ipFrom );
		if ( len == -1 )
			break;

		if ( data[0] == MSGMGR_PACKETID_ANNOUNCE_PRESENCE &&
			*((int*)&data[1]) == MSGMGR_VERSION )
		{
			int iPort = *((int*)&data[5]);
		
			// See if we have a machine with this info yet.
			CIPAddr connectAddr = ipFrom;
			connectAddr.port = iPort;

			// NOTE: we'll accept connections from machines we were connected to earlier but
			// lost the connection to.
			CSender *pSender = FindSenderByAddr( ipFrom.ip );
			if ( !pSender || !pSender->m_pSocket )
			{
				// 'nitiate the connection.
				ITCPSocket *pNew = CreateTCPSocket();
				if ( pNew->BindToAny( 0 ) && TCPSocket_Connect( pNew, &connectAddr, 1000 ) )
				{
					char nameStr[256];
					char title[512];
					if ( !ConvertIPAddrToString( &ipFrom, nameStr, sizeof( nameStr ) ) )
						Q_snprintf( nameStr, sizeof( nameStr ), "%d.%d.%d.%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3] );
					
					Q_snprintf( title, sizeof( title ), "%s:%d", nameStr, iPort );

					// If the sender didn't exist yet, add a new one.
					if ( !pSender )
					{
						pSender = new CSender;
						
						IConsoleWnd *pWnd = CreateConsoleWnd(
							AfxGetInstanceHandle(),
							IDD_OUTPUT,
							IDC_DEBUG_OUTPUT,
							false
							);

						pSender->m_pConsoleWnd = pWnd;
						pWnd->SetTitle( title );
						
						Q_strncpy( pSender->m_Name, title, sizeof( pSender->m_Name ) );
						m_Senders.AddToTail( pSender );
						m_Machines.AddString( pSender->m_Name );
					}

					pSender->m_Addr = connectAddr;
					pSender->m_pSocket = pNew;
				}
				else
				{
					pNew->Release();
				}
			}
		}
	}

	
	// Read input from our current connections.
	FOR_EACH_LL( m_Senders, i )
	{
		CSender *pSender = m_Senders[i];

		while ( 1 )
		{
			if ( !pSender->m_pSocket )
				break;

			CUtlVector<unsigned char> data;
			if ( !pSender->m_pSocket->Recv( data ) )
				break;

			if ( data[0] == MSGMGR_PACKETID_MSG )
			{
				char *pMsg = (char*)&data[1];
				pSender->m_pConsoleWnd->PrintToConsole( pMsg );
				OutputDebugString( pMsg );
			}
		}
	}
}


void CMessageWatchDlg::OnDestroy() 
{
	// Stop the idling thread
	m_cWinIdle.EndIdle();
	CDialog::OnDestroy();
}


CSender* CMessageWatchDlg::FindSenderByAddr( const unsigned char ip[4] )
{
	FOR_EACH_LL( m_Senders, i )
	{
		if ( memcmp( m_Senders[i]->m_Addr.ip, ip, 4 ) == 0 )
			return m_Senders[i];
	}
	return NULL;
}


CSender* CMessageWatchDlg::FindSenderByName( const char *pName )
{
	FOR_EACH_LL( m_Senders, i )
	{
		if ( stricmp( pName, m_Senders[i]->m_Name ) == 0 )
			return m_Senders[i];
	}
	return NULL;
}


// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CMessageWatchDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CMessageWatchDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}

void CMessageWatchDlg::OnDblclkMachines() 
{
	int index = m_Machines.GetCurSel();
	if ( index != LB_ERR )
	{
		CString str;
		m_Machines.GetText( index, str );

		CSender *pSender = FindSenderByName( str );
		if ( pSender )
			pSender->m_pConsoleWnd->SetVisible( true );
	}	
}

void CMessageWatchDlg::OnShowall() 
{
	FOR_EACH_LL( m_Senders, i )
	{
		m_Senders[i]->m_pConsoleWnd->SetVisible( true );
	}	
}

void CMessageWatchDlg::OnHideall() 
{
	FOR_EACH_LL( m_Senders, i )
	{
		m_Senders[i]->m_pConsoleWnd->SetVisible( false );
	}	
}