/****************************************************************************************/
/* Talk.c																				*/
/*																						*/
/* Sample application for Contemporary Controls Com20020 Null Stack NT Driver.			*/
/* This function will send/receive ascii messages between two computers using the		*/
/* Contemporary Controls Null Stack driver.												*/
/*																						*/
/* Version: 1.1																			*/
/* Author:  Bennet Levine																*/
/* History: 03/26/99 Version 1.0 release												*/
/*			06/23/99 Modified for use with 1.1 driver. Changed to version 1.1.			*/
/*			02/18/00 Modified for use with Plug-n-Play cards.							*/
/*			02/05/02 M.WANG		Add Recon Dialog window for Event Driver mode			*/
/*			02/25/02 M.WANG		Delete resetevent in receivethread						*/
/*			10/23/03 RCW		Remove 'ResetEvent()' calls for RX and TX, as these		*/
/*								are now handled in the driver.
/*--------------------------------------------------------------------------------------*/
/*																						*/
/* Functions:																			*/
/*																						*/
/* WinMain: Standard Windows main function.												*/
/* WndProc: Standard Windows callback function.											*/
/* SettingsDialogProc: Dialog handler for the settings dialog.							*/
/* TalkDialogProc: Dialog handler for the talk dialog.									*/
/* TransmitBuffer: Function to transmit ARCNET packet.									*/
/* ReceiveThread: Thread to process all received packets and display them in the talk	*/
/*				  dialog.																*/
/* ReconThread: Thread to process all RECONs and display them in the					*/ 
/*				Number of RECONS box.													*/
/****************************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <process.h>
#include <winioctl.h>
#include "arcx.h"
#include "resource.h"

#define TRANSMIT_TMA 1
#define TRANSMIT_NAK 2
#define TRANSMIT_NA  3
#define TRANSMIT_TA  4

//if you desire to poll the 20020 driver comment out the line below
#define EVENT_DRIVEN

BYTE gbyNodeID;
HWND hReceiveWindow;
HWND hReconWindow;
BOOL bRunning, ReRunning;
HINSTANCE hInstance;
HWND hWinMain;
DWORD dwRecon=0;
HANDLE ReconEvent;

#ifdef EVENT_DRIVEN
HANDLE TxEvent, RxEvent;
#endif

void EchoThread(PVOID pvoid);
void OriginateThread(PVOID pvoid);
void ReceiveThread(PVOID pvoid);
void ReconThread(PVOID pvoid);
BYTE TransmitBuffer(BYTE byNodeID, BYTE *buffer);
//
// Local configuration if AI-SRVR used.
//
SRVR_CONFIG		cfg_local;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT APIENTRY TalkDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam);
LRESULT APIENTRY SrvrConnectDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam);
LRESULT APIENTRY SettingsDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam);
LRESULT APIENTRY ReconDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static char szAppName[] = "Talk";
	HWND hwnd;
	MSG msg;
	WNDCLASSEX wndclass;

	wndclass.cbSize		= sizeof(wndclass);
	wndclass.style		= CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc= WndProc;
	wndclass.cbClsExtra	= 0;
	wndclass.cbWndExtra	= 0;
	wndclass.hInstance	= hInstance;
	wndclass.hIcon		= LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor	= LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = szAppName;
	wndclass.lpszClassName = szAppName;
	wndclass.hIconSm	= LoadIcon(NULL, IDI_APPLICATION);

	RegisterClassEx(&wndclass);

	hwnd = CreateWindow(szAppName, 
						"Talk",
						WS_OVERLAPPEDWINDOW,
						CW_USEDEFAULT,
						CW_USEDEFAULT,
						CW_USEDEFAULT,
						CW_USEDEFAULT,
						NULL,
						NULL,
						hInstance,
						NULL);

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while(GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{

	static BOOL bEcho, bOriginate;
	char szString[50];
	HMENU hMenu;

	hMenu = GetMenu(hwnd);
	switch (iMsg)
	{
	case WM_CREATE:
		hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
		hWinMain = hwnd;
		return 0;
		
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case IDM_SRVR_CONNECT:
			if (DialogBox(hInstance, "SrvrConnectDialog", hwnd, SrvrConnectDialogProc))
			{	
				//if we were properly setup
				wsprintf(szString, "Talk - Initialized - %d", gbyNodeID);
				SetWindowText(hwnd, szString);
				EnableMenuItem(hMenu, IDM_SRVR_CONNECT, MF_GRAYED);
			}
			return 0;

		case IDM_SETTINGS:
			if (DialogBox(hInstance, "SettingsDialog", hwnd, SettingsDialogProc))
			{	
				//if we were properly setup
				wsprintf(szString, "Talk - Initialized - %d", gbyNodeID);
				SetWindowText(hwnd, szString);
				EnableMenuItem(hMenu, IDM_SETTINGS, MF_GRAYED);
			}
			return 0;

		case IDM_TALK:
			DialogBox(hInstance, "TalkDialog", hwnd, TalkDialogProc);
			return 0;

		case IDM_QUIT:
			bRunning = FALSE;
			SendMessage(hwnd, WM_DESTROY, 0, 0);
			return 0;
		}
		break;

	case WM_DESTROY:

#ifdef EVENT_DRIVEN
		Com20020ResetWakeOnReceive();
		Com20020ResetWakeOnTXComplete();
#endif
		bRunning = FALSE;
		Com20020CancelTX();
		Com20020FlushRX();
		Com20020Exit();
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

LRESULT APIENTRY SrvrConnectDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	char			hostname[128];
	long			result;

	switch (iMsg)
	{
	case WM_INITDIALOG:
		// Grey the IP Address edit box.
		SendDlgItemMessage( hDlg , IDC_IPADDRESS , WM_ENABLE , 0 , 0 );
		// Put the default port into the port box.
		SetDlgItemInt( hDlg , IDC_PORT , 5001 , 0 );
		// Grey the port box.
		SendDlgItemMessage( hDlg , IDC_PORT , WM_ENABLE , 0 , 0 );
		// Check the Auto-Detect radio button.
		CheckRadioButton( hDlg , IDC_AUTODETECT , IDC_MANUALDETECT , IDC_AUTODETECT );
		//
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_AUTODETECT:
			// Clear the IP Address edit box.
			SetDlgItemText( hDlg , IDC_IPADDRESS , "" );
			// Grey the IP Address edit box.
			SendDlgItemMessage( hDlg , IDC_IPADDRESS , WM_ENABLE , 0 , 0 );
			// Put the default port into the port box.
			SetDlgItemInt( hDlg , IDC_PORT , 5001 , 0 );
			// Grey the port box.
			SendDlgItemMessage( hDlg , IDC_PORT , WM_ENABLE , 0 , 0 );
			//
			return TRUE;

		case IDC_MANUALDETECT:
			// Enable the IP Address edit box.
			SendDlgItemMessage( hDlg , IDC_IPADDRESS , WM_ENABLE , 1 , 0 );
			// Enable the port box.
			SendDlgItemMessage( hDlg , IDC_PORT , WM_ENABLE , 1 , 0 );
			// Set keyboard focus to IP Address edit box.
			SendMessage( hDlg , WM_NEXTDLGCTL , (WPARAM)GetDlgItem( hDlg , IDC_IPADDRESS ) , TRUE );
			//
			return TRUE;

		case IDOK:
			// If manual control radio button is checked.
			if ( IsDlgButtonChecked( hDlg , IDC_MANUALDETECT ) )
			{
				// Get the hostname.
				GetDlgItemText( hDlg , IDC_IPADDRESS , hostname , sizeof( hostname ) );
				// Get port number.
				cfg_local.port = GetDlgItemInt( hDlg , IDC_PORT , &result , FALSE );
				// Point to hostname.
				cfg_local.hostname = hostname;
			}
			// Else auto-detect is selected.
			else
			{
				// Hostname is null.
				cfg_local.hostname = 0;
				// Port is null.
				cfg_local.port = 0;
			}
			// Initialize server.
			result = Com20020Init( (COM20020_CONFIG *)&cfg_local , 0 , CI_RG );
			// Show error box and return if problem.
			if(result != 0)
 			{
	 			MessageBox(hDlg, "COM20020 was not properly intialized!", "Talk Error", MB_OK | MB_ICONEXCLAMATION);
 				EndDialog(hDlg, FALSE);
				return TRUE;
 			}
			// End and return.
			EndDialog(hDlg, TRUE);
			return TRUE;

		case IDCANCEL:
			// Don't initialize.
			EndDialog(hDlg, FALSE);
			return TRUE;
		}
		break;
	}
	return FALSE;
}

LRESULT APIENTRY SettingsDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	char			szValue[32];
	WORD			wNodeID;
	COM20020_CONFIG cfg;
	UCHAR			uBoardNumber;
	UCHAR			uHardwareType;
	UCHAR			uSpeed;
	UCHAR			uTimeout;
	int				result;

	switch (iMsg)
	{
	case WM_INITDIALOG:
		// Add strings to baudrate list box.
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "156.25Kbs" );
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "312.5Kbs" );
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "625Kbs" );
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "1.25Mbs" );
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "2.5Mbs" );
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "5Mbs" );
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "7.125Mbs" );
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_ADDSTRING , 0 , (LPARAM) "10Mbs" );
		// Make sure hardware type list box shows first value.
		SendDlgItemMessage( hDlg , IDC_SPEED , CB_SETCURSEL , 4 , 0 );
		// Add strings to extended timeout list box.
		SendDlgItemMessage( hDlg , IDC_TIMEOUT , CB_ADDSTRING , 0 , (LPARAM) "Standard" );
		SendDlgItemMessage( hDlg , IDC_TIMEOUT , CB_ADDSTRING , 0 , (LPARAM) "Quad" );
		SendDlgItemMessage( hDlg , IDC_TIMEOUT , CB_ADDSTRING , 0 , (LPARAM) "Eight" );
		SendDlgItemMessage( hDlg , IDC_TIMEOUT , CB_ADDSTRING , 0 , (LPARAM) "Sixteen" );
		// Make sure hardware type list box shows first value.
		SendDlgItemMessage( hDlg , IDC_TIMEOUT , CB_SETCURSEL , 0 , 0 );
		// Add strings to board number list box.
		SendDlgItemMessage( hDlg , IDC_BOARDNUMBER , CB_ADDSTRING , 0 , (LPARAM) "Device 1" );
		SendDlgItemMessage( hDlg , IDC_BOARDNUMBER , CB_ADDSTRING , 0 , (LPARAM) "Device 2" );
		SendDlgItemMessage( hDlg , IDC_BOARDNUMBER , CB_ADDSTRING , 0 , (LPARAM) "Device 3" );
		SendDlgItemMessage( hDlg , IDC_BOARDNUMBER , CB_ADDSTRING , 0 , (LPARAM) "Device 4" );
		// Make sure hardware type list box shows first value.
		SendDlgItemMessage( hDlg , IDC_BOARDNUMBER , CB_SETCURSEL , 0 , 0 );
		// Add strings to hardware type list box.
		SendDlgItemMessage( hDlg , IDC_HWTYPE , CB_ADDSTRING , 0 , (LPARAM) "USB22" );
		SendDlgItemMessage( hDlg , IDC_HWTYPE , CB_ADDSTRING , 0 , (LPARAM) "PCI20" );
		// Make sure hardware type list box shows first value.
		SendDlgItemMessage( hDlg , IDC_HWTYPE , CB_SETCURSEL , 0 , 0 );
		break;
	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			// Get node id.			
			GetDlgItemText(hDlg, IDC_NODEID, szValue, 5);
			wNodeID = (int)strtol(szValue, NULL, 10);
			if (wNodeID > 255)
			{
				MessageBox(hDlg, "The node ID must be between 0 and 255", "Talk Error", MB_OK | MB_ICONEXCLAMATION);
				return TRUE;
			}
			gbyNodeID = (unsigned char)wNodeID;

			// Get board number.
			uBoardNumber = (unsigned char)SendDlgItemMessage( hDlg , IDC_BOARDNUMBER , CB_GETCURSEL , 0 , 0 );

			// Get hardware type.
			uHardwareType = (unsigned char)SendDlgItemMessage( hDlg , IDC_HWTYPE , CB_GETCURSEL , 0 , 0 );

			// Get extended timeout.
			uTimeout = (unsigned char)SendDlgItemMessage( hDlg , IDC_TIMEOUT , CB_GETCURSEL , 0 , 0 );
			switch ( uTimeout )
			{
			case 0:
				uTimeout = STANDARD_TIMEOUT;
				break;
			case 1:
				uTimeout = QUAD_TIMEOUT;
				break;
			case 2:
				uTimeout = EIGHT_TIMEOUT;
				break;
			case 3:
				uTimeout = SIXTEEN_TIMEOUT;
				break;
			default:
				break;
			}

			// Get speed.
			uSpeed = (unsigned char)SendDlgItemMessage( hDlg , IDC_SPEED , CB_GETCURSEL , 0 , 0 );
			switch ( uSpeed )
			{
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
				// Reversed...
				uSpeed = 4 - uSpeed;
			case 5:
				// 5 is 5MHz.
				break;
			case 6:
				// 6 is 7MHz.
				uSpeed = 7;
				break;
			case 7:
				// 7 is 10MHz
				uSpeed = 10;
				break;
			default:
				break;
			}
			 
			//parameters ok - try to initialize com20020
			cfg.uiCom20020BaseIOAddress = 0;
			cfg.byCom20020InterruptLevel = 0;
			cfg.byCom20020Timeout = uTimeout;
 			cfg.byCom20020NodeID = (unsigned char)wNodeID;
 			cfg.bCom20020_128NAKs = TRUE;
 			cfg.bCom20020ReceiveAll = FALSE;
 			cfg.byCom20020ClockPrescaler = uSpeed;
			if ( uSpeed > 5 )
 				cfg.bCom20020SlowArbitration = TRUE;
			else
 				cfg.bCom20020SlowArbitration = FALSE;

 			cfg.bCom20020ReceiveBroadcasts = TRUE;
			result = Com20020Init(&cfg, uBoardNumber , uHardwareType );
			if(result != 0)
 			{
	 			MessageBox(hDlg, "COM20020 was not properly intialized!", "Talk Error", MB_OK | MB_ICONEXCLAMATION);
 				EndDialog(hDlg, FALSE);
				return TRUE;
 			}
			EndDialog(hDlg, TRUE);
			return TRUE;

		case IDCANCEL:
			//ignore settings
			EndDialog(hDlg, FALSE);
			return TRUE;
		}
		break;
	}
	return FALSE;
}

LRESULT APIENTRY TalkDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	BYTE				byReturn;
	char				buffer[508];
	BYTE				byNodeID, byRegNumber;
	COM20020_REGISTER	reg;

	switch (iMsg)
	{
	case WM_INITDIALOG:
		hReceiveWindow = GetDlgItem(hDlg, IDC_RECEIVE);
		hReconWindow = GetDlgItem(hDlg, IDC_RECONS);
		bRunning = TRUE;

#ifdef EVENT_DRIVEN
		//setup transmit completion event
		RxEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
		Com20020WakeOnReceive(RxEvent);

		//setup receive packets available event
		TxEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
		Com20020WakeOnTXComplete(TxEvent);
#endif
		_beginthread(ReconThread, 0, &bRunning);
		_beginthread(ReceiveThread, 0, &bRunning);
		return FALSE;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_SEND:
			byNodeID = GetDlgItemInt(hDlg, IDC_NODEID_BOX, NULL, FALSE);
			if (byNodeID == 0)
			{
				//no one to send to 
				SetDlgItemText(hDlg, IDC_STATUS, "Set NodeID");
				return TRUE;
			}
			if (byNodeID == 256)
			{
				//destination node ID must be between 0 and 255
				SetDlgItemText(hDlg, IDC_STATUS, "Illegal ID");
				return TRUE;
			}
			//send the message
			GetDlgItemText(hDlg, IDC_SEND_DATA, buffer, 506);
			byReturn = TransmitBuffer(byNodeID, buffer);
			switch(byReturn)
			{
			case TRANSMIT_TMA:
				SetDlgItemText(hDlg, IDC_STATUS, "Message ACKed");
				break;
				
			case TRANSMIT_NAK:
				SetDlgItemText(hDlg, IDC_STATUS, "TX ended due to EXCNAK");
				break;

			case TRANSMIT_NA:
				SetDlgItemText(hDlg, IDC_STATUS, "TX Unavailable");
				break;

			default:
			case TRANSMIT_TA:
				SetDlgItemText(hDlg, IDC_STATUS, "TX Not ACKed");
				break;
			}
			return TRUE;
			
		case IDC_READ:
			byRegNumber = GetDlgItemInt(hDlg, IDC_REGNUMBER, NULL, FALSE);
			if (byRegNumber > 7)
				return TRUE;
			reg.bWrite = 0;
			reg.byRegister = byRegNumber;
			reg.byValue = 0;
			Com20020Register(&reg);
			SetDlgItemInt(hDlg, IDC_REGVALUE, reg.byValue, 0);
			return TRUE;

		case IDC_WRITE:
			byRegNumber = GetDlgItemInt(hDlg, IDC_REGNUMBER, NULL, FALSE);
			if (byRegNumber > 7)
				return TRUE;
			reg.bWrite = 1;
			reg.byRegister = byRegNumber;
			reg.byValue = GetDlgItemInt(hDlg, IDC_REGVALUE, NULL, FALSE);
			Com20020Register(&reg);
			return TRUE;

		case IDOK:			
		case IDCANCEL:
			bRunning = FALSE;
			dwRecon=0;
			SetEvent(ReconEvent);
			//ignore settings
			EndDialog(hDlg, FALSE);
			return TRUE;
		}
		break;
	}
	return FALSE;
}

BYTE TransmitBuffer(BYTE byNodeID, BYTE *buffer)
{
	BOOL						bInProgress, bTransmissionAcked, bReturn;
	COM20020_TRANSMIT_BUFFER	ctb;
	COM20020_STATUS				cs;
	WORD						i;

	ctb.byDestinationNodeID = byNodeID;
	ctb.uiNumberOfBytes = lstrlen(buffer);
	for (i=0; i<ctb.uiNumberOfBytes; i++)
		ctb.byDataBuffer[i] = buffer[i];
		 	
	bTransmissionAcked = FALSE; //keep transmitting until message is acknowledged
	while (!bTransmissionAcked)
	{		
		bReturn = TRUE;
		while (bReturn)
			bReturn = Com20020Transmit(&ctb);

		//wait for the message to be acknowledged by the originating node				
 		bInProgress = TRUE;

#ifdef EVENT_DRIVEN
		WaitForSingleObject(TxEvent, INFINITE);
#endif

		while (bInProgress)
		{	
			Com20020Status(&cs);
 			if (cs.bTransmissionComplete && cs.bTransmissionAcknowledged)
			{
	  			bInProgress = FALSE; //the message was properly received
				return TRANSMIT_TMA;
			}	
			if (cs.bTransmissionComplete && !cs.bTransmissionAcknowledged)
			{				
				bInProgress = FALSE;
				return TRANSMIT_TA;
			}
				
			if (cs.bExcessiveNAKs) //message was killed because of excessive NAKs
			{
				bInProgress = FALSE;
				return TRANSMIT_NAK;
			}							
		}
		return TRANSMIT_NAK;
	}
	return TRANSMIT_NAK;
}	

void ReceiveThread(PVOID pvoid)
{
	//receive all messages and place them into the receive edit control
	BOOL						*pbRunning;
 	unsigned int				i;
	COM20020_RECEIVE_BUFFER		crb;
	unsigned char				buffer[509];

	pbRunning = (BOOL *)pvoid;

	while (*pbRunning)
	{
#ifdef EVENT_DRIVEN
		if ( WaitForSingleObject(RxEvent, 50) != WAIT_OBJECT_0 )
		{
			continue;
		}
		Com20020Receive(&crb);		
		if (crb.dwNumberOfFilledBuffers <= 1)
#else
		Com20020Receive(&crb);		
#endif
		if (crb.dwNumberOfFilledBuffers) 
		{
			for (i=0; i<crb.uiNumberOfBytes; i++)
				buffer[i] = crb.byDataBuffer[i];

			buffer[i] = '\0';
			if (hReceiveWindow && *pbRunning)
				SetWindowText(hReceiveWindow, buffer);
		}
	}
}

void ReconThread(PVOID pvoid)
{
	BOOL			*pbRunning;
	HANDLE			ReconEvent;
	unsigned char	bReturn;
	DWORD			dwReturn;
	char			dbgbuffer[50];

	wsprintf(dbgbuffer, "%ld", dwRecon);
	SetWindowText(hReconWindow,dbgbuffer);
	pbRunning = (BOOL *)pvoid;
	ReconEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	bReturn = Com20020WakeOnRecon(ReconEvent);
	if (bReturn != 0)
	{
		wsprintf(dbgbuffer, "wake_on_recon DIOC returned an error:%X", GetLastError());
		MessageBox(hWinMain, dbgbuffer, "WinLoop Error", MB_OK | MB_ICONEXCLAMATION);
	}
	while (*pbRunning)
	{
		dwReturn = WaitForSingleObject(ReconEvent, INFINITE);
		ResetEvent(ReconEvent);
		if (dwReturn == WAIT_OBJECT_0)
		{
			dwRecon++;
		}
		wsprintf(dbgbuffer, "%ld", dwRecon);
		SetWindowText(hReconWindow,dbgbuffer);
	}
}
