// TxDlg.cpp : implementation file
//
#include "stdafx.h"
#include "arcview.h"
#include "TxDlg.h"
//
#include "Globals.h"
#include "ArcX.h"

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

/////////////////////////////////////////////////////////////////////////////
// CTxDlg dialog


CTxDlg::CTxDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CTxDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CTxDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
}


void CTxDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CTxDlg)
	DDX_Control(pDX, IDC_NODEID, m_NodeID);
	DDX_Control(pDX, IDC_TIMEOUTS, m_TimeoutCount);
	DDX_Control(pDX, IDC_STOP, m_Stop);
	DDX_Control(pDX, IDC_START, m_Start);
	DDX_Control(pDX, IDC_SEQUENTIAL, m_SequentialData);
	DDX_Control(pDX, IDC_RESET, m_Reset);
	DDX_Control(pDX, IDC_RECONS, m_ReconCount);
	DDX_Control(pDX, IDC_PKTSIZE, m_PacketSize);
	DDX_Control(pDX, IDC_PKTDELAY, m_PacketDelay);
	DDX_Control(pDX, IDC_PKTCOUNT, m_PacketCount);
	DDX_Control(pDX, IDC_LOOPTEST, m_LoopTest);
	DDX_Control(pDX, IDC_LOGERR, m_LogErrors);
	DDX_Control(pDX, IDC_GOODPKTS, m_GoodPackets);
	DDX_Control(pDX, IDC_FIXEDSIZE, m_FixedPktSize);
	DDX_Control(pDX, IDC_EXCNAKS, m_ExcNaks);
	DDX_Control(pDX, IDC_DATAERR, m_DataErrors);
	DDX_Control(pDX, IDC_CHECKSUM, m_AddChecksum);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CTxDlg, CDialog)
	//{{AFX_MSG_MAP(CTxDlg)
	ON_BN_CLICKED(IDOK, OnExit)
	ON_BN_CLICKED(IDC_START, OnStart)
	ON_BN_CLICKED(IDC_STOP, OnStop)
	ON_BN_CLICKED(IDC_RESET, OnReset)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// Local Variables.
//
//static CMutex			myMutex;
//
static bool				TxRunning = FALSE;
static bool				TxExit = FALSE;
//
static int				fixedPacketSize;
static int				sequentialData;
static int				echoMode;
static int				addChecksum;
static int				logErrors;
//
static int				nAddress;
static int				packetCount;
static int				maxPacketSize;
static int				delayCount;
//
static long				lGoodPackets, lExcnaks, lRecons;
static long				lTimeouts, lPacketDataErrors;

