#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>
#include <../source/PAL/oshead.h>
#include <../source/vfs/iobase.h>

#ifdef		__WIN32__

typedef struct	comm_port_s
{
	ioport_base_t	vtbl;
	char		port_name[_MAX_PATH];
	HANDLE		port;
	HANDLE		cancel_io;
}	comm_port_t, *comm_port_p;

/*
 *	COMM port methods - Win32
 */

V_LOCAL	vsize_t	V_CALL
io_comm_read(void *object,void *buf,vsize_t len)
{
	DWORD rd=0;
	u8_t *ptr=(u8_t*)buf;
	comm_port_t *self=(comm_port_t*)object;
	for(;;)
	{
		if(WaitForSingleObject(self->cancel_io,0)==WAIT_OBJECT_0)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"commRead(): input canceled (code %d).\n",
				GetLastError());
#endif		/* CHECKED_BUILD */
			ResetEvent(self->cancel_io);
			return 0;
		}
		if(!ReadFile(self->port,ptr,len,&rd,NULL))
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"commRead(): ReadFile() failed (code %d).\n",
				GetLastError());
#endif		/* CHECKED_BUILD */
			return 0;
		}
		len-=rd;
		ptr+=rd;
		if(len)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_TRACE,
				"commRead(): timeout reached; trying again ...\n");
#endif		/* CHECKED_BUILD */
			/*	Sleep some time to give application ability to cancel IO
			 *	operation from another thread; than try again. */
			Sleep(10);
		}
		else
			break;
	}
	return (vsize_t)(ptr-(u8_t*)buf);
}

V_LOCAL vsize_t	V_CALL
io_comm_write(void *object,void *buf,vsize_t len)
{
	DWORD wrote;
	WriteFile(((comm_port_t*)object)->port,
		buf,len,&wrote,NULL);
	return (vsize_t)wrote;
}

