/*
 * sercp - serial copy for transfering file to/from ZX Spectrum 128 AY's RS232
 * Copyright (c) 2018-2019 Pavel Vymetalek <pavel@vym.cz>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version (GPL-3.0-or-later).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
/*
 * IFDEFS inspired here:
 * https://iq.opengenus.org/detect-operating-system-in-c/
 *
 *  vmin, vtime insipired here:
 * 	// http://unixwiz.net/techtips/termios-vmin-vtime.html
 *
 */

#ifdef _WIN32
	#include <stdio.h>
	#include <stdint.h>
	#include <io.h>
	#include <windows.h>
	#include <getopt.h>
	#include <strings.h>
	#include <signal.h>
	#include <fcntl.h>
	#include <errno.h>
	#include <unistd.h>
	#include <libgen.h>
	#include <sys/stat.h>
	#include <error.h>
	#include <time.h>
	#include <sys/time.h>
	#include <utime.h>
#else
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <strings.h>
	#include <errno.h>
	#include <stdint.h>
	#include <libgen.h>
	#include <getopt.h>
	#include <termios.h>
	#include <unistd.h>
	#include <err.h>
	#include <sys/stat.h>
	#include <sys/ioctl.h>
	#include <sys/signal.h>
	#include <signal.h>
	#include <fcntl.h>
	#include <poll.h>
	#include <time.h>
	#include <sys/time.h>
	#include <utime.h>
#endif

#define false 0
#define FALSE 0
#define true 1
#define TRUE 1
const char* _version = "v0.8.1";
// SERIAL
FILE *tapout_fd = NULL;
int is_outfile = 0;
int baud_rate = 0;
int wait_ms = 800;
char *path;
char sercp_file[FILENAME_MAX];
unsigned char buff[32768];
unsigned char *p_buff;
unsigned int is_binary = 0;

float width;		// width of terminal
long pos = 0;
int scp = 0;
int is_scp_read = 0;
int is_continue = 1;
int is_flow_control_hw = 0;			// 1 - set flow control to NONE
int is_old_protocol = 0;			// 1 - set old protocol without AKC messages

// prototypes

void sleep_ms(int milliseconds);

char SERIALDEVICE[128] = {
#ifdef _WIN32
	"\\\\.\\COM1"
#else
	"/dev/ttyUSB0"
#endif
};

#ifdef _WIN32
	static HANDLE serial_fd;
#else
	int serial_fd;
	// struct sigaction saterm;           /* definition of signal action */
	struct pollfd spolfd_serial[1];	  // pole descriptoru pro poll
#endif


typedef struct {
	uint16_t block_len;	// legth of block
	uint8_t  block_xor;	// xor of all bytes in block
	uint8_t  block_sum;	// 8-bit sum of all bytes in block
} __attribute__((packed)) fi_sum;// used as fi_blocks in fileinfo

typedef struct {
	uint16_t length;		// length of data in fileinfo
	uint8_t h_xor;			// xor of all bytes of fileinfo
	uint8_t h_sum;			// sum of all bytes of fileinfo
	uint8_t fi_numblocks;	// number of transfered blocks
	uint8_t fi_name[64];	// filename (8.3 format for now)
	uint8_t fi_reserved[12];// reserved
	uint16_t fi_time[1];	// fat file time
	uint16_t fi_date[1];	// fat file date
	fi_sum fi_blocks[256];	// blocks - see fi_sums above
} __attribute__((packed)) FILEINFO;

FILEINFO fileinfo;

void usage(void) {
	printf ("sercp %s (c)2018-2024 Pavel Vymetalek <pavel@vym.cz>\n", _version);
	printf ("serial copy for transfering file to/from ZX Spectrum 128 AY's RS232\n");
	// printf ("Use 1109-bytes of fileinfo - blocks sums, filename, etc.\n");
	printf ("Confirmation of communication using ack messages - no HW flow control\n");
	printf ("Still compatible with old .sercp - with -l switch\n");
	printf ("More info at https://vym.cz/sercp/\n");
#ifdef _WIN32
	printf ("Usage:\nsercp.exe -d com1 [-t] [-r] <filename>\n");
#else
	printf ("Usage:\nsercp -d /dev/serial [-t] [-r] <filename>\n");
#endif
	printf ("\t-v, --version\tShow version info\n");
	printf ("\t-h, --help\tShow this text\n");
#ifdef _WIN32
	printf ("\t-d, --device\tSerial com port\n");
#else
	printf ("\t-d, --device\tSerial communication device. Default /dev/ttyUSB0\n");
#endif
	printf ("\t-b, --baud\tSet the communication baud rate. Default 38400Bd\n");
	printf ("\t-t, --turbo\tSet the baud rate 115200Bd\n");
	printf ("\t-l, --hwflow\tSet the flow control to CTS/RTS. Default is NONE.\n\t\t\tThis is useful for old .sercp with HW flow control\n");
	printf ("\t-o, --oldprot\tUse the old protocol without AKC messages.\n\t\t\tThis is useful for old .sercp with missing HW flow control\n");
	printf ("\t-w, --wait\tWaiting in milliseconds between transmitted blocks.\n\t\t\tIn case of improperly functioning hw flow control\n");
	printf ("\t-r, --read\tRead file from serial port\n");
}

