//
// Programme de test des ports COM
//
// Limite à 12 ports (fichier ressources)
// Limite à 32 ports (1 par bits dans un long)
// Ne se compile qu'en 32 bits (Make/FreeProcInstance, CreateFile...)
//
// (c)Copyright Acksys SARL, 1995 -- JPT
//
// version
//   1.0	1995 version initiale
//   1.1	14-mai-97
//		clarification du message MSG_FIN_EMIS dans testcom.rc
//		numero de version du programme et date dans le titre
//		allongement de la taille max d'une ressource message
//		flush des messages aussitot qu'ils sont ecrits dans la liste
//   1.2	30-juillet-98
//		#define pour avoir le meme source en MSVC et BCC
//		traitement par evenements pour un meilleur parallelisme
//

#define  STRICT
#include <windows.h>
#pragma hdrstop
#include <stdio.h>
#ifndef _MSC_VER
#include <BWCC.H>	// pour les styles Borland
#endif /*_MSC_VER*/

#include "main.h"
#include "tstcomrc.h"

////////////////////////////////////////////////////////////
// Définitions des variables initialisées ici
////////////////////////////////////////////////////////////

// Les callbacks

HWND HMainDialog = NULL;
LRESULT _export CALLBACK MainDialogFunc( HWND hWnd, UINT iMessage,
					WPARAM wParam, LPARAM lParam );
LRESULT AnalyseControle(int notificationCode, int controlId, HWND control);

// Les debugs

#define WPrintf(a,b,c)	WinPrintf(a,(long)b, (long)c)
void WinPrintf(char *format, long param1, long param2);
char *StringError(void);	// Conversion de GetLastError() en texte

// L'application (fichier et ports)

// les etats
enum { IDLE_STATE, TX_STATE } AppState = IDLE_STATE;
char NomFichier[100];

// Les paramètres des ports

#define PORTCOUNT	((CHK_COMLAST+1) - CHK_COM1 + 1)
#define COMMON_BUF_SIZE	30	// buffers lecture fichier/écriture ports

typedef struct { long Value, Flag; } VITESSE, *PVITESSE;
VITESSE Vitesses[] = {
	{ 300,		BAUD_300 },
	{ 1200,		BAUD_1200 },
	{ 4800,		BAUD_4800 },
	{ 9600,		BAUD_9600 },
	{ 14400,	BAUD_14400 },
	{ 19200,	BAUD_19200 },
	{ 38400,	BAUD_38400 },
	{ 57600,	BAUD_57600 },
	{ 0, 0 }
};
#define B9600	3
PVITESSE PVitesse = &Vitesses[B9600];
COMMPROP Properties;		// pour vitesses supportees. NB: 115200 bauds
							// existe mais n'est pas supporte
							// (vitesse reelle 125 Kb)

// les ports

typedef struct {
	HANDLE		hCom;
	HANDLE		hFile;
	char		friendlyName[20];
	char		systemName[20];
	char		sendbuf[COMMON_BUF_SIZE];
	char		recvbuf[COMMON_BUF_SIZE];
	BOOL		wanted;			// TRUE si on veut tester ce port
	BOOL		isPendingRead;	// TRUE jusqu'au premier ReadFile
	int			lastWriteLen;
	DWORD		lastReadLen;
	DWORD		lastEvents;
	int			totalReadLen;
	OVERLAPPED	writeOverlap;
	OVERLAPPED	readOverlap;
	OVERLAPPED	eventOverlap;
}PORT;
PORT com[PORTCOUNT];
HANDLE WaitPortObjects[PORTCOUNT*3];	// evenements a attendre
PORT *WaitPortIndex[PORTCOUNT*3];		// leur reference au port
int ObjCount;							// nombre d'evenements utiles


int LireFichier(HANDLE hFile,char *enreg,int len);
void SendFileOnManyPorts(PVOID);
void StopTransfers(void);
int InitSelectedComs(char *sendfile);	// renvoie le nombre de ports ok
BOOL InitTimeouts(PORT *pCom, int duree);
void CloseSelectedComs(void);
void SendReceive(int count);

// Les ressources messages
#define MAXMSGNUM	50
#define MAXMSGLEN	100
char message[MAXMSGNUM][MAXMSGLEN];

////////////////////////////////////////////////////////////
// Fonctions
////////////////////////////////////////////////////////////

