/* Serial logging application */
/* Written by Mike Miller */

#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#include <string.h>

typedef struct _LOGSER_CFG {
    short port;
    bool save;
    bool overwrite;
    bool display;
} 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", hCom);

    /* Configure the serial port */
    memset(&dcb, 0, sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);

    GetCommState(hCom, &dcb);
    dcb.BaudRate = CBR_115200;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    dcb.fBinary = FALSE;
    dcb.fParity = TRUE;

    if (!SetCommState(hCom, &dcb)) {
	error(hCom, "| Unable to set Comm Port config.\t|\n| Error was %d\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 %d\t\t\t|\n", GetLastError());
    }

    /* 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");
    return;
}

int parse_arguments(int argc, _TCHAR * argv[], LOGSER_CFG * cfg)
{
    if (argc != 2 && argc != 3 && argc != 4) {
	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;

    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;
	    }
	    if (0 == strncmp(argv[cur_arg], "-s", 2)) {	/* Save only */
		cfg->save = true;
		cfg->display = false;
	    }
	}
    return 0;
}

void print_header(LOGSER_CFG cfg)
{
    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("|=======================================|\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);
}