// fileinfo 1109bytes
/************************************************************************/
static struct option long_options[] = {
	{"device", required_argument, NULL, 'd'},
	{"baud", required_argument, NULL, 'b'},
	{"wait", required_argument, NULL, 'w'},
	{"hwflow", no_argument, NULL, 'l'},
	{"oldprot", no_argument, NULL, 'o'},
	{"read", no_argument, NULL, 'r'},
	{"turbo", no_argument, NULL, 't'},
	{"version", no_argument, NULL, 'v'},
	{"help", no_argument, NULL, 'h'},
	{0, 0, 0, 0}
};

void TestArgs (int argc, char *argv[])
{
	int c;
	while (1) {
		int option_index = 0;
		c = getopt_long (argc, argv, "d:b:w:lortvh", long_options, &option_index);
		if (c == -1) {
			// end of arguments
			break;
		}
		switch (c) {
			case 'd':
#ifdef _WIN32
				sprintf(SERIALDEVICE, "\\\\.\\%s", optarg);
#else
				sprintf(SERIALDEVICE, "%s", optarg);
#endif
				break;
			case 'b':
				baud_rate = atoi(optarg);
				break;
			case 't':
				baud_rate = 115200;
				break;
			case 'w':
				wait_ms = atoi(optarg);
				break;
			case 'r':
				is_scp_read = 1;
				break;
			case 'l':
				is_flow_control_hw = 1;
				break;
			case 'o':
				is_old_protocol = 1;
				break;
			case 'v':
				printf ("%s\n", _version);
				exit(1);
				break;
			case 'h':
				usage();
				exit(1);
				break;
			default:
				break;
		}
	}
	if (optind < argc) {
		strncpy (&sercp_file[0], argv[argc-1], FILENAME_MAX);		// input file name or path - without option switch
		sercp_file[FILENAME_MAX-1] = '\0';
	}
}

#define CFG_NAME ".sercprc"

void ParseCfg() {
	char *home, *val;
	char cfg_path[256];
	char opt[1024];
	int optidx;
	FILE *fp;

#ifdef _WIN32
	home = getenv( "USERPROFILE" );
	if (!home) return;
	snprintf(cfg_path, sizeof(cfg_path), "%s\\%s", home, CFG_NAME);
#else
	home = getenv( "HOME" );
	if (!home) return;
	snprintf(cfg_path, sizeof(cfg_path), "%s/%s", home, CFG_NAME);
#endif
	if (!(fp = fopen(cfg_path, "rb"))) return;
	
	fgets(opt, sizeof(opt), fp);
	while (!feof(fp)) {
		if ((val=strchr(opt, '\r'))) *val = '\0';
		if ((val=strchr(opt, '\n'))) *val = '\0';
		
		val = strchr(opt, ' ');
		if (val) *val++ = '\0';
		
		for (optidx = 0; long_options[optidx].name; optidx++) {
			if (!strcmp(opt, long_options[optidx].name)) {
				switch (long_options[optidx].val) {
					case 'd':
						if (val) {
#ifdef _WIN32
							sprintf(SERIALDEVICE, "\\\\.\\%s", val);
#else
							sprintf(SERIALDEVICE, "%s", val);
#endif
						}
						break;
					case 'b':
						if (val) baud_rate = atoi(val);
						break;
					case 't':
						baud_rate = 115200;
						break;
					case 'w':
						if (val) wait_ms = atoi(val);
						break;
					case 'l':
						is_flow_control_hw = 1;
						break;
					case 'o':
						is_old_protocol = 1;
						break;
					default:
						break;
				}
			}
		}
		fgets(opt, sizeof(opt), fp);
	}
	fclose(fp);
}


