/*
	--- English/Anglais ---
	This program shows how to use LAP-B (HDLC/ABM) under Windows NT.
	the two serial channels must be connected together with a null-modem
	cable (crossing the clocks as well) or a couple of modems.
	The transmission parameters (speed, signal management) must be
	set up on both channels before using this program.
	You MUST start the 'net' side before the 'host' side'.

	--- French/Francais ---
	Demonstration de l'utilisation de LAP-B (HDLC/ABM) sous Windows NT
	Les deux voies doivent etre reliees avec un cable croise (croisant
	aussi les horloges) ou bien une paire de modems.
	Les parametres de transmission (vitesse, gestion des signaux)
	doivent etre prealablement initialises sur les deux voies.
	vous DEVEZ lancer le cote 'net' avant le cote 'host'.

	Example/Exemple:
		mcxmode com3:9600,n,8,l,nullmdm
		mcxmode com4:9600,n,8,l,nullmdm
		start lapbdemo com3 net x
		start lapbdemo com4 host x
	Avec horloges ettd/etcd:
		mcxmode com3:9600,n,8,l,ettd
		mcxmode com4:9600,n,8,l,etcd
		start lapbdemo com3 net out
		start lapbdemo com4 host in

		--- English/Anglais ---
		Type some text in the 'host' console. Each time [Enter] is
		typed, the data is transmitted and displayed in the 'net'
		console.
		Type [Control-Z] and [Enter] to request a disconnection and
		finish the test.
		The receiver detects the request and stops also.
		Typing [Control-C] will stop either side but wont request a
		disconnection.

		--- French/Francais ---
		Taper du texte dans la console associee a 'host'. A chaque
		appui sur [Entree] le texte est transmis et s'affiche dans
		la console associee a 'net'.
		Taper [Control-Z] et [Entree] pour terminer et demander une
		deconnexion.
		Le recepteur detecte la deconnexion et s'arrete aussi.
		Taper [Controle-C] permet d'arreter l'emetteur comme le
		recepteur mais ne provoque pas de deconnexion logique.
*/

#include <windows.h>
#include <winuser.h>
#include <winioctl.h>
#include <stdio.h>
#include <mcxproto.h>
#include <ack_w32.h>

#define DBG_SYSCALL	if(0)fprintf
void CreateStateTask(HANDLE hfd);
void Receive(HANDLE port);
void Send(HANDLE port);

#define ARG_PROG	argv[0]
#define ARG_PORT	argv[1]
#define ARG_ROLE	argv[2]
#define ARG_CLOCK	argv[3]

void
main(int argc,char **argv)
{
	HANDLE port;
	BOOLEAN wait, active;		// uniquement pour la clarte
	char role;
	BOOLEAN nullmodem;
	char ConsoleTitle[100];

	if(argc != 4) {
usage:
		fprintf(stderr,"\n");
		fprintf(stderr,"Usage: %s nom role(Host/Net) clock(in/out/x)\n",
			ARG_PROG);
		fprintf(stderr,"\n");
		fprintf(stderr,"\tHost\tAddress=1/B, call initiator\n");
		fprintf(stderr,"\tNet \tAddress=3/A, call answerer\n");
		fprintf(stderr,"\tin  \tRxClk & TxClk=inputs\n");
		fprintf(stderr,"\tout \tRxClk & TxClk=outputs\n");
		fprintf(stderr,"\tx   \tRxClk=input, TxClk=output\n");
		exit(1);
	}

	role = ARG_ROLE[0];
	if(role == 'h') role = 'H';

	if(stricmp(ARG_CLOCK, "in") == 0) {
		if(role != 'H') {
			fprintf(stderr,"role=%c & in incompatibles\n", role);
			goto usage;
		}
		nullmodem = FALSE;
	} else if(stricmp(ARG_CLOCK, "out") == 0) {
		if(role == 'H') {
			fprintf(stderr,"role=%c & out incompatibles\n", role);
			goto usage;
		}
		nullmodem = FALSE;
	} else if(stricmp(ARG_CLOCK, "x") == 0) {
		nullmodem = TRUE;
	} else {
		goto usage;
	}

	sprintf(ConsoleTitle,"%s using %s as %s", ARG_PROG, ARG_PORT,
		(role == 'H') ? "host" : "network");
	if(!SetConsoleTitle(ConsoleTitle)) {
		printf("SetConsoletitle: error %d\n", GetLastError());
	}
	DBG_SYSCALL(stderr,"Setup");
	port = McxLapbSetup(ARG_PORT,
			(BOOLEAN)(role=='H'),
			nullmodem,
			MCX_FRAMELEN_DEFAULT,
			FILE_FLAG_OVERLAPPED);
	if(port == INVALID_HANDLE_VALUE){
		printf("LapbSetup: error %d\n", GetLastError());
		exit(1);
	}

	DBG_SYSCALL(stderr,"Disconnect (precaution)\n");
	if(!McxLapbDisconnect(port, wait=FALSE)){
		printf("LapbConnect: error %d\n", GetLastError());
	}

	DBG_SYSCALL(stderr,"Connect");
	if(!McxLapbConnect(port, active=(role=='H'), wait=TRUE)){
		printf("LapbConnect: error %d\n", GetLastError());
		if(GetLastError() == ERROR_NOT_READY) {
			printf("Did you start the network side ?\n");
		}
		exit(1);
	}

	if(role == 'H')
		Send(port);
	else
		Receive(port);

	if(!McxLapbDisconnect(port, wait=TRUE)){
		printf("LapbDisconnect: error %d\n", GetLastError());
	}
	if(!CloseHandle(port)){
		printf("CloseHandle: error %d\n", GetLastError());
	}
}