/////////////////////////////////////////////////////////////////////////////
// Local Functions.
//
// Here to send a packet and to wait for completion.
//
int SendPacket( COM20020_TRANSMIT_BUFFER *txb , HANDLE hTxEvent )
{
	COM20020_STATUS		sts;
	DWORD				result;
	int					status = 0;

    // Read status to clear flags.
	result = Com20020Status( &sts );

	// Send the packet.
	result = Com20020Transmit( txb );

	// Return if problem.
	if ( result != 0 )
	{
 		return result;
	}

	// Wait for completion; up to 1 second.
	result = WaitForSingleObject( hTxEvent , TX_COMPLETE_WAIT );

	// If we timed out.
	if ( result != WAIT_OBJECT_0 )
	{
		// Get the error
		result = GetLastError();
		// Terminate the transmission.
		result = Com20020CancelTX();
		// We've had a timeout.
		status |= E_TIMEOUT;
	}

	// Get status.
	result = Com20020Status( &sts );

	// Are there any errors?
	if ( sts.bRecon )
		status |= E_RECON;
	if ( sts.bExcessiveNAKs )
		status |= E_EXCNAK;

	// And return.
	return status;
}
//
// Transmit thread.
//
UINT TxThread( LPVOID pParam )
{
	CTxDlg						*hDlg = (CTxDlg *)pParam;
    char						buffer[128] , *src , *dst;
	char						marker;
	int							packetWaitCount;
    int							status, result , nByteCount;
    int							nPacketLength, i , j;
	unsigned short				checksum , dchk;
    BOOL						bEndLoop;
	COM20020_TRANSMIT_BUFFER	txb;
	COM20020_RECEIVE_BUFFER		rxb;
	COM20020_STATUS				sts;
	HANDLE						hRxEvent;
	HANDLE						hTxEvent;
	HANDLE						hTmrEvent;
	FILE						*err_file;
	//CSingleLock					*mPtr;	// _DBUG

    // If we should log to disk.
	if ( logErrors )
    {
        // Open a file.
        err_file = fopen( "arcnet.tmp" , "w" );
        // If unable to open the file.
        if ( err_file == NULL )
        {
            // Bring up error message.
			hDlg->MessageBox( "Unable to open log file" , "File Open Error" , MB_OK | MB_ICONEXCLAMATION);
            // Set flag.
            TxExit = TRUE;
            // And return.
            return -1;
        }
		// Print something.
		fprintf( err_file , "Starting error file log...\r\n\r\n" );
    }

	// Mutex stuff...
	//mPtr = new CSingleLock( hDlg->m_mutex );	// _DBUG
	CSingleLock sLock( hDlg->m_mutex );

    // Create event(s).
    hRxEvent   	= CreateEvent( NULL , FALSE , FALSE , NULL );
    hTxEvent   	= CreateEvent( NULL , FALSE , FALSE , NULL );
    hTmrEvent  	= CreateEvent( NULL , FALSE , FALSE , NULL );

	// Make sure they're reset.
    ResetEvent( hRxEvent );
    ResetEvent( hTxEvent );
    ResetEvent( hTmrEvent );

    // Enable event driven operation.
    Com20020WakeOnReceive( hRxEvent );
	Com20020WakeOnTXComplete( hTxEvent );

	// Flush the receive buffer.
	result = Com20020FlushRX();
	// Get the status to clear any flags that
	// may be left over.
	result = Com20020Status( &sts );

	// Set the running flag.
	TxRunning = TRUE;

	// Clear our 'end' flag.
	bEndLoop = FALSE;

	// Zero the sequence counter.
	marker = 0;

    // Loop here.
	//
	while ( TxRunning == TRUE )
    {
		// If we're through.
		if ( bEndLoop == TRUE )
		{
			// Sleep.
			Sleep( 500 );
			// And continue.
			continue;
		}

        // Calculate packet length.
        if ( fixedPacketSize )
            nPacketLength = maxPacketSize;
        else
		{
			// 'rand() % n' gives '0 - (n-1)'.
			nPacketLength = ( rand() % maxPacketSize ) + 1;
			if ( ( nPacketLength > 253 ) && ( nPacketLength < 257 ) )
				nPacketLength = 253;
			// If it's too small.
			if ( nPacketLength < 2 )
			{
				// Make it the minimum packet size of sequence
				// number and low byte of IP.
				nPacketLength = 2;
			}
		}
		// Add sequence marker byte.
		txb.byDataBuffer[ OFS_SEQUENCE ] = marker++;
		// Add last byte of our IP address.
		txb.byDataBuffer[ OFS_IPADDR ] = host_ip_address.S_un.S_un_b.s_b4;
		// If we're to add a checksum.
		if ( addChecksum )
		{
			// Initialize checksum with sequence byte..
			checksum = txb.byDataBuffer[OFS_SEQUENCE];
			// Add in IP address byte..
			checksum += txb.byDataBuffer[OFS_IPADDR];
			// Must have at least four bytes.
			if ( nPacketLength < 4 )
				nPacketLength = 4;
			// Fill output buffer with data, skipping
			// marker and checksum bytes.
			for ( i = 4 ; i < nPacketLength ; i++ )
			{
				if ( sequentialData )
				{
					txb.byDataBuffer[i] = i;
					dchk = i;
				}
				else
				{
					j = rand() % 255;
					txb.byDataBuffer[i] = j;
					dchk = j;
				}
				// Update checksum.
				dchk &= 0x00ff;
				checksum += dchk;
			}
			// Place checksum in buffer.
			*((unsigned short *)&txb.byDataBuffer[OFS_CHECKSUM]) = checksum;
		}
		else
		{
			// No checksum; fill output buffer with data, skipping
			// marker bytes (starting at checksum location).
			for ( i = OFS_CHECKSUM ; i < nPacketLength ; i++ )
			{
				if ( sequentialData )
					txb.byDataBuffer[i] = i;
				else
					txb.byDataBuffer[i] = rand() % 255;
			}
		}
		// Setup destination node id.
		txb.byDestinationNodeID = nAddress;
		// And number of bytes.
		txb.uiNumberOfBytes = nPacketLength;
		//
		// _DBUG: delayCount is max time we'll wait for response!!!
		//
		// If we should delay between packets.
		if ( delayCount != 0 )
		{
			// Wait for that time.
			ResetEvent( hTmrEvent );
			WaitForSingleObject( hTmrEvent , delayCount );
		}
		// Send the packet.
        status = SendPacket( &txb , hTxEvent );
        // If packet was transmitted successfully.
        if ( status == 0 )
        {
			// If we should wait for echo response.
			if ( echoMode )
			{
				// Initialize wait count.
				packetWaitCount = 10;
WaitForPacket:
				// Wait for message to be received.
				result = WaitForSingleObject( hRxEvent , RX_EVENT_WAIT );
				// If we weresignaled.
				if ( result == WAIT_OBJECT_0 )
				{
UnloadPackets:
					// Get packet.
					result = Com20020Receive( &rxb );
					// If this is from the correct source/dest, has the proper sequence
					// number, and matches our IP address...
					if ( ( rxb.bySourceNodeID == nAddress ) &&
						 ( rxb.byDestinationNodeID == gNodeID ) &&
						 ( rxb.byDataBuffer[ OFS_SEQUENCE ] == txb.byDataBuffer[ OFS_SEQUENCE ] ) &&
						 ( rxb.byDataBuffer[ OFS_IPADDR ] == txb.byDataBuffer[ OFS_IPADDR ] ) )
					{
						// Check the data.
						nByteCount = rxb.uiNumberOfBytes;
						src = (char *)rxb.byDataBuffer;
						dst = (char *)txb.byDataBuffer;
						while ( nByteCount )
						{
							if ( *src++ != *dst++ )
							{
								lPacketDataErrors++;
								break;
							}
							--nByteCount;
						}
						// If all the bytes check out.
						if ( nByteCount == 0 )
							lGoodPackets++;
						// Else we have data errors.
						else
						{
							// If we're supposed to log errors.
							if ( logErrors )
							{
								// Log the tx buffer.
								writePacket( err_file , &txb );
								// Log the rx buffer.
								writePacket( err_file , &rxb );
							}
						}
					}
					else
					{
						// If we get here, we've received a packet that we
						// didn't transmit. If 'receive all' is active, we could
						// be receiving a lot of extra messages, so we discard
						// packets until we see one meant for us. If we don't,
						// we'll assume that the packet is lost, and timeout.
						//
						// If there are more packets to look at.
						if ( rxb.dwNumberOfFilledBuffers > 1 )
							// See what they are.
							goto UnloadPackets;
						// If we haven't exhausted our count yet.
						if ( packetWaitCount-- )
						{
							// Must have been meant for someone else; go back and wait
							// for our packet.
							goto WaitForPacket;
						}
					}
				}
				// Else timeout because of no response.
				else
				{
					// Increment counter.
					++lTimeouts;
					// Cancel the transmission.
					result = Com20020CancelTX();
				}
			}
			else
			{
				// Increment good packets.
				++lGoodPackets;
			}
        }
        else
        {
            if (status & E_TIMEOUT)
                lTimeouts++;
            if (status & E_RECON)
                lRecons++;
            if (status & E_EXCNAK)
                lExcnaks++;
        }
        // Are we counting packets?
        if ( packetCount != -1 )
        {
            // If we're done.
            if ( --packetCount == 0 )
            {
                // Set the flag.
                bEndLoop = 1;
            }
        }
		// Doing this so we don't lock up when trying to
		// stop the process.
		//if ( TxRunning == TRUE )
		// Lock mutex.
		if ( sLock.Lock( 1000 ) )
		{
			// Display totals.
			sprintf( buffer , "%ld" , lGoodPackets );
			hDlg->m_GoodPackets.SetWindowText( buffer );
			sprintf( buffer , "%ld" , lExcnaks );
			hDlg->m_ExcNaks.SetWindowText( buffer );
			sprintf( buffer , "%ld" , lRecons );
			hDlg->m_ReconCount.SetWindowText( buffer );
			sprintf( buffer , "%ld" , lTimeouts );
			hDlg->m_TimeoutCount.SetWindowText( buffer );
			sprintf( buffer , "%ld" , lPacketDataErrors );
			hDlg->m_DataErrors.SetWindowText( buffer );
			
			// Unlock mutex.
			sLock.Unlock();
		}
    }

	// If we've been logging errors.
	if ( logErrors )
	{
		fclose( err_file );
	}

	// End event notification.
	Com20020ResetWakeOnReceive();
	Com20020ResetWakeOnTXComplete();

	// Get rid of event handle.
	CloseHandle( hRxEvent );
	CloseHandle( hTxEvent );
	CloseHandle( hTmrEvent );

    // Set flag.
    TxExit = TRUE;

	//
	return 0;
}