//*****************************************************************************
// TODO read bytes from serial port for 200ms

void FlushSerialPort() {
	ssize_t len = 0;
	if (serial_fd) {
		sleep_ms(20);
#ifdef _WIN32
		unsigned long ulNumBytes = 0;
		PurgeComm(serial_fd, PURGE_RXABORT| PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR);
		do {
			sleep_ms(100);
			ReadFile(serial_fd, p_buff, 100, &ulNumBytes, NULL);
			len = (size_t) ulNumBytes;
		} while (len > 1);
#else
		tcflush(serial_fd, TCIOFLUSH);
		do {
			sleep_ms(100);
			len = read (serial_fd, p_buff, 100);
		} while (len > 1);
#endif
	}
}

//*****************************************************************************

void CloseSerialPort() {
	if (serial_fd) {
		#ifdef _WIN32
		PurgeComm(serial_fd, PURGE_RXABORT| PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR);
		CloseHandle(serial_fd);
		#else
		tcflush(serial_fd, TCIOFLUSH);
		close (serial_fd);
		#endif
	}
}


//*****************************************************************************
//

void sleep_ms(int milliseconds) {
#ifdef WIN32
	Sleep(milliseconds);
#else
	usleep(milliseconds * 1000);
#endif
}

/************************************************************************/
int GetTerminalWidth(void) {
#ifdef _WIN32
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	int columns;
// 	int rows;

	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
	columns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
// 	rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
	return (columns);
#else
	struct winsize termsize;
	ioctl (STDOUT_FILENO, TIOCGWINSZ, &termsize);
	return (termsize.ws_col);
#endif
}

#define PROGRESS_PERCENT	0
#define PROGRESS_COMPLETTE	1
#define PROGRESS_ERROR_CSUM	2

/************************************************************************/
void DoProgress(size_t pos, size_t max, unsigned char csum_ok) {
	width = GetTerminalWidth();
	float percent, p, m;
	int imax = width - 40;
	int ipercent;
	int px;
	char progress_char;
	p = pos;
	m = max;
	percent = (100 / m * p) + 0.5;
	ipercent = percent / 100 * imax;
	if (is_binary) progress_char = '#';
	else  progress_char = '=';
	printf ("Proceed bytes: %6d/%6d [", (int) pos, (int)max);
	for (px = 0; px < imax; px++) {
		if (px < ipercent) printf ("%c", progress_char);
		else printf (" ");
	}
	if (csum_ok == 0) {
		printf ("] %3d %%\r", (int)percent);
	} else if (csum_ok == 1) {
		printf ("]  OK  \r");
	} else {
		printf ("]  ERR \r");
	}
	fflush (stdout);
}

/************************************************************************/

int CheckFileInfo(FILEINFO* p_fi) {
	unsigned char *p_fiinfo = (unsigned char*)p_fi;
	uint16_t fi_len;
	uint8_t fi_xor = 0;
	uint8_t fi_sum = 0;
	fi_len = p_fi->length;
	p_fiinfo += 4;			// sum is counting from offset 4
	for (uint16_t indx = 0; indx < (fi_len-4); indx++) {
		fi_xor ^= *p_fiinfo;
		fi_sum += *p_fiinfo;
		p_fiinfo++;
	}
	if (fi_xor == p_fi->h_xor && fi_sum == p_fi->h_sum) {
		return 0;
	}
	return -1;
}

/************************************************************************/
void CountFileInfoChecksum(FILEINFO* p_fi) {
	unsigned char *p_fiinfo = (unsigned char*)p_fi;
	uint16_t fi_len;
	uint8_t fi_xor = 0;
	uint8_t fi_sum = 0;
	fi_len = p_fi->length;
	p_fiinfo += 4;			// sum is counting from offset 4
	for (uint16_t indx = 0; indx < (fi_len-4); indx++) {
		fi_xor ^= *p_fiinfo;
		fi_sum += *p_fiinfo;
		p_fiinfo++;
	}
	p_fi->h_xor = fi_xor;
	p_fi->h_sum = fi_sum;
}