#ifndef _MSC_VER
#pragma argsused
#endif /*_MSC_VER*/
int
InitAnyInstance(HINSTANCE hInst, HINSTANCE prev)
{
#ifndef _MSC_VER
	// Forcer le link avec les classes Borland.
	BWCCRegister(hInst);
#endif /*_MSC_VER*/
	

	// Charger la table des messages. A faire en tout premier lieu.
	int i = 1;
	while( LoadString(hInst, i, (LPSTR)message[i], MAXMSGLEN) )
		i++;

	HMainDialog = CreateDialog(hInst,
							MAKEINTRESOURCE(FENETRE_PRINCIPALE),
							NULL,
							(DLGPROC)MainDialogFunc
						);
	if(HMainDialog == NULL){
		MessageBox(NULL, message[MSG_DLG_CREAT], message[MSG_INTERNE],
				MB_APPLMODAL | MB_ICONSTOP | MB_OK);
		return 0;
	}

	return 1;	// OK, continuer
}

int MustProcessMainLoop(MSG *pMsg)
{
	if(HMainDialog)
		return !IsDialogMessage(HMainDialog,pMsg);
	else
		return TRUE;
}

#ifndef _MSC_VER
#pragma argsused		// Turn off warning: Parameter is never used
#endif /*_MSC_VER*/
LRESULT _export CALLBACK
MainDialogFunc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	switch (iMessage)
	{
		case WM_INITDIALOG:
			//
			// ATTENTION: CreateDialog() n'est pas encore terminé,
			// HMainDialog n'est donc pas initialisé
			//
			// Afficher une chaine initiale :
			SetDlgItemText(hWnd,SAIS_FICHIER,message[MSG_FILE_DFLT]);
			// Sélectionner toute la chaine :
			SendDlgItemMessage(hWnd,SAIS_FICHIER,
							(WORD)EM_SETSEL,
							NULL,
							MAKELONG(0,0x7fff));
			return TRUE;	// focus donné au 1er controle

		case WM_COMMAND:
			// The WM_COMMAND message is sent when a control sends a
			// notification message.
			// If an application processes this message, it should return zero.
			//
			// HIWORD(wParam):	notification code
			// LOWORD(wParam);	control identifier
			// (HWND)lParam;		handle of control
			return AnalyseControle(HIWORD(wParam), LOWORD(wParam), (HWND)lParam);

		case WM_CLOSE:					// Bouton systeme CLOSE
			if(AppState == TX_STATE) {
				StopTransfers();
			}
			DestroyWindow(hWnd);		// provoque appel WM_DESTROY
			break;

		case WM_DESTROY:
			PostQuitMessage( 0 );	// Provoque sortie boucle principale
			break;
	}
	return 0;
}

#ifndef _MSC_VER
#pragma argsused
#endif /*_MSC_VER*/
LRESULT
AnalyseControle(int notificationCode, int controlId, HWND hControl)
{
	switch(controlId)
	{
		case BTN_EMETTRE:{

			if(AppState == TX_STATE) {
				StopTransfers();
				break;
			}

			// Demander au controle SAIS_FICHIER de renvoyer le texte saisi
			GetDlgItemText(HMainDialog,SAIS_FICHIER,
							(LPSTR)NomFichier,
							sizeof(NomFichier));

			// Faire la liste des ports à traiter
			for( int i=CHK_COM1; i<=CHK_COMLAST; i++ ){
				// interroger le checkBox correspondant
				if(i-CHK_COM1 < PORTCOUNT)
					com[i-CHK_COM1].wanted = IsDlgButtonChecked(HMainDialog,i);
			}
			// Envoyer le fichier sur les ports choisis
			AppState = TX_STATE;	// only the send thread can reset this
			HANDLE thd;
			DWORD tid;
			thd = CreateThread(NULL,0,
				(LPTHREAD_START_ROUTINE)SendFileOnManyPorts,NULL,0,&tid);
			if(thd == NULL){
				AppState = IDLE_STATE;
				WPrintf(message[MSG_THR_OPEN],
						"SendFileOnManyPorts",StringError());
				exit(1);
			}
			break;
		}

		case BTN_EFFACER:{
			HWND hList = GetDlgItem(HMainDialog,LST_INFO);
			SendMessage(hList, LB_RESETCONTENT, 0L, 0L);
			break;
		}

		case BTN_COMPARAMS:{
			// Cycler dans la liste des vitesses
			PVitesse++;
			if( PVitesse->Value == 0 )
				PVitesse = Vitesses;

			// Afficher la vitesse dans le bouton (espace réduit)
			char buf[12];
			if( PVitesse->Value < 10000 )
				sprintf(buf,"%ld bds",*PVitesse);
			else if( PVitesse->Value < 100000 )
				sprintf(buf,"%ld b.",*PVitesse);
			else
				sprintf(buf,"%ld b",*PVitesse);
			SetDlgItemText(HMainDialog,BTN_COMPARAMS,buf);
			break;
		}

		default:
			return 1;	// message non traité
	}
	return 0;			// pour les messages traités
}