/////////////////////////////////////////////////////////////////////////////
// CTxDlg message handlers

BOOL CTxDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	// Set initial values.
	fixedPacketSize = 0;
	sequentialData = 0;
	echoMode = 0;
	addChecksum = 0;
	logErrors = 0;
	//
	lGoodPackets = 0;
	lExcnaks = 0;
	lRecons = 0;
	lTimeouts = 0;
	lPacketDataErrors = 0;

	// Put initial values in edit boxes.
	m_GoodPackets.SetWindowText( "0" );
	m_ExcNaks.SetWindowText( "0" );
	m_ReconCount.SetWindowText( "0" );
	m_TimeoutCount.SetWindowText( "0" );
	m_DataErrors.SetWindowText( "0" );
	//
	m_NodeID.SetWindowText( "5" );
	m_PacketCount.SetWindowText( "0" );
	m_PacketSize.SetWindowText( "508" );
	m_PacketDelay.SetWindowText( "0" );

	// Assume we want a loop test.
	m_LoopTest.SetCheck( TRUE );

	// Create the mutex; the first FALSE means we don't own it, so that
	// anyone can get it first (we don't have to unlock it).
	m_mutex = new CMutex( FALSE , NULL );

	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CTxDlg::OnStart() 
{
	CWinThread		*TxThreadPtr;

	// Get node ID.
	nAddress = GetDlgItemInt( IDC_NODEID , NULL , TRUE );
	// Error if out of bounds.
	if ( ( nAddress < 0 ) || ( nAddress > 255 ) )
	{
		MessageBox( "Node ID must be between 0 and 255" , "Parameter Error" , MB_OK | MB_ICONEXCLAMATION );
		return;
	}

	// Get Packet count.
	packetCount = GetDlgItemInt( IDC_PKTCOUNT , NULL , TRUE );
	// Error if out of bounds.
	if ( packetCount < 0 )
	{
		MessageBox( "Packet count must be >= 0 (0=INF)" , "Parameter Error" , MB_OK | MB_ICONEXCLAMATION );
		return;
	}

	// Get packet size.
	maxPacketSize = GetDlgItemInt( IDC_PKTSIZE , NULL , TRUE );
	// Error if out of bounds.
	if ( ( maxPacketSize < 4 ) || ( maxPacketSize > 508 ) )
	{
		MessageBox( "Packet size must be between 4 and 508" , "Parameter Error" , MB_OK | MB_ICONEXCLAMATION );
		return;
	}
	if ( ( maxPacketSize > 253 ) && ( maxPacketSize < 257 ) )
	{
		MessageBox( "Packets can't be 254,255, or 256 long" , "Parameter Error" , MB_OK | MB_ICONEXCLAMATION );
		return;
	}

	// Get Packet delay.
	delayCount = GetDlgItemInt( IDC_PKTDELAY , NULL , TRUE );
	if ( delayCount < 0 )
	{
		MessageBox( "Can't go backwards in time..." , "Parameter Error" , MB_OK | MB_ICONEXCLAMATION );
		return;
	}

	// Get checkbox flags.
	fixedPacketSize = m_FixedPktSize.GetCheck();
	sequentialData = m_SequentialData.GetCheck();
	echoMode = m_LoopTest.GetCheck();
	addChecksum = m_AddChecksum.GetCheck();
	logErrors = m_LogErrors.GetCheck();

	// Create the mutex.
	//pMutex = new CMutex( TRUE , NULL , NULL);

	// Clear the flags.
	TxRunning = TxExit = FALSE;
	// Start the receive thread.
	TxThreadPtr = AfxBeginThread( TxThread , this , 0 , 0 , 0 , 0 );
	// Wait for it to start; be prepared for an early exit.
	while ( ( TxRunning == FALSE ) && ( TxExit == FALSE ) )
	{
		// Sleep for a short time.
		Sleep( 50 );
	}
	// Disable start button.
	m_Start.EnableWindow( FALSE );
	// Enable stop button.
	m_Stop.EnableWindow( TRUE );
	// Disable reset button.
	m_Reset.EnableWindow( FALSE );
}