/************************************************************************/
int CheckSumBlock(FILEINFO* p_fi, uint8_t block_indx, uint8_t *p_buffer) {
	uint16_t block_len;
	uint8_t b_xor = 0;
	uint8_t b_sum = 0;
	uint8_t block_xor;
	uint8_t block_sum;
	block_len = p_fi->fi_blocks[block_indx].block_len;
	block_sum = p_fi->fi_blocks[block_indx].block_sum;
	block_xor = p_fi->fi_blocks[block_indx].block_xor;
	for (uint16_t indx = 0; indx < block_len; indx++) {
		b_xor ^= *p_buffer;
		b_sum += *p_buffer;
		p_buffer++;
	}
	if (b_xor == block_xor && b_sum == block_sum) {
		return 0;	// success
	}
	return -1;		// sum error
}

/************************************************************************/
void CountSumBlock(FILEINFO* p_fi, uint8_t block_indx, uint8_t *p_buffer, uint16_t block_len) {
	uint8_t block_xor = 0;
	uint8_t block_sum = 0;
	for (uint16_t indx = 0; indx < block_len; indx++) {
		block_xor ^= *p_buffer;
		block_sum += *p_buffer;
		p_buffer++;
	}
	p_fi->fi_blocks[block_indx].block_len = block_len;
	p_fi->fi_blocks[block_indx].block_sum = block_sum;
	p_fi->fi_blocks[block_indx].block_xor = block_xor;
}


/************************************************************************/
uint32_t GetOverallLen(FILEINFO *p_fi) {
	uint32_t overall_len = 0;
	uint8_t block_num = p_fi->fi_numblocks;
	for (uint8_t indx = 0; indx < block_num; indx++) {
		overall_len += p_fi->fi_blocks[indx].block_len;
	}
	return overall_len;
}

void sendAckFileinfo(void) {
uint8_t fi_ack_buffer[] = {'A','c','k',0, 0x49, 0x0F};
	if (is_old_protocol) return;
#ifdef _WIN32
	unsigned long ulNumBytes;
	WriteFile(serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer), &ulNumBytes, NULL);
#else
	write (serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer));
	if (tcdrain(serial_fd) == -1) {
		perror("tcdrain err1: ");
		return;
	}
#endif
}

void sendAckBlock(uint8_t block_num) {
	uint8_t fi_ack_buffer[] = {'A','C','K',0, 0x49, 0xCF};
	int x;
	uint8_t s_xor = 0;
	uint8_t s_sum = 0;
	if (is_old_protocol) return;

	fi_ack_buffer[3] = block_num;
	// printf("\n Ack sent: ");
	for (x=0; x<(sizeof(fi_ack_buffer)-2);x++) {
		s_xor ^= fi_ack_buffer[x];
		s_sum += fi_ack_buffer[x];
		fi_ack_buffer[4] = s_xor;
		fi_ack_buffer[5] = s_sum;
		// printf("%2X ",fi_ack_buffer[x]);
	}
		// printf("%2X ",fi_ack_buffer[4]);
		// printf("%2X \n",fi_ack_buffer[5]);
#ifdef _WIN32
	unsigned long ulNumBytes;
	WriteFile(serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer), &ulNumBytes, NULL);
#else
	write (serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer));
	if (tcdrain(serial_fd) == -1) {
		perror("tcdrain err1: ");
		return;
	}
#endif
}

uint8_t buf_ack[8];

uint8_t WaitReadAck(void) {
	size_t len = 0;
	#ifdef _WIN32
	unsigned long ulNumBytes =0;
	while (1) {
		sleep_ms(10);
		ReadFile(serial_fd, &buf_ack[len], sizeof(buf_ack)-len, &ulNumBytes, NULL);
		len += (size_t) ulNumBytes;
		if (len == 6) break;
	}
#else
	// int x;
	int result;
	memset(buf_ack, 0, 8);
	while (1) {
		result = poll (spolfd_serial, 1, 300);		// 200ms timeout
		if (result == 0) {
			// nothing is comming
			continue;
		} else if (result == -1) {
			FlushSerialPort();
			CloseSerialPort();
			exit(EXIT_FAILURE);
		} else if (result) {
			len += read (serial_fd, &buf_ack[len], sizeof(buf_ack)-len);
			if (len == 6) {
				break;
			}
		}
	}
#endif
	return (len);
}
// receive fileinfo ACK
int CheckAckSum() {
	int x;
	uint8_t xorsum = 0;
	uint8_t sumsum = 0;
	for (x = 0; x < 4; x++) {
		sumsum += buf_ack[x];
		xorsum ^= buf_ack[x];
	}
	if ((xorsum == buf_ack[4]) && (sumsum == buf_ack[5])) {
		return 0;
	} else {
		return 1;
	}
}