void
WinPrintf(char *format, long param1, long param2)
{
	char buf[100];

	sprintf(buf,format,param1,param2);
	int rc = SendDlgItemMessage(HMainDialog, LST_INFO,
							LB_ADDSTRING, NULL, (LPARAM)(LPSTR)buf);
	if (rc == LB_ERR || rc == LB_ERRSPACE){
		MessageBox(HMainDialog, message[MSG_LB_ERR], message[MSG_INTERNE],
				  MB_APPLMODAL | MB_ICONEXCLAMATION | MB_OK);
	}
	if (!UpdateWindow(HMainDialog)){
		MessageBox(HMainDialog, message[MSG_LB_ERR], message[MSG_INTERNE],
				  MB_APPLMODAL | MB_ICONEXCLAMATION | MB_OK);
	}
}

char *
StringError()
{
	int len;
	static char *lpMsgBuf = NULL;

	if(lpMsgBuf) {
		// Free the buffer.
		LocalFree(lpMsgBuf);
	}

	FormatMessage( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
		(LPTSTR) &lpMsgBuf,
		0,
		NULL 
		);
	len = strlen(lpMsgBuf);
	while(len > 0 && (lpMsgBuf[len-1] == '\n' || lpMsgBuf[len-1] == '\r'))
		lpMsgBuf[--len] = '\0';

	return lpMsgBuf;
}

void
SendFileOnManyPorts(PVOID arg)
{
	int count;
	count = InitSelectedComs(NomFichier);
	if( count != 0 ) {
		WPrintf(message[MSG_DEB_EMIS],0,0);
		SendReceive(count);
		WPrintf(message[MSG_FIN_EMIS],0,0);
		CloseSelectedComs();
		WPrintf(message[MSG_FIN_CLS],0,0);
	} else {
		WPrintf(message[MSG_PAS_EMIS],0,0);
	}
	AppState = IDLE_STATE;
}

void
StopTransfers()
{
	PORT *pCom;			// index dans la table com[]

	for(pCom=com; pCom < &com[PORTCOUNT]; pCom++) {
		if( pCom->wanted ){
			CloseHandle(pCom->hFile);
			PurgeComm(pCom->hCom,PURGE_TXCLEAR|PURGE_TXABORT);
		}
	}
}

int LireFichier(HANDLE hFile,char *enreg,int len)
{
	BOOL rc;
	DWORD readLen;

	rc = ReadFile(hFile, enreg, len, &readLen, NULL);
	if( !rc ){
		if(GetLastError() == ERROR_INVALID_HANDLE)
			return 0;	// erreur provoquee par action sur la boite de Dialogue
		WPrintf(message[MSG_FILE_READ], StringError(), 0);
		return -1;
	}
	return readLen;
}

