/* Serial logging application */
/* Written by Mike Miller */

#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#include <string.h>

typedef struct _LOGSER_CFG {
	int port;
	bool save;
	bool overwrite;
	bool display;
	DWORD baudRate;
	BYTE parity;
	BYTE byteSize;
	BYTE stopBits;
} LOGSER_CFG;

void usage(_TCHAR * argv[]);
void print_header(LOGSER_CFG cfg);
int parse_arguments(int argc, _TCHAR * argv[], LOGSER_CFG * cfg);
void error(HANDLE hCom, const char *fmt, ...);

int _tmain(int argc, _TCHAR * argv[])
{
	HANDLE hCom = INVALID_HANDLE_VALUE;
	char com_name[11] /* \\.\.COMXX plus a null */ ;
	char filename[10] /* comXX.txt plus a null */ ;
	DCB dcb;
	BYTE byte;
	DWORD bytesRead;
	FILE *fp = NULL;
	LOGSER_CFG cfg;
	int stat;

	stat = parse_arguments(argc, argv, &cfg);
	if (stat != 0) {
		return stat;
	}

	print_header(cfg);

	sprintf(com_name, "\\\\.\\COM%d", cfg.port);	/* Actual string should be \\.\COM */

	printf("| Opening port %d (as %s)...\t|\n", cfg.port, com_name);

	hCom = CreateFile(com_name, GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL);
	if (hCom == INVALID_HANDLE_VALUE) {
		error(hCom, "| Error opening %s!\t\t|\n", com_name);
		return 1;
	}
	printf("| handle=0x%02X\t\t\t\t|\n", (INT_PTR) hCom);

	/* Configure the serial port */
	memset(&dcb, 0, sizeof(dcb));
	dcb.DCBlength = sizeof(dcb);

	GetCommState(hCom, &dcb);
	dcb.BaudRate = cfg.baudRate;
	dcb.ByteSize = cfg.byteSize;
	dcb.Parity = cfg.parity;
	dcb.StopBits = cfg.stopBits;

	dcb.fBinary = FALSE;
	dcb.fParity = TRUE;

	if (!SetCommState(hCom, &dcb)) {
		error(hCom, "| Unable to set Comm Port config.\t|\n| Error was 0x%08X\t\t\t|\n",
			GetLastError());
		return 1;
	}

	/* Configure timeouts */
	COMMTIMEOUTS timeouts;
	/* No timeouts */
	timeouts.ReadIntervalTimeout = MAXDWORD;
	timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
	timeouts.ReadTotalTimeoutConstant = MAXDWORD - 1;
	timeouts.WriteTotalTimeoutMultiplier = 0;
	timeouts.WriteTotalTimeoutConstant = 0;

	if (!SetCommTimeouts(hCom, &timeouts)) {
		printf("| Unable to set Comm Port timeouts.\t|\n| Error was 0x%08X\t\t\t|\n", GetLastError());
		return 1;
	}

	/* Open the output file */
	if (cfg.save == true) {
		sprintf(filename, "com%02d.txt", cfg.port);
		if (cfg.overwrite == true)
			fp = fopen(filename, "wS");	/* Overwrite, caching optimized for sequencial access */
		else
			fp = fopen(filename, "a+S");	/* Append, remove EOF, caching optimized for sequencial access */

		if (fp == NULL) {
			error(hCom, "| Unable to open output file %s!\t|\n", filename);
			return 1;
		}
	}

	printf("\\=======================================/\n");
	/* Everyone's ready. Let's roll */

	/* Read byte-by-byte, and print */
	/* We do a flush once per line. Not sure how much it helps... */
	/* This sounds inefficient, but it seems to work relatively well (<1% CPU on my test PC) */
	for (;;) {
		do {
			ReadFile(hCom, &byte, 1, &bytesRead, NULL);
			if (cfg.display == true)
				printf("%c", byte);
			if (cfg.save == true)
				fprintf(fp, "%c", byte);
		}
		while (byte != '\n' && byte != '\r');
		if (cfg.save == true)
			fflush(fp);
	}

	/* We never reach this point, and so we rely on windows to close the COM port & output file */
	/* The code is here, just in case, but is commented out to remove the warning */
	/*
	CloseHandle(hCom);
	fclose(fp);
	return 0;
	*/

}

void usage(_TCHAR * argv[])
{
	printf("Serial Logger by Mike Miller\n");
	printf("Displays and/or logs serial communication\n\n");
	printf("Usage:\t%s <com_port> -d -s -o\n", argv[0]);
	printf("\tCOMM port should be specified in decimal (Supported ports: 1-32)\n");
	printf("\tTo display data only, use -d\n");
	printf("\tTo save data only, use -s\n");
	printf("\t  The default is append to the end of a file. To overwrite add -o\n");
	printf("\tTo configure the baud rate, use -b <baudrate> (default is 115200)\n");
	printf("\tTo configure parity, use -p followed by the config (default is 8N1)\n");
	return;
}