void RecvAckFileinfo(void) {
	if (is_old_protocol) return;
	if (WaitReadAck() == 6) {
		if (CheckAckSum()) {
			printf("\nErr Ack fileinfo\n");
			for (int x=0; x<6; x++) {
				printf("%2X ",buf_ack[x]);
			}
			printf ("\n");
			exit(-1);
		};
	};
	sleep_ms(100);		// 100ms to wait speccy
}

void RecvAckBlock(uint8_t block_number) {
	if (is_old_protocol) return;
	if (WaitReadAck() == 6) {
		if (CheckAckSum() || (buf_ack[3] != block_number)) {
			printf("\nErr ACK block\n");
			for (int x=0; x<6; x++) {
				printf("%2X ",buf_ack[x]);
			}
			printf ("\n");
			exit(-1);
		}
	}
	sleep_ms(100);		// 100ms to wait speccy
}

/************************************************************************/
void sercpRecv(void) {
	int recv_phase = 0;			// 0 - fileinfo, 1 - 16kiB block, 2 - last block
	uint8_t block_index = 0;
	size_t len = 0;
	uint16_t length = 0;
	uint16_t expected_len = 0;
	uint32_t overall_length = 0;		// overall length of file
	uint32_t recv_length = 0;			// receive length counter
	struct utimbuf f_datetime;
	struct tm timdat;
#ifndef _WIN32
	int result;
#endif
	FILEINFO *p_fileinfo = &fileinfo;
	unsigned char *p_buff = buff;
	tapout_fd = NULL;
#ifdef _WIN32
	unsigned long ulNumBytes;
#endif

	memset (p_fileinfo, 0, sizeof(fileinfo));
	while (is_continue) {
#ifndef _WIN32
		result = poll (spolfd_serial, 1, 200);		// 200ms timeout
		if (result == 0) {
			// nothing is comming
			continue;
		}
#endif
		switch (recv_phase) {
			case 0:
				// receive fileinfo
#ifdef _WIN32
					sleep_ms(10);
					ReadFile(serial_fd, p_buff, sizeof(fileinfo)-length, &ulNumBytes, NULL);
					len = (size_t) ulNumBytes;
#else
					sleep_ms(10);
					len = read (serial_fd, p_buff, sizeof(fileinfo)-length);
#endif
					p_buff += len;
					length += len;
					if (length == sizeof(fileinfo)) {
						memcpy((unsigned char*) p_fileinfo, buff, sizeof(fileinfo));
						if (CheckFileInfo(p_fileinfo) == 0) {
							overall_length = GetOverallLen(p_fileinfo);
							printf("File: \"%s\" number of blocks:%d, length of file: %u\n", p_fileinfo->fi_name, p_fileinfo->fi_numblocks, overall_length);
							recv_phase++;			// will follow receive data blocks
							block_index = 0;		// begins from first block
							p_buff = buff;			// initialize buffer pointer
							length = 0;
							expected_len = p_fileinfo->fi_blocks[block_index].block_len;
							sleep_ms(100);
							sendAckFileinfo();
							tapout_fd = fopen ((char*)p_fileinfo->fi_name, "wb");
							if (tapout_fd == NULL) {
								#ifdef _WIN32
								printf ("can't open output file");
								exit (EXIT_FAILURE);

								#else
								err (1, "can't open output file");
								#endif
							}
						} else {
							printf("Fileinfo corrupted. End...\n");
							FlushSerialPort();
							CloseSerialPort();
							exit(EXIT_FAILURE);
							break;
						}
						break;
					} else if (length > sizeof(fileinfo)){
#ifdef _WIN32
						ReadFile(serial_fd, p_buff,  sizeof(fileinfo), &ulNumBytes, NULL);
						len = (uint16_t)ulNumBytes;
#else
						len = read (serial_fd, p_buff, sizeof(fileinfo));
#endif
						printf("Received unknown data. End...\n");
						FlushSerialPort();
						exit (EXIT_FAILURE);
						break;
					}
				break;
			case 1:
				// receive of data block - max. length 16kiB
#ifdef _WIN32
					ReadFile(serial_fd, p_buff, expected_len, &ulNumBytes, NULL);
					len = (size_t)ulNumBytes;
#else
					len = read (serial_fd, p_buff, expected_len);
#endif
					sleep_ms(10);
					p_buff += len;
					expected_len -= len;
					length += len;
					recv_length += len;
					DoProgress(recv_length, overall_length, PROGRESS_PERCENT);
					if (length == p_fileinfo->fi_blocks[block_index].block_len) {
						// prijaty prvni block
						if (CheckSumBlock(p_fileinfo, block_index, buff) == 0) {
							// block is ok - write to file
							if (tapout_fd) fwrite(buff, length, 1, tapout_fd);
							// TODO
							sleep_ms(100);
							sendAckBlock(block_index);
						}
						length = 0;
						p_buff = buff;
						block_index++;
						expected_len = p_fileinfo->fi_blocks[block_index].block_len;
					}
					if (expected_len == 0 || block_index == 255) {
						printf ("\nTransfer successful\n");
						is_continue = 0;
						recv_phase++;
						break;
					}
					break;
			}
	}
	if (tapout_fd) {
		fflush (tapout_fd);
		fclose (tapout_fd);
		// set date and time which is received
		memset(&timdat, 0, sizeof(struct tm));
		if (*p_fileinfo->fi_date != 0) {
			timdat.tm_year = (((*p_fileinfo->fi_date & 0xFE00) >> 9) + 1980)-1900;
			timdat.tm_mon = ((*p_fileinfo->fi_date & 0x1E0) >> 5) - 1;
			timdat.tm_mday = *p_fileinfo->fi_date & 0x1F;
			timdat.tm_hour = ((*p_fileinfo->fi_time ) >> 11);
			timdat.tm_min = ((*p_fileinfo->fi_time & 0x7e0) >> 5);
			timdat.tm_sec = (*p_fileinfo->fi_time & 0x1F)*2;
			timdat.tm_isdst = -1;
			f_datetime.modtime = mktime(&timdat);
			f_datetime.actime = f_datetime.modtime;
			utime ((char*)p_fileinfo->fi_name, &f_datetime);
		}
	}
}