V_LOCAL	void
io_comm_reset(comm_port_t *self)
{
	SetEvent(self->cancel_io);
	PurgeComm(self->port,
		PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
}

V_LOCAL	u32_t V_CALL
io_comm_destroy(vhandle_t handle)
{
	comm_port_p self=(comm_port_p)handle;
	UNREF_EXIT(self);
	if(self->port!=INVALID_HANDLE_VALUE)
	{
		if(self->cancel_io!=INVALID_HANDLE_VALUE)
		{
			io_comm_reset(self);
			CloseHandle(self->cancel_io);
		}
		CloseHandle(self->port);
	}
	memFreeObject(self);
	return 0;
}

/*
 *	init_comm_port()
 */
V_LOCAL	vboolean
init_comm_port(comm_port_t *self,int port_num,int rate,int bytesize,
	int parity,int stopbits)
{
	DCB		dcb;
	COMMTIMEOUTS	timeouts;
	sprintf(self->port_name,"COM%1d",port_num);
	self->port=CreateFile(self->port_name,
		GENERIC_READ|GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if(!self->port)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"commOpen(%d,%d,%d,%d,%d): CreateFile() failed (code %d)\n",
			port_num,rate,bytesize,parity,stopbits,GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	ZeroMemory(&timeouts,sizeof(COMMTIMEOUTS));
	/* Minimal baud rate is 110 baud: 73 ms interval between bytes */
	timeouts.ReadTotalTimeoutMultiplier=73;
	if(!SetCommTimeouts(self->port,&timeouts))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"commOpen(%d,%d,%d,%d,%d): SetCommTimeouts() failed "
			"(code %d)\n",port_num,rate,bytesize,parity,stopbits,
			GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	ZeroMemory(&dcb,sizeof(DCB));
	dcb.DCBlength=sizeof(DCB);
	if(!GetCommState(self->port,&dcb))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"commOpen(%d,%d,%d,%d,%d): GetCommState() failed (code %d)\n",
			port_num,rate,bytesize,parity,stopbits,GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	dcb.BaudRate=rate;
	dcb.fBinary=TRUE;
	dcb.fParity=parity ? TRUE : FALSE;
	dcb.fOutxCtsFlow=FALSE;
	dcb.fOutxDsrFlow=FALSE;
	dcb.fDtrControl=DTR_CONTROL_DISABLE;
	dcb.fDsrSensitivity=FALSE;
	dcb.fOutX=FALSE;
	dcb.fInX=FALSE;
	dcb.fNull=FALSE;
	dcb.fRtsControl=RTS_CONTROL_DISABLE;
	/* 07.07.00: can be set to FALSE prevent error 995 (io operation
	 * canceled)
	 */
	dcb.fAbortOnError=TRUE;
	dcb.ByteSize=(unsigned char)bytesize;
	dcb.Parity=(unsigned char)(parity ? EVENPARITY : NOPARITY);
	dcb.StopBits=(unsigned char)stopbits;
	if(!SetCommState(self->port,&dcb))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"commOpen(%d,%d,%d,%d,%d): SetCommState() failed (code %d)\n",
			port_num,rate,bytesize,parity,stopbits,GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	PurgeComm(self->port,
		PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
	self->cancel_io=CreateEvent(NULL,TRUE,FALSE,NULL);
	return true;
}

#elif	defined(__UNIX__)

#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>
#ifdef	__linux
#include <linux/serial.h>
#endif	/* __linux */

typedef struct	comm_port_s
{
	ioport_base_t	vtbl;
    int 			fd;
	struct termios	old_settings;
}	comm_port_t, *comm_port_p;

V_LOCAL	vsize_t	V_CALL
io_comm_read(void *object,void *buf,vsize_t len)
{
	return read(((comm_port_p)object)->fd,buf,len);
}

V_LOCAL vsize_t	V_CALL
io_comm_write(void *object,void *buf,vsize_t len)
{
	return write(((comm_port_p)object)->fd,buf,len);
}

V_LOCAL	void
io_comm_reset(comm_port_t *self)
{
	tcflush(self->fd,TCIFLUSH | TCOFLUSH);
	return;
}

V_LOCAL	u32_t V_CALL
io_comm_destroy(vhandle_t handle)
{
	int fd;
	comm_port_p self=(comm_port_p)handle;
    if(--self->vtbl.base.ref_count)
    	return self->vtbl.base.ref_count;
	fd=self->fd;
	if(fd!=-1)
	{
		tcflush(fd,TCIFLUSH | TCOFLUSH);
		tcsetattr(fd,TCSANOW,&self->old_settings);
		close(fd);
	}
	memFreeObject(handle);
	return 0;
}

/*
 *	init_comm_port()
 *	See linux Serial-programming-HOWTO and man pages for tcXXXX
 *	functions for details.
 */
V_LOCAL	vboolean
init_comm_port(comm_port_t *self,int port_num,int rate,int bytesize,
	int parity,int stopbits)
{
#ifdef	CHECKED_BUILD
	static char caller[] = "commOpen";
#endif	/* CHECKED_BUILD */
	char pname[_MAX_PATH]; /* name of device file */
	int near_rate, baud_rate; /* nearest flow rate and bitmask */
	struct termios tio; /* port settings */
    sprintf(pname,"/dev/ttyS%d",port_num-1);
	/* Open serial port */
	self->fd=open(pname,O_RDWR | O_NOCTTY | O_SYNC);
    if(self->fd == -1)
	{
#ifdef	CHECKED_BUILD
		char buf[256];
#ifdef	CONFIG_HAVE_STERROR_R
		strerror_r(errno,buf,256);
#else	/* !CONFIG_HAVE_STERROR_R */
		strCopy(buf,strerror(errno),256);
#endif	/* !CONFIG_HAVE_STERROR_R */
		conlPrintf(LOG_ERROR,
			"%s('%s'): open() failed: %s\n",
			caller, pname, buf);
		if(errno==EACCES)
		{
			conlPrintf(LOG_ERROR,
				"%s(): as root, type 'chmod 0666 %s'\n",
				caller, pname);
		}
#endif	/* CHECKED_BUILD */
    	return false;
	}
	if(tcflush(self->fd,TCIFLUSH | TCOFLUSH) == -1)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"%s('%s'): unable to flush port.\n",
			caller,pname);
#endif	/* CHECKED_BUILD */
	}
	/* Save current port state */
	tcgetattr(self->fd,&self->old_settings);
	memCopy(&tio,&self->old_settings,sizeof(tio));
	/* Ignore modem status lines state */
	tio.c_cflag |= CLOCAL;
	/* Don't discard input */
	tio.c_cflag |= CREAD;
	/* Set number of stop bits */
	if(stopbits)
		tio.c_cflag |= CSTOPB;
	else
		tio.c_cflag &= ~CSTOPB;
	/* Set byte size */
	tio.c_cflag &= ~CSIZE;
	switch(bytesize)
	{
		case 7:
			tio.c_cflag |= CS7;
			break;
		case 6:
			tio.c_cflag |= CS6;
			break;
		case 5:
			tio.c_cflag |=CS5;
			break;
		default: /* assume 8 bit */
			tio.c_cflag |= CS8;
			break;
	}
	/* Change parity checking mode */
	if(parity)
	{
		tio.c_iflag |= INPCK;
		tio.c_cflag |= PARENB;
		tio.c_iflag &= ~IGNPAR;
	}
	else
	{
		tio.c_iflag &= ~INPCK;
		tio.c_cflag &= ~PARENB;
		tio.c_iflag |= IGNPAR;
	}
	/* In either case, don't mark erroneous bytes */
	tio.c_iflag &= ~PARMRK;
	/* Full 8-bit input */
	tio.c_iflag &= ~ISTRIP;
	/* Ignore break conditions */
	tio.c_iflag |= IGNBRK;
	tio.c_iflag &= ~BRKINT;
	/* Don't handle EOLs */
	tio.c_iflag &= ~IGNCR;
	tio.c_iflag &= ~ICRNL;
	tio.c_iflag &= ~INLCR;
	/* Disable Start / Stop control */
	tio.c_iflag &= ~IXON;
	tio.c_iflag &= ~IXOFF;
	/* Don't modify output data */
	tio.c_oflag &= ~(OPOST | ONLCR);
	/* Raw (non-canonical mode) */
	tio.c_lflag &= ~(ICANON | ECHO | ECHONL | ISIG | IEXTEN);
	/* Determine nearest baud rate */
#ifdef __linux
	/* There are very high rates defined in asm/termbits.h */
	if(rate>=4000000)
	{
		baud_rate=B4000000;
		near_rate=4000000;
	} else if(rate>=3500000)
	{
		baud_rate=B3500000;
		near_rate=4000000;
	} else if(rate>=3000000)
	{
		baud_rate=B3000000;
		near_rate=3000000;
	} else if(rate>=2500000)
	{
		baud_rate=B2500000;
		near_rate=2500000;
	} else if(rate>=2000000)
	{
		baud_rate=B2000000;
		near_rate=2000000;
	} else if(rate>=1500000)
	{
		baud_rate=B1500000;
		near_rate=1500000;
	} else if(rate>=1152000)
	{
		baud_rate=B1152000;
		near_rate=1152000;
	} else if(rate>=1000000)
	{
		baud_rate=B1000000;
		near_rate=1000000;
	} else if(rate>=921600)
	{
		baud_rate=B921600;
		near_rate=921600;
	} else if(rate>=576000)
	{
		baud_rate=B576000;
		near_rate=576000;
	} else if(rate>=500000)
	{
		baud_rate=B500000;
		near_rate=500000;
	} else if(rate>=460800)
	{
		baud_rate=B460800;
		near_rate=460800;
	} else if(rate>=230400)
	{
		baud_rate=B230400;
		near_rate=230400;
	} else if(rate>=115200)
	{
		baud_rate=B115200;
		near_rate=115200;
	} else if(rate>=57600)
	{
		baud_rate=B57600;
		near_rate=57600;
	} else
#endif /* __linux */
	/* Lower rates defined by POSIX.1 */
	if(rate>=38400)
	{
		baud_rate=B38400;
		near_rate=38400;
	} else if(rate>=19200)
	{
		baud_rate=B19200;
		near_rate=19200;
	} else if(rate>=9600)
	{
		baud_rate=B9600;
		near_rate=9600;
	} else if(rate>=4800)
	{
		baud_rate=B4800;
		near_rate=4800;
	} else if(rate>=2400)
	{
		baud_rate=B2400;
		near_rate=2400;
	} else if(rate>=1200)
	{
		baud_rate=B1200;
		near_rate=1200;
	} else if(rate>=600)
	{
		baud_rate=B600;
		near_rate=600;
	} else if(rate>=300)
	{
		baud_rate=B300;
		near_rate=300;
	} else if(rate>=200)
	{
		baud_rate=B200;
		near_rate=200;
	} else if(rate>=150)
	{
		baud_rate=B150;
		near_rate=150;
	} else if(rate>=134)
	{
		baud_rate=B134;
		near_rate=134;
	} else if(rate>=110)
	{
		baud_rate=B110;
		near_rate=110;
	} else if(rate>=75)
	{
		baud_rate=B75;
		near_rate=75;
	} else /* rate < 75 baud; select 50 baud */
	{
		baud_rate=B50;
		near_rate=50;
	}
	cfsetospeed(&tio,baud_rate);
	cfsetispeed(&tio,baud_rate);
	/* Unblock 'read()' if at least one byte available */
	tio.c_cc[VMIN]=1;
	/* Set infinite read timeout */
	tio.c_cc[VTIME]=0;
	/* Set serial port state */
	if(tcsetattr(self->fd,TCSANOW,&tio)==-1)
	{
#ifdef	CHECKED_BUILD
		char buf[256];
#ifdef	CONFIG_HAVE_STERROR_R
		strerror_r(errno,buf,256);
#else	/* !CONFIG_HAVE_STERROR_R */
		strCopy(buf,strerror(errno),256);
#endif	/* !CONFIG_HAVE_STERROR_R */
		conlPrintf(LOG_ERROR,
			"%s('%s'): tcsetattr() failed: %s\n",
			caller,pname,buf);
#endif	/* CHECKED_BUILD */
    	return false;
	}
	if(near_rate != rate)
	{
#ifdef	__linux
		/* Try to set non-standart baud rate */
		/* BUGBUG: is this correct ? */
		struct serial_struct serinfo;
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_INFO,
			"commOpen(): trying non-standard rate %d ...\n",
			rate);
#endif	/* CHECKED_BUILD */
		if(ioctl(self->fd,TIOCGSERIAL,&serinfo) < 0)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"%s(): unable to get info for '%s' (errno %d)\n",
				caller,pname,errno);
#endif		/* CHECKED_BUILD */
    		return false;
		}
		serinfo.baud_base=rate;
		serinfo.custom_divisor=1;
		if(ioctl(self->fd,TIOCSSERIAL,&serinfo) < 0)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"%s(): unable to set info for '%s' (errno %d)\n",
				caller,pname,errno);