void Receive(HANDLE port)
{
	HANDLE thd;
	int count;
	char buf[100];
	int lastState = MCX_LINK_OFF;

	CreateStateTask(port);
	AcksysInitAsyncRead();

	DBG_SYSCALL(stderr,"Read");
	count = AcksysAsyncRead(port,buf,sizeof(buf));
	while(count > 0 || (count < 0 && (count=AcksysWaitRead(port)) > 0)) {
		int state;

		printf("received %d chars:<%*.*s>\n",
			count, count, count, buf);

		DBG_SYSCALL(stderr,"State");
		state = McxLapbState(port);
		if(lastState != state){
			printf("Read: State change: %s -> %s\n",
				McxLapbStateToString(lastState),
				McxLapbStateToString(state));
			lastState = state;
		}

		DBG_SYSCALL(stderr,"Read");
		count = AcksysAsyncRead(port,buf,sizeof(buf));
	}

	// standard EOF indication
	if(!count || GetLastError() == ERROR_HANDLE_EOF){
		printf("Link level lost!\n");
	} else {
		printf("ReadFile: error %d\n", GetLastError());
	}
	SetCommMask(port,0);	// cancels LapbWait(WaitCommEvent)

	if(!FlushFileBuffers(port)){
		printf("FlushFileBuffers: error %d\n", GetLastError());
	}
}

void Send(HANDLE port)
{
	HANDLE thd;
	int count;
	char buf[100];
	int lastState = MCX_LINK_OFF;

	CreateStateTask(port);
	AcksysInitAsyncWrite();
	printf("Type in some text:");
	while(fgets(buf,sizeof(buf),stdin) != NULL){
		int state;

		buf[strlen(buf)-1] = '\0';	// oter le '\n'
		DBG_SYSCALL(stderr,"Write");
		count = AcksysAsyncWrite(port, buf, strlen(buf));
		if(count < 0) {
			DBG_SYSCALL(stderr,"WaitWrite");
			count = AcksysWaitWrite(port);
		}
		if(count < 0) {
			if(GetLastError() == ERROR_NOT_READY){
				printf("Link level lost!\n");
				break;
			}
			printf("WriteFile: error %d\n", GetLastError());
			exit(1);
		}
		DBG_SYSCALL(stderr,"State");
		state = McxLapbState(port);
		if(lastState != state){
			printf("Write: State change: %s -> %s\n",
				McxLapbStateToString(lastState),
				McxLapbStateToString(state));
			lastState = state;
		}
		DBG_SYSCALL(stderr,"gets");
	}

	SetCommMask(port,0);	// cancels LapbWait(WaitCommEvent)

	if(!FlushFileBuffers(port)){
		printf("FlushFileBuffers: error %d\n", GetLastError());
	}
}

DWORD WINAPI StateTask(LPVOID *p);
void CreateStateTask(HANDLE hfd)
{
	DWORD id;
	if(AcksysCreateThread(StateTask, hfd) == NULL) {
		printf("err thread %d\n",GetLastError());
		exit(1);
	}
	Sleep(50);	// Yield
}

DWORD WINAPI StateTask(LPVOID *p)
{
	DWORD Events;
	DWORD count;
	HANDLE port = (HANDLE)p;
	int lastState = MCX_LINK_OFF;

	printf("Demarrage de la tache de WaitCommEvent\n");
	SetCommMask(port,EV_MCXLAPB);
	lastState = McxLapbState(port);
	for(;;){
		int state;

		switch(Events = McxLapbWait(port)) {
		case 0:	return 0;
		case -1: fprintf(stderr,"LapbWait error!\n"); return -1;
		}

		DBG_SYSCALL(stderr,"State");
		state = McxLapbState(port);
		if(lastState != state){
			printf("State change: %s -> %s\n",
				McxLapbStateToString(lastState),
				McxLapbStateToString(state));
			lastState = state;
		}
	}
}