/************************************************************************/
void sercpSend(void) {
	FILE *tap_fd;
	char *p_bname, *p_basec;
	char *p_shortfilename;
	size_t fn_len;
	unsigned int  no, len;
	struct stat st;
	FILEINFO *p_fileinfo = &fileinfo;
	unsigned char *p_buff = buff;
	uint32_t file_len;
	uint8_t num_blocks = 0;
	ssize_t odeslano;
	uint32_t len_sent;
	uint16_t send_size;
	uint32_t overall_sent;
	uint8_t blck_num = 0;
#ifdef _WIN32
	unsigned long ulNumBytes;
#endif
	struct tm *timdat;

	no = stat(sercp_file, &st);
	if (no == -1) {
		printf ("can't stat input file\n");
		exit (EXIT_FAILURE);
	}
	if (st.st_size == 0) {
		printf ("Zero length of file. End\n");
		exit (EXIT_FAILURE);
	}

	tap_fd = fopen(sercp_file, "rb");
	if (tap_fd == NULL)	{
		printf ("can't open input file\n");
		exit (EXIT_FAILURE);
	}

// FIXME do this more sophisticated and crossplatform, for example by __SIZE_WIDTH__
#if defined (__linux__) || defined(_WIN32) || defined (BSD)
	printf ("File %s, length: %ld\n", sercp_file, st.st_size);
#else
	printf ("File %s, length: %lld\n", sercp_file, st.st_size);
#endif

	timdat = localtime(&st.st_mtime);

	uint16_t FatDate = ((timdat->tm_year - 80) << 9) | ((timdat->tm_mon + 1) << 5) | timdat->tm_mday;
	uint16_t FatTime = ((timdat->tm_hour << 11) | (timdat->tm_min << 5) | (timdat->tm_sec >> 1));

	memset (p_fileinfo, 0, sizeof(fileinfo));		// erase fileinfo

	p_basec = strdup(sercp_file);
	p_bname = basename (p_basec);
	if (strlen(p_bname) > 12) {
		printf ("Short filename: ");
		p_shortfilename = strdup(p_bname);
		fn_len = strlen(p_shortfilename);
		memcpy(p_shortfilename+4, p_shortfilename+fn_len-8, 8);
		*(p_shortfilename+12) = 0;
		printf ("%s\n", p_shortfilename);
		p_bname = p_shortfilename;
	}

	memcpy(p_fileinfo->fi_name, p_bname, strlen(p_bname));
	memcpy(p_fileinfo->fi_date, &FatDate, sizeof(FatDate));
	memcpy(p_fileinfo->fi_time, &FatTime, sizeof(FatTime));
	file_len = (uint32_t) st.st_size;
	while (file_len) {
		len = fread(buff, 1, 16384, tap_fd);			// read 16kiB from file
		CountSumBlock(p_fileinfo, num_blocks, buff, len);
		file_len -= len;
		num_blocks++;
	}
	p_fileinfo->fi_numblocks = num_blocks;
	p_fileinfo->length = num_blocks*4 + 85;
	CountFileInfoChecksum(p_fileinfo);
#ifdef _WIN32
	WriteFile(serial_fd, (void*)p_fileinfo, sizeof(fileinfo), &ulNumBytes, NULL);
#else
	odeslano = write (serial_fd, (void*)p_fileinfo, sizeof(fileinfo));
	if (tcdrain(serial_fd) == -1) {
		perror("tcdrain err1: ");
		return;
	}
#endif
	printf("Fileinfo sent with filename: %s\n", p_bname);
	if (!is_flow_control_hw && !is_old_protocol) {
		RecvAckFileinfo();
	} else {
		sleep_ms(wait_ms);
	}
	rewind(tap_fd);
	file_len = (uint32_t) st.st_size;
	overall_sent = 0;
	while (file_len) {
		len = fread(buff, 1, 16384, tap_fd);			// read 16kiB from file
		p_buff = buff;
		if (len > 256) {
			send_size = 256;
		} else {
			send_size = len;
		}
		len_sent = len;
		while (len_sent) {
#ifdef _WIN32
			WriteFile(serial_fd, (void*)p_buff, send_size, &ulNumBytes, NULL);
			odeslano = (size_t)ulNumBytes;
#else
			odeslano = write (serial_fd, (void*)p_buff, send_size);
			if (tcdrain(serial_fd) == -1) {
				perror("tcdrain err2: ");
				return;
			}
#endif
			p_buff += odeslano;
			overall_sent += odeslano;
			len_sent -= odeslano;
			if (len_sent < 256) {
				send_size = len_sent;
			}
			DoProgress(overall_sent, st.st_size, PROGRESS_PERCENT);
		}
		file_len -= len;
		if (!is_flow_control_hw && !is_old_protocol) {
			RecvAckBlock(blck_num);
		} else {
			if (file_len > 0) {
				sleep_ms(wait_ms);
			}
		}
		blck_num++;
	}
	printf("\nFile sent...\n");
#ifdef __APPLE__
	// FIXME on __APPLE__ this waiting helps
	sleep_ms(wait_ms);
#endif
	fclose(tap_fd);
}