#endif		/* CHECKED_BUILD */
	    	return false;
		}
#else	/* !__linux */
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,
			"commOpen(): requested %d rate; set %d\n",
			rate,near_rate);
#endif	/* CHECKED_BUILD */
#endif	/* !__linux */
	}
	return true;
}

#endif	/* __UNIX__ */

int	V_CALL
io_comm_control(void *object,unsigned code,void *parameters)
{
	/* TODO: more ioctl()s */
	switch(code)
	{
		case pctl_get_type:
			/* COMM port is a character device */
			if(!parameters)
				return -1;
			*((u32_t*)parameters) = 0;
			return 0;
		case pctl_comm_reset:
			io_comm_reset((comm_port_t*)object);
			return 0;
		default:
			break;
	}
	return -1;
}

/*
 *	Factory function
 */
vhandle_t	V_GLOBAL
commOpen(int port_num,int rate,int bytesize,int parity,int stopbits)
{
	comm_port_p port = (comm_port_p)ioAllocPort(sizeof(comm_port_t));
	if(!init_comm_port(port,port_num,rate,bytesize,parity,stopbits))
	{
		io_comm_destroy(port);
		return NULL;
	}
	port->vtbl.base.destroy = io_comm_destroy;
	port->vtbl.read = io_comm_read;
	port->vtbl.write = io_comm_write;
	port->vtbl.ioctl = io_comm_control;
	return port;
}