//
// Initialise les ports choisis et inhibe les ports en erreur
//
int InitSelectedComs(char *nomFichier)
{
	int numCom;				// numero du 1er nom de COM possible
	PORT *pComTab;			// index dans la table com[]
	int goodPortCount = 0;

	numCom = 1;				// numero du 1er nom de COM possible
	ObjCount = 0;
	for(pComTab=com; pComTab < &com[PORTCOUNT]; pComTab++, numCom++) {
		if( pComTab->wanted ){
			sprintf(pComTab->friendlyName,message[MSG_UCOM_FMT],numCom);
			sprintf(pComTab->systemName,message[MSG_SCOM_FMT],numCom);
			// Sous Windows 95 on peut ouvrir indifféremment COMx ou \\.\COMx
			// Sous Windows NT 3.1 on doit ouvrir \\.\COMx
			pComTab->hCom = CreateFile(pComTab->systemName,
									GENERIC_READ|GENERIC_WRITE,
									0,				// requis par  la doc Win32
									NULL,
									OPEN_EXISTING,	// requis par  la doc Win32
									FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
									NULL);			// requis par  la doc Win32
			if( pComTab->hCom == INVALID_HANDLE_VALUE ){
				WPrintf(message[MSG_COM_OPEN],
							pComTab->systemName,StringError());
				pComTab->wanted = FALSE;
				continue;
			}
			// Initialiser les paramètres du port
			DCB dcb;
			COMMPROP commProp;

			if( !GetCommProperties(pComTab->hCom,&Properties) ){
				WPrintf(message[MSG_COM_GETP], pComTab->friendlyName, StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}
			if( (PVitesse->Flag & Properties.dwSettableBaud) == 0 ){
				WPrintf(message[MSG_COM_BAUD], pComTab->friendlyName, StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}
			if( !GetCommState(pComTab->hCom,&dcb) ){
				WPrintf(message[MSG_COM_GETS], pComTab->friendlyName, StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}
			dcb.BaudRate = PVitesse->Value;
			if( !SetCommState(pComTab->hCom,&dcb) ){
				WPrintf(message[MSG_COM_SETS], pComTab->friendlyName, StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}

			if( !SetupComm(pComTab->hCom,10,COMMON_BUF_SIZE) ){
				WPrintf(message[MSG_COM_SETB], pComTab->friendlyName, StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}
			if( !SetCommMask(pComTab->hCom,EV_TXEMPTY) ){
				WPrintf(message[MSG_COM_SETM], pComTab->friendlyName, StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}
			if( !InitTimeouts(pComTab,0) ){
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}

			// ouvrir le fichier a émettre
			pComTab->hFile = CreateFile(
								nomFichier,
								GENERIC_READ,
								FILE_SHARE_READ|FILE_SHARE_WRITE,
								NULL,
								OPEN_EXISTING,
								FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
								NULL);
			if( pComTab->hFile == INVALID_HANDLE_VALUE ){
				WPrintf(message[MSG_FILE_OPEN],nomFichier,StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				continue;
			}
			// Initialiser l'écriture parallèle sur les ports
			// les read/write Event est cree signale pour demarrer l'emission
			pComTab->writeOverlap.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
			pComTab->readOverlap.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
			pComTab->eventOverlap.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
			if(pComTab->writeOverlap.hEvent == INVALID_HANDLE_VALUE
			  || pComTab->readOverlap.hEvent == INVALID_HANDLE_VALUE
			  || pComTab->eventOverlap.hEvent == INVALID_HANDLE_VALUE){
				WPrintf(message[MSG_EVT_CREAT], pComTab->friendlyName, StringError());
				pComTab->wanted = FALSE;
				CloseHandle(pComTab->hCom);
				CloseHandle(pComTab->hFile);
				CloseHandle(pComTab->writeOverlap.hEvent);
				CloseHandle(pComTab->readOverlap.hEvent);
				CloseHandle(pComTab->eventOverlap.hEvent);
				continue;
			}
			pComTab->isPendingRead = FALSE;
			pComTab->totalReadLen = 0;
			pComTab->lastReadLen = 0;
			pComTab->lastEvents = 0;
			goodPortCount++;
			WaitPortIndex[ObjCount] = pComTab;
			WaitPortObjects[ObjCount++] = pComTab->writeOverlap.hEvent;
			WaitPortIndex[ObjCount] = pComTab;
			WaitPortObjects[ObjCount++] = pComTab->readOverlap.hEvent;
			WaitPortIndex[ObjCount] = pComTab;
			WaitPortObjects[ObjCount++] = pComTab->eventOverlap.hEvent;
		}
	}
	return ObjCount / 3;
}

BOOL
InitTimeouts(PORT *pCom, int duree)
{
	COMMTIMEOUTS ct;

	ct.ReadIntervalTimeout = 0;
	ct.ReadTotalTimeoutMultiplier = 0;
	ct.ReadTotalTimeoutConstant = duree;
	ct.WriteTotalTimeoutMultiplier = 0;
	ct.WriteTotalTimeoutConstant = 0;
	if(!SetCommTimeouts(pCom->hCom,&ct)) {
		WPrintf(message[MSG_COM_SETM], pCom->friendlyName, StringError());
		return FALSE;
	}
	return TRUE;
}

void CloseSelectedComs()
{
	PORT *pCom;			// index dans la table com[]
	BOOL rc;

	//
	// terminer les emissions en cours
	//
	for(pCom=com; pCom < &com[PORTCOUNT]; pCom++) {
		if( pCom->wanted ){
			FlushFileBuffers(pCom->hCom);
		}
	}

	//
	// arreter les lectures en cours et en relancer d'autres avec timeout
	//
	for(pCom=com; pCom < &com[PORTCOUNT]; pCom++) {
		if( pCom->wanted ){

			PurgeComm(pCom->hCom,PURGE_RXABORT);
			if(pCom->isPendingRead) {
				rc = GetOverlappedResult(
								pCom->hCom,
								&pCom->readOverlap,
								&pCom->lastReadLen,
								TRUE);	// attente fin du read
				if( !rc && GetLastError() != ERROR_OPERATION_ABORTED){
					WPrintf(message[MSG_ERR_OVER], pCom->friendlyName,
							StringError());
				}
				pCom->totalReadLen += pCom->lastReadLen;
			}

			InitTimeouts(pCom,COMMON_BUF_SIZE*12000/Vitesses[0].Value);

			rc = ReadFile(
					pCom->hCom,
					pCom->recvbuf,
					sizeof(pCom->recvbuf),
					NULL,
					&pCom->readOverlap);
			if( !rc && GetLastError() != ERROR_IO_PENDING ){
				WPrintf(message[MSG_COM_READ], pCom->friendlyName,
					StringError());
			}
		}
	}

	for(pCom=com; pCom < &com[PORTCOUNT]; pCom++) {
		if( pCom->wanted ){
			rc = GetOverlappedResult(
							pCom->hCom,
							&pCom->readOverlap,
							&pCom->lastReadLen,
							TRUE);	// attente fin du read
			if( !rc ){
				WPrintf(message[MSG_ERR_OVER], pCom->friendlyName,
						StringError());
			}
			pCom->totalReadLen += pCom->lastReadLen;

			if(pCom->totalReadLen)
				WPrintf(message[MSG_RES_READ], pCom->friendlyName,
					pCom->totalReadLen);

			CloseHandle(pCom->hCom);
			CloseHandle(pCom->hFile);
			CloseHandle(pCom->writeOverlap.hEvent);
			CloseHandle(pCom->readOverlap.hEvent);
			CloseHandle(pCom->eventOverlap.hEvent);
		}
	}
}

void SendReceive(int count)
{
  	int finished = 0;	// nb de ports ou l'emission est terminee

	while (finished != count)
	{
		int rc;
		int len;
		PORT *pCom;

		rc = WaitForMultipleObjects(
					ObjCount,
					WaitPortObjects,
					FALSE,
					INFINITE);

		if(rc >= WAIT_OBJECT_0 && rc < WAIT_OBJECT_0+ObjCount) {
			// un event attendu est arrive
			rc -= WAIT_OBJECT_0;
			pCom = WaitPortIndex[rc];
			ResetEvent(WaitPortObjects[rc]);
			switch(rc%3) {
			case 0:		// write end
				len = LireFichier(
							pCom->hFile,
							pCom->sendbuf,
							sizeof(pCom->sendbuf));
				if(len > 0) {
					// NOUVELLE ECRITURE
					DWORD writtenLen;
					pCom->lastWriteLen = len;
					rc = WriteFile(
							pCom->hCom,
							pCom->sendbuf,
							len,
							&writtenLen,
							&pCom->writeOverlap);
					if( !rc && GetLastError() != ERROR_IO_PENDING ){
						WPrintf(message[MSG_COM_WRIT], pCom->friendlyName,
								StringError());
					}
				} else {
					// transfert termine
				}
				break;

			case 1:		// read end

				if(pCom->isPendingRead) {
					rc = GetOverlappedResult(
								pCom->hCom,
								&pCom->readOverlap,
								&pCom->lastReadLen,
								TRUE);	// attente fin du read
					if( !rc ){
						WPrintf(message[MSG_ERR_OVER], pCom->friendlyName,
								StringError());
					}
				}
				do {
					if(pCom->isPendingRead)
						pCom->totalReadLen += pCom->lastReadLen;

					rc = ReadFile(
							pCom->hCom,
							pCom->recvbuf,
							sizeof(pCom->recvbuf),
							&pCom->lastReadLen,
							&pCom->readOverlap);
					pCom->isPendingRead=TRUE;
				} while(rc);	// si les donnees sont immediatement pretes
				if( !rc && GetLastError() != ERROR_IO_PENDING ){
					WPrintf(message[MSG_COM_READ], pCom->friendlyName,
						StringError());
				}
				break;

			case 2:		// com event
				if( pCom->lastEvents & EV_TXEMPTY ) {
					finished++;
				} else {
					do {
						rc = WaitCommEvent(
								pCom->hCom,
								&pCom->lastEvents,
								&pCom->eventOverlap);
					} while( rc && !(pCom->lastEvents & EV_TXEMPTY) );

					if( pCom->lastEvents & EV_TXEMPTY ) {
						finished++;
					} else {
						if( GetLastError() != ERROR_IO_PENDING )
							WPrintf(message[MSG_COM_EVNT], pCom->friendlyName,
								StringError());
					}
				}
				break;
			}
		} else {
			// event inconnu ou erreur Wait
			if(rc == WAIT_FAILED)
				WPrintf(message[MSG_ERR_WAIT], StringError(), 0);
			else
				WPrintf(message[MSG_UNK_WAIT], rc, 0);
		}
	}
}