int OpenUart() {
#ifdef _WIN32
	DCB sDCB;
	COMMTIMEOUTS sCommTimeouts;

	serial_fd = CreateFile(SERIALDEVICE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (serial_fd == INVALID_HANDLE_VALUE) {
		return (-1);
	}
	if (GetCommState(serial_fd, &sDCB) == 0) {
		return (-1);
	}
	if (baud_rate == 0) baud_rate = 38400;
	sDCB.BaudRate = baud_rate;
	sDCB.ByteSize = 8;
	sDCB.Parity = NOPARITY;
	sDCB.fBinary = TRUE;
	sDCB.StopBits = ONESTOPBIT;
	sDCB.fAbortOnError = TRUE;
	sDCB.fOutxDsrFlow = FALSE;
	sDCB.fDtrControl = DTR_CONTROL_DISABLE;
	if (is_flow_control_hw) {
		sDCB.fRtsControl = RTS_CONTROL_HANDSHAKE;
		sDCB.fOutxCtsFlow = TRUE;
	} else {
		sDCB.fRtsControl = RTS_CONTROL_DISABLE;
		sDCB.fOutxCtsFlow = FALSE;
	}
	if (SetCommState(serial_fd, &sDCB) == 0) {
		return (-1);
	}

	if (GetCommTimeouts(serial_fd, &sCommTimeouts) == 0) {
		return (-1);
	}
	sCommTimeouts.ReadIntervalTimeout = MAXDWORD;	//80
	sCommTimeouts.ReadTotalTimeoutConstant = 00;
	sCommTimeouts.ReadTotalTimeoutMultiplier = 00;
	sCommTimeouts.WriteTotalTimeoutConstant = 80;
	sCommTimeouts.WriteTotalTimeoutMultiplier = 80;

	if (SetCommTimeouts(serial_fd, &sCommTimeouts) == 0) {
		return (-1);
	}
	return (0);
#else
	struct termios oldtio, newtio;
	/* open the device to be non-blocking (read will return immediatly) */
	serial_fd = open(SERIALDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);	 	//	| O_NDELAY
	if (serial_fd < 0) {
		perror(SERIALDEVICE);
		return(-1);
	}
#ifdef __APPLE__
	// Note that open() follows POSIX semantics: multiple open() calls to the same file will succeed
	// unless the TIOCEXCL ioctl is issued. This will prevent additional opens except by root-owned
	// processes.
	// See tty(4) <x-man-page//4/tty> and ioctl(2) <x-man-page//2/ioctl> for details.
	if (ioctl(serial_fd, TIOCEXCL) == -1) {
		printf("Error setting TIOCEXCL on %s - %s(%d).\n", SERIALDEVICE, strerror(errno), errno);
		return (-1);
	}

	// Now that the device is open, clear the O_NONBLOCK flag so subsequent I/O will block.
	// See fcntl(2) <x-man-page//2/fcntl> for details.
	if (fcntl(serial_fd, F_SETFL, 0) == -1) {
		printf("Error clearing O_NONBLOCK %s - %s(%d).\n", SERIALDEVICE, strerror(errno), errno);
		return (-1);
	}


#endif
	tcgetattr(serial_fd, &oldtio); /* save current port settings */
	bzero(&newtio, sizeof (newtio));
	cfmakeraw(&newtio);
	switch (baud_rate){
		case 115200:
			newtio.c_cflag = B115200;
			break;
		case 57600:
			newtio.c_cflag = B57600;
			break;
		case 38400:
			newtio.c_cflag = B38400;
			break;
		case 19200:
			newtio.c_cflag = B19200;
			break;
		case 9600:
			newtio.c_cflag = B9600;
			break;
		case 4800:
			newtio.c_cflag = B4800;
			break;
		case 2400:
			newtio.c_cflag = B2400;
			break;
		case 1200:
			newtio.c_cflag = B1200;
			break;
		default:
			baud_rate = 38400;
      newtio.c_cflag = B38400;
      break;
	}
	cfsetospeed(&newtio, baud_rate);
	cfsetispeed(&newtio, baud_rate);		// set ispeed same as ospeed (see POSIX)
#if defined (__APPLE__) || defined (BSD)
	if (is_flow_control_none) {
		printf("Sorry, flow control is not tested on this platform\n");
		newtio.c_cflag |= CS8 | CLOCAL | CREAD | HUPCL;
	} else {
		newtio.c_cflag |= CS8 | CLOCAL | CREAD | CCTS_OFLOW | CRTS_IFLOW | HUPCL;
	}
#else
	if (is_flow_control_hw) {
		// standard CTS/RTS flow control
		newtio.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS;
	} else {
		// usefull for eLeMeNt ZX and machines or serial port without CTS/RTS wires
		newtio.c_cflag |= CS8 | CLOCAL | CREAD;
	}
#endif
	newtio.c_iflag &= ~(IXON | IXOFF | IXANY);
	newtio.c_oflag &= ~(OPOST);
	newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);		// raw input
	printf ("Serial device: %s, communication speed is: %d Bd, flow control: %s\n", SERIALDEVICE, baud_rate, (is_flow_control_hw)?"CTS/RTS":"None");

	// http://unixwiz.net/techtips/termios-vmin-vtime.html
	newtio.c_cc[VMIN] = 1;	// minimum number to read
	newtio.c_cc[VTIME] = 1; // time to wait
	tcsetattr(serial_fd, TCSANOW, &newtio);
	sleep_ms(20);
	return 0;
#endif
}

/************************************************************************/
/************************************************************************/
int main(int argc, char** argv, char** env)
{
	width = GetTerminalWidth();
	ParseCfg();
	if (argc < 2) {
		// printf("You must specify the Serial device and file\n");
		usage();
		exit(1);
	}
	TestArgs (argc, argv);

	if (OpenUart() == -1) {
		printf ("Can't open serial port\n");
		exit (EXIT_FAILURE);
	}
	FlushSerialPort();						// make the input fifo empty
#ifndef _WIN32
	spolfd_serial[0].fd = serial_fd;		// watching descriptor
	spolfd_serial[0].events = POLLIN;		// watch data on input
#endif
	if (is_scp_read) {
		sercpRecv();
	} else {
		sercpSend();
	}
	CloseSerialPort();
	return 0;
}