int parse_arguments(int argc, _TCHAR * argv[], LOGSER_CFG * cfg)
{
	if (argc < 2) {
		usage(argv);
		return 1;
	}

	/* The first argument must be the port. Flags can come in any order after that */
	sscanf(argv[1], "%d", &(cfg->port));
	
	if ((cfg->port < 1) || (cfg->port > 32)) {
		usage(argv);
		return 1;
	}

	cfg->save = true;
	cfg->display = true;
	cfg->overwrite = false;
	cfg->baudRate = CBR_115200;
	cfg->byteSize = 8;
	cfg->parity = NOPARITY;
	cfg->stopBits = ONESTOPBIT;
	// __asm int 3h;

	if (argc != 2)		/* We have flags */
		for (int cur_arg = 2; cur_arg < argc; cur_arg++) {
			if (0 == strncmp(argv[cur_arg], "-o", 2))	/* Overwrite */
				cfg->overwrite = true;
			if (0 == strncmp(argv[cur_arg], "-d", 2)) {	/* Display only */
				cfg->display = true;
				cfg->save = false;
			}
			else if (0 == strncmp(argv[cur_arg], "-s", 2)) {	/* Save only */
				cfg->save = true;
				cfg->display = false;
			}
			else if (0 == strncmp(argv[cur_arg], "-b", 2)) { /* baudRate */
				if (!sscanf(argv[++cur_arg], "%d", &(cfg->baudRate)))
				{
					printf("Invalid baud rate %s\n",argv[cur_arg]);
					return 1;
				}
			}
			else if (0 == strncmp(argv[cur_arg], "-p", 2)) { /* baudRate */
				int foo;
				char p;
				char s;
				char bs;
				foo = sscanf(argv[++cur_arg], "%c%c%c",&bs,&p,&s);
				cfg->byteSize = (BYTE) atoi(&bs);
				if (foo == 3) {
					switch (p) {
				case 'N':
				case 'n':
					cfg->parity=(BYTE) NOPARITY;
					break;
				case 'E':
				case 'e':
					cfg->parity=(BYTE) EVENPARITY;
					break;
				case 'O':
				case 'o':
					cfg->parity=(BYTE) ODDPARITY;
					break;
				case 'M':
				case 'm':
					cfg->parity=(BYTE) MARKPARITY;
					break;
				case 'S':
				case 's':
					cfg->parity=(BYTE) SPACEPARITY;
					break;
				default:
					printf("Unknown Parity type: %c\n",p);
					usage(argv);
					return 1;
					}
					switch (s) {
				case '1':
					cfg->stopBits=(BYTE) ONESTOPBIT;
					break;
				case '2':
					cfg->stopBits=(BYTE) TWOSTOPBITS;
					break;
				default:
					printf("Unknown Stop Bits: %d\n",s);
					usage(argv);
					return 1;

					}
				} else {
					printf("Unknown parity value: %s\n",argv[cur_arg]);										usage(argv);
					return 1;
				}

			}

		}
		return 0;
}

void print_header(LOGSER_CFG cfg)
{
	char parity = '?';
	char stopbits = '?';
	switch (cfg.parity) {
		case NOPARITY:
			parity = 'N';
			break;
		case EVENPARITY:
			parity = 'E';
			break;
		case ODDPARITY:
			parity = 'O';
			break;
		case MARKPARITY:
			parity = 'M';
			break;
		case SPACEPARITY:
			parity = 'S';
			break;
	}
	switch (cfg.stopBits) {
		case ONESTOPBIT:
			stopbits = '1';
			break;
		case TWOSTOPBITS:
			stopbits = '2';
			break;
	}
	printf("/=======================================\\\n");
	printf("| Serial Logger by Mike Miller          |\n");
	printf("| Easily log and/or display serial data |\n");
	printf("| Display: %c, Save: %c, Overwrite: %c     |\n", cfg.display ? 'Y' : 'N',
		cfg.save ? 'Y' : 'N', cfg.overwrite ? 'Y' : 'N');
	printf("| Bitrate: %-6d  Parity: %d%c%c          |\n",cfg.baudRate, cfg.byteSize, parity,stopbits);
	printf("|=======================================|\n");
	return;
}

void error(HANDLE hCom, const char *fmt, ...)
{
	va_list argp;
	va_start(argp, fmt);
	vprintf(fmt, argp);
	va_end(argp);
	printf("\\=======================================/\n");
	CloseHandle(hCom);
}