void CTxDlg::OnStop() 
{
	// If RX thread is running.
	if ( TxRunning == TRUE )
	{
		// Create control mechanism for mutex.
		CSingleLock sLock( m_mutex );
		// Lock the mutex.
		if ( sLock.Lock( 1000 ) )
		{
			// Clear the thread running flag.
			TxRunning = FALSE;
			// Wait for it.
			while ( TxExit == FALSE )
			{
				Sleep( 500 );
			}
			// Unlock the mutex.
			sLock.Unlock();
		}
	}
	// Enable start button.
	m_Start.EnableWindow( TRUE );
	// Disable stop button.
	m_Stop.EnableWindow( FALSE );
	// Enable reset button.
	m_Reset.EnableWindow( TRUE );
}

void CTxDlg::OnReset() 
{
	// Set initial count values.
	lGoodPackets = 0;
	lExcnaks = 0;
	lRecons = 0;
	lTimeouts = 0;
	lPacketDataErrors = 0;

	// Put initial values in edit boxes.
	m_GoodPackets.SetWindowText( "0" );
	m_ExcNaks.SetWindowText( "0" );
	m_ReconCount.SetWindowText( "0" );
	m_TimeoutCount.SetWindowText( "0" );
	m_DataErrors.SetWindowText( "0" );
}

void CTxDlg::OnExit() 
{
	// If TX thread is running.
	if ( TxRunning == TRUE )
	{
		// Clear the thread running flag.
		TxRunning = FALSE;
		// Wait for it.
		while ( TxExit == FALSE )
		{
			Sleep( 500 );
		}
	}
	// And exit normally.
	CDialog::OnOK();
}
