/*
 * DEC 93 Erik Bos <erik@xs4all.nl>
 *
 * Copyright 1996 Marcus Meissner
 *
 * Copyright 2001 Mike McCormack
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * History:
 *
 * Mar 31, 1999. Ove Kåven <ovek@arcticnet.no>
 * - Implemented buffers and EnableCommNotification.
 *
 * Apr 3, 1999.  Lawson Whitney <lawson_whitney@juno.com>
 * - Fixed the modem control part of EscapeCommFunction16.
 *
 * Mar 3, 1999. Ove Kåven <ovek@arcticnet.no>
 * - Use port indices instead of unixfds for win16
 * - Moved things around (separated win16 and win32 routines)
 * - Added some hints on how to implement buffers and EnableCommNotification.
 *
 * May 26, 1997.  Fixes and comments by Rick Richardson <rick@dgii.com> [RER]
 * - ptr->fd wasn't getting cleared on close.
 * - GetCommEventMask() and GetCommError() didn't do much of anything.
 *   IMHO, they are still wrong, but they at least implement the RXCHAR
 *   event and return I/O queue sizes, which makes the app I'm interested
 *   in (analog devices EZKIT DSP development system) work.
 *
 * August 12, 1997.  Take a bash at SetCommEventMask - Lawson Whitney
 *                                     <lawson_whitney@juno.com>
 * July 6, 1998. Fixes and comments by Valentijn Sessink
 *                                     <vsessink@ic.uva.nl> [V]
 * Oktober 98, Rein Klazes [RHK]
 * A program that wants to monitor the modem status line (RLSD/DCD) may
 * poll the modem status register in the commMask structure. I update the bit
 * in GetCommError, waiting for an implementation of communication events.
 *
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winreg.h"
#include "winuser.h"
#include "wine/winuser16.h"
#include "wine/port.h"
#include "win.h"
#include "winerror.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(comm);

/* window's semi documented modem status register */
#define COMM_MSR_OFFSET  35
#define MSR_CTS  0x10
#define MSR_DSR  0x20
#define MSR_RI   0x40
#define MSR_RLSD 0x80
#define MSR_MASK (MSR_CTS|MSR_DSR|MSR_RI|MSR_RLSD)

#define FLAG_LPT 0x80

#define MAX_PORTS   9

struct DosDeviceStruct {
    char *devicename;   /* /dev/ttyS0 */
    HANDLE handle;
    int suspended;
    int unget,xmit;
    int baudrate;
    int evtchar;
    /* events */
    int commerror, eventmask;
    /* buffers */
    char *inbuf,*outbuf;
    unsigned ibuf_size,ibuf_head,ibuf_tail;
    unsigned obuf_size,obuf_head,obuf_tail;
    /* notifications */
    HWND wnd;
    int n_read, n_write;
    OVERLAPPED read_ov, write_ov;
    /* save terminal states */
    DCB16 dcb;
    /* pointer to unknown(==undocumented) comm structure */
    SEGPTR seg_unknown;
    BYTE unknown[40];
};

static struct DosDeviceStruct COM[MAX_PORTS];
static struct DosDeviceStruct LPT[MAX_PORTS];

/* update window's semi documented modem status register */
/* see knowledge base Q101417 */
static void COMM_MSRUpdate( HANDLE handle, UCHAR * pMsr )
{
    UCHAR tmpmsr=0;
    DWORD mstat=0;

    if(!GetCommModemStatus(handle,&mstat))
        return;

    if(mstat & MS_CTS_ON) tmpmsr |= MSR_CTS;
    if(mstat & MS_DSR_ON) tmpmsr |= MSR_DSR;
    if(mstat & MS_RING_ON) tmpmsr |= MSR_RI;
    if(mstat & MS_RLSD_ON) tmpmsr |= MSR_RLSD;
    *pMsr = (*pMsr & ~MSR_MASK) | tmpmsr;
}

void COMM_Init(void)
{
	int x;
	char option[10], temp[256], *btemp;
	HKEY hkey;

	for (x=0; x!=MAX_PORTS; x++) {
		strcpy(option,"COMx");
		option[3] = '1' + x;
		option[4] = '\0';

		/* default value */
		strcpy(temp, "*");

		if(!RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\serialports", &hkey))
		{
		    DWORD type, count = sizeof(temp);

		    RegQueryValueExA(hkey, option, 0, &type, temp, &count);
		    RegCloseKey(hkey);
		}

		if (!strcmp(temp, "*") || *temp == '\0')
			COM[x].devicename = NULL;
		else {
		  	btemp = strchr(temp,',');
			if (btemp != NULL) {
			  	*btemp++ = '\0';
				COM[x].baudrate = atoi(btemp);
			} else {
				COM[x].baudrate = -1;
			}
			if ((COM[x].devicename = malloc(strlen(temp)+1)) == NULL)
				WARN("Can't malloc for device info!\n");
			else {
				COM[x].handle = 0;
				strcpy(COM[x].devicename, temp);
				TRACE("%s = %s\n", option, COM[x].devicename);
			}
 		}

		strcpy(option, "LPTx");
		option[3] = '1' + x;
		option[4] = '\0';

		/* default value */
		strcpy(temp, "*");

		if(!RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\parallelports", &hkey))
		{
		    DWORD type, count = sizeof(temp);

		    RegQueryValueExA(hkey, option, 0, &type, temp, &count);
		    RegCloseKey(hkey);
		}

		if (!strcmp(temp, "*") || *temp == '\0')
			LPT[x].devicename = NULL;
		else {
			if ((LPT[x].devicename = malloc(strlen(temp)+1)) == NULL)
				WARN("Can't malloc for device info!\n");
			else {
				LPT[x].handle = 0;
				strcpy(LPT[x].devicename, temp);
				TRACE("%s = %s\n", option, LPT[x].devicename);
			}
		}

	}
}


static struct DosDeviceStruct *GetDeviceStruct(int index)
{
	if ((index&0x7F)<=MAX_PORTS) {
            if (!(index&FLAG_LPT)) {
		if (COM[index].handle)
		    return &COM[index];
	    } else {
		index &= 0x7f;
		if (LPT[index].handle)
		    return &LPT[index];
	    }
	}

	return NULL;
}

static int    GetCommPort_ov(LPOVERLAPPED ov, int write)
{
	int x;

	for (x=0; x<MAX_PORTS; x++) {
		if (ov == (write?&COM[x].write_ov:&COM[x].read_ov))
			return x;
	}

	return -1;
}

static int ValidCOMPort(int x)
{
	return(x < MAX_PORTS ? (int) COM[x].devicename : 0);
}

static int ValidLPTPort(int x)
{
	return(x < MAX_PORTS ? (int) LPT[x].devicename : 0);
}

static int WinError(void)
{
        TRACE("errno = %d\n", errno);
	switch (errno) {
		default:
			return CE_IOE;
		}
}

static unsigned comm_inbuf(struct DosDeviceStruct *ptr)
{
  return ((ptr->ibuf_tail > ptr->ibuf_head) ? ptr->ibuf_size : 0)
    + ptr->ibuf_head - ptr->ibuf_tail;
}

static unsigned comm_outbuf(struct DosDeviceStruct *ptr)
{
  return ((ptr->obuf_tail > ptr->obuf_head) ? ptr->obuf_size : 0)
    + ptr->obuf_head - ptr->obuf_tail;
}

static void comm_waitread(struct DosDeviceStruct *ptr);
static void comm_waitwrite(struct DosDeviceStruct *ptr);

static VOID WINAPI COMM16_ReadComplete(DWORD status, DWORD len, LPOVERLAPPED ov)
{
	int prev ;
	WORD mask = 0;
	int cid = GetCommPort_ov(ov,0);
	struct DosDeviceStruct *ptr;

	if(cid<0) {
		ERR("async write with bad overlapped pointer\n");
		return;
	}
	ptr = &COM[cid];

	/* we get cancelled when CloseComm is called */
	if (status==STATUS_CANCELLED)
	{
		TRACE("Cancelled\n");
		return;
	}

	/* read data from comm port */
	if (status != STATUS_SUCCESS) {
		ERR("async read failed %08lx\n",status);
		COM[cid].commerror = CE_RXOVER;
		return;
	}
	TRACE("async read completed %ld bytes\n",len);

	prev = comm_inbuf(ptr);

	/* check for events */
	if ((ptr->eventmask & EV_RXFLAG) &&
	    memchr(ptr->inbuf + ptr->ibuf_head, ptr->evtchar, len)) {
		*(WORD*)(COM[cid].unknown) |= EV_RXFLAG;
		mask |= CN_EVENT;
	}
	if (ptr->eventmask & EV_RXCHAR) {
		*(WORD*)(COM[cid].unknown) |= EV_RXCHAR;
		mask |= CN_EVENT;
	}

	/* advance buffer position */
	ptr->ibuf_head += len;
	if (ptr->ibuf_head >= ptr->ibuf_size)
		ptr->ibuf_head = 0;

	/* check for notification */
	if (ptr->wnd && (ptr->n_read>0) && (prev<ptr->n_read) &&
	    (comm_inbuf(ptr)>=ptr->n_read)) {
		/* passed the receive notification threshold */
		mask |= CN_RECEIVE;
	}

	/* send notifications, if any */
	if (ptr->wnd && mask) {
		TRACE("notifying %04x: cid=%d, mask=%02x\n", ptr->wnd, cid, mask);
		PostMessageA(ptr->wnd, WM_COMMNOTIFY, cid, mask);
	}

	/* on real windows, this could cause problems, since it is recursive */
	/* restart the receive */
	comm_waitread(ptr);
}

/* this is meant to work like write() */
static INT COMM16_WriteFile(HANDLE hComm, LPCVOID buffer, DWORD len)
{
	OVERLAPPED ov;
	DWORD count= -1;

	ZeroMemory(&ov,sizeof ov);
	ov.hEvent = CreateEventA(NULL,0,0,NULL);
	if(ov.hEvent==INVALID_HANDLE_VALUE)
		return -1;

	if(!WriteFile(hComm,buffer,len,&count,&ov))
	{
		if(GetLastError()==ERROR_IO_PENDING)
		{
			GetOverlappedResult(hComm,&ov,&count,TRUE);
		}
	}
	CloseHandle(ov.hEvent);

	return count;
}

static VOID WINAPI COMM16_WriteComplete(DWORD status, DWORD len, LPOVERLAPPED ov)
{
	int prev, bleft;
	WORD mask = 0;
	int cid = GetCommPort_ov(ov,1);
	struct DosDeviceStruct *ptr;

	if(cid<0) {
		ERR("async write with bad overlapped pointer\n");
		return;
	}
	ptr = &COM[cid];

	/* read data from comm port */
	if (status != STATUS_SUCCESS) {
		ERR("async write failed\n");
		COM[cid].commerror = CE_RXOVER;
		return;
	}
	TRACE("async write completed %ld bytes\n",len);

	/* update the buffer pointers */
	prev = comm_outbuf(&COM[cid]);
	ptr->obuf_tail += len;
	if (ptr->obuf_tail >= ptr->obuf_size)
		ptr->obuf_tail = 0;

	/* write any TransmitCommChar character */
	if (ptr->xmit>=0) {
		len = COMM16_WriteFile(ptr->handle, &(ptr->xmit), 1);
		if (len > 0) ptr->xmit = -1;
	}

	/* write from output queue */
	bleft = ((ptr->obuf_tail <= ptr->obuf_head) ?
		ptr->obuf_head : ptr->obuf_size) - ptr->obuf_tail;

	/* check for notification */
	if (ptr->wnd && (ptr->n_write>0) && (prev>=ptr->n_write) &&
	  (comm_outbuf(ptr)<ptr->n_write)) {
		/* passed the transmit notification threshold */
		mask |= CN_TRANSMIT;
	}

	/* send notifications, if any */
	if (ptr->wnd && mask) {
		TRACE("notifying %04x: cid=%d, mask=%02x\n", ptr->wnd, cid, mask);
		PostMessageA(ptr->wnd, WM_COMMNOTIFY, cid, mask);
	}

	/* start again if necessary */
	if(bleft)
		comm_waitwrite(ptr);
}

static void comm_waitread(struct DosDeviceStruct *ptr)
{
	int bleft;
        COMSTAT stat;

	/* FIXME: get timeouts working properly so we can read bleft bytes */
	bleft = ((ptr->ibuf_tail > ptr->ibuf_head) ?
		(ptr->ibuf_tail-1) : ptr->ibuf_size) - ptr->ibuf_head;

	/* find out how many bytes are left in the buffer */
	if(ClearCommError(ptr->handle,NULL,&stat))
		bleft = (bleft<stat.cbInQue) ? bleft : stat.cbInQue;
	else
		bleft = 1;

	/* always read at least one byte */
	if(bleft==0)
		bleft++;

	ReadFileEx(ptr->handle,
		ptr->inbuf + ptr->ibuf_head,
		bleft,
		&ptr->read_ov,
		COMM16_ReadComplete);
}

static void comm_waitwrite(struct DosDeviceStruct *ptr)
{
	int bleft;

	bleft = ((ptr->obuf_tail <= ptr->obuf_head) ?
		ptr->obuf_head : ptr->obuf_size) - ptr->obuf_tail;
	WriteFileEx(ptr->handle,
		ptr->outbuf + ptr->obuf_tail,
		bleft,
		&ptr->write_ov,
		COMM16_WriteComplete);
}

/*****************************************************************************
 *	COMM16_DCBtoDCB16	(Internal)
 */
INT16 COMM16_DCBtoDCB16(LPDCB lpdcb, LPDCB16 lpdcb16)
{
	if(lpdcb->BaudRate<0x10000)
		lpdcb16->BaudRate = lpdcb->BaudRate;
	else if(lpdcb->BaudRate==115200)
			lpdcb16->BaudRate = 57601;
	else {
		WARN("Baud rate can't be converted\n");
		lpdcb16->BaudRate = 57601;
	}
	lpdcb16->ByteSize = lpdcb->ByteSize;
	lpdcb16->fParity = lpdcb->fParity;
	lpdcb16->Parity = lpdcb->Parity;
	lpdcb16->StopBits = lpdcb->StopBits;

	lpdcb16->RlsTimeout = 50;
	lpdcb16->CtsTimeout = 50;
	lpdcb16->DsrTimeout = 50;
	lpdcb16->fNull = 0;
	lpdcb16->fChEvt = 0;
	lpdcb16->fBinary = 1;

	lpdcb16->fDtrflow = (lpdcb->fDtrControl==DTR_CONTROL_HANDSHAKE);
	lpdcb16->fRtsflow = (lpdcb->fRtsControl==RTS_CONTROL_HANDSHAKE);
	lpdcb16->fOutxCtsFlow = lpdcb->fOutxCtsFlow;
	lpdcb16->fOutxDsrFlow = lpdcb->fOutxDsrFlow;
	lpdcb16->fDtrDisable = (lpdcb->fDtrControl==DTR_CONTROL_DISABLE);

	lpdcb16->fInX = lpdcb->fInX;

	lpdcb16->fOutX = lpdcb->fOutX;
/*
	lpdcb16->XonChar =
	lpdcb16->XoffChar =
 */
	lpdcb16->XonLim = 10;
	lpdcb16->XoffLim = 10;

	return 0;
}


/**************************************************************************
 *         BuildCommDCB		(USER.213)
 *
 * According to the ECMA-234 (368.3) the function will return FALSE on
 * success, otherwise it will return -1.
 */
INT16 WINAPI BuildCommDCB16(LPCSTR device, LPDCB16 lpdcb)
{
	/* "COM1:96,n,8,1"	*/
	/*  012345		*/
	int port;
	DCB dcb;

	TRACE("(%s), ptr %p\n", device, lpdcb);

	if (strncasecmp(device,"COM",3))
		return -1;
	port = device[3] - '0';

	if (port-- == 0) {
		ERR("BUG ! COM0 can't exist!\n");
		return -1;
	}

	if (!ValidCOMPort(port)) {
		FIXME("invalid COM port %d?\n",port);
		return -1;
	}

	memset(lpdcb, 0, sizeof(DCB16)); /* initialize */

	lpdcb->Id = port;
	dcb.DCBlength = sizeof(DCB);

	if (strchr(device,'=')) /* block new style */
		return -1;

	if(!BuildCommDCBA(device,&dcb))
		return -1;

	return COMM16_DCBtoDCB16(&dcb, lpdcb);
}

/*****************************************************************************
 *	OpenComm		(USER.200)
 */
INT16 WINAPI OpenComm16(LPCSTR device,UINT16 cbInQueue,UINT16 cbOutQueue)
{
	int port;
	HANDLE handle;

    	TRACE("%s, %d, %d\n", device, cbInQueue, cbOutQueue);

	if (strlen(device) < 4)
	   return IE_BADID;

	port = device[3] - '0';

	if (port-- == 0)
		ERR("BUG ! COM0 or LPT0 don't exist !\n");

	if (!strncasecmp(device,"COM",3)) {

                TRACE("%s = %s\n", device, COM[port].devicename);

		if (!ValidCOMPort(port))
			return IE_BADID;

		if (COM[port].handle)
			return IE_OPEN;

		handle = CreateFileA(device, GENERIC_READ|GENERIC_WRITE,
			FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
			FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING, 0 );
		if (handle == INVALID_HANDLE_VALUE) {
			ERR("Couldn't open %s ! (%s)\n", COM[port].devicename, strerror(errno));
			return IE_HARDWARE;
		} else {
			memset(COM[port].unknown, 0, sizeof(COM[port].unknown));
                        COM[port].seg_unknown = 0;
			COM[port].handle = handle;
			COM[port].commerror = 0;
			COM[port].eventmask = 0;
			COM[port].evtchar = 0; /* FIXME: default? */
                        /* save terminal state */
			GetCommState16(port,&COM[port].dcb);
                        /* set default parameters */
                        if(COM[port].baudrate>-1){
                            DCB16 dcb;
			    memcpy(&dcb,&COM[port].dcb,sizeof dcb);
                            dcb.BaudRate=COM[port].baudrate;
                            /* more defaults:
                             * databits, parity, stopbits
                             */
                            SetCommState16( &dcb);
                        }
			/* init priority characters */
			COM[port].unget = -1;
			COM[port].xmit = -1;
			/* allocate buffers */
			COM[port].ibuf_size = cbInQueue;
			COM[port].ibuf_head = COM[port].ibuf_tail = 0;
			COM[port].obuf_size = cbOutQueue;
			COM[port].obuf_head = COM[port].obuf_tail = 0;

			COM[port].inbuf = malloc(cbInQueue);
			if (COM[port].inbuf) {
			  COM[port].outbuf = malloc(cbOutQueue);
			  if (!COM[port].outbuf)
			    free(COM[port].inbuf);
			} else COM[port].outbuf = NULL;
			if (!COM[port].outbuf) {
			  /* not enough memory */
			  SetCommState16(&COM[port].dcb);
			  CloseHandle(COM[port].handle);
			  ERR("out of memory\n");
			  return IE_MEMORY;
			}

			ZeroMemory(&COM[port].read_ov,sizeof (OVERLAPPED));
			ZeroMemory(&COM[port].write_ov,sizeof (OVERLAPPED));

                        comm_waitread( &COM[port] );
			USER16_AlertableWait++;

			return port;
		}
	}
	else
	if (!strncasecmp(device,"LPT",3)) {

		if (!ValidLPTPort(port))
			return IE_BADID;

		if (LPT[port].handle)
			return IE_OPEN;

		handle = CreateFileA(device, GENERIC_READ|GENERIC_WRITE,
			FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0 );
		if (handle == INVALID_HANDLE_VALUE) {
			return IE_HARDWARE;
		} else {
			LPT[port].handle = handle;
			LPT[port].commerror = 0;
			LPT[port].eventmask = 0;
			return port|FLAG_LPT;
		}
	}
	return IE_BADID;
}

/*****************************************************************************
 *	CloseComm		(USER.207)
 */
INT16 WINAPI CloseComm16(INT16 cid)
{
	struct DosDeviceStruct *ptr;

    	TRACE("cid=%d\n", cid);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no cid=%d found!\n", cid);
		return -1;
	}
	if (!(cid&FLAG_LPT)) {
		/* COM port */
                UnMapLS( COM[cid].seg_unknown );
		USER16_AlertableWait--;
		CancelIo(ptr->handle);

		/* free buffers */
		free(ptr->outbuf);
		free(ptr->inbuf);

		/* reset modem lines */
		SetCommState16(&COM[cid].dcb);
	}

	if (!CloseHandle(ptr->handle)) {
		ptr->commerror = WinError();
		/* FIXME: should we clear ptr->handle here? */
		return -1;
	} else {
		ptr->commerror = 0;
		ptr->handle = 0;
		return 0;
	}
}

/*****************************************************************************
 *	SetCommBreak		(USER.210)
 */
INT16 WINAPI SetCommBreak16(INT16 cid)
{
	struct DosDeviceStruct *ptr;

	TRACE("cid=%d\n", cid);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no cid=%d found!\n", cid);
		return -1;
	}

	ptr->suspended = 1;
	ptr->commerror = 0;
	return 0;
}

/*****************************************************************************
 *	ClearCommBreak	(USER.211)
 */
INT16 WINAPI ClearCommBreak16(INT16 cid)
{
	struct DosDeviceStruct *ptr;

    	TRACE("cid=%d\n", cid);
	if (!(ptr = GetDeviceStruct(cid))) {
		FIXME("no cid=%d found!\n", cid);
		return -1;
	}
	ptr->suspended = 0;
	ptr->commerror = 0;
	return 0;
}

/*****************************************************************************
 *	EscapeCommFunction	(USER.214)
 */
LONG WINAPI EscapeCommFunction16(UINT16 cid,UINT16 nFunction)
{
	struct  DosDeviceStruct *ptr;
	int	max;

    	TRACE("cid=%d, function=%d\n", cid, nFunction);

	switch(nFunction) {
	case GETMAXCOM:
	        TRACE("GETMAXCOM\n");
		for (max = MAX_PORTS;!COM[max].devicename;max--)
			;
		return max;

	case GETMAXLPT:
		TRACE("GETMAXLPT\n");
		for (max = MAX_PORTS;!LPT[max].devicename;max--)
			;
		return FLAG_LPT + max;

	case GETBASEIRQ:
		TRACE("GETBASEIRQ\n");
		/* FIXME: use tables */
		/* just fake something for now */
		if (cid & FLAG_LPT) {
			/* LPT1: irq 7, LPT2: irq 5 */
			return (cid & 0x7f) ? 5 : 7;
		} else {
			/* COM1: irq 4, COM2: irq 3,
			   COM3: irq 4, COM4: irq 3 */
			return 4 - (cid & 1);
		}
	}

	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no cid=%d found!\n", cid);
		return -1;
	}

	switch (nFunction) {
	case RESETDEV:
	case CLRDTR:
	case CLRRTS:
	case SETDTR:
	case SETRTS:
	case SETXOFF:
	case SETXON:
		if(EscapeCommFunction(ptr->handle,nFunction))
			return 0;
		else {
			ptr->commerror = WinError();
			return -1;
		}

	case CLRBREAK:
	case SETBREAK:
	default:
		WARN("(cid=%d,nFunction=%d): Unknown function\n",
			cid, nFunction);
	}
	return -1;
}

/*****************************************************************************
 *	FlushComm	(USER.215)
 */
INT16 WINAPI FlushComm16(INT16 cid,INT16 fnQueue)
{
	DWORD queue;
	struct DosDeviceStruct *ptr;

    	TRACE("cid=%d, queue=%d\n", cid, fnQueue);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no cid=%d found!\n", cid);
		return -1;
	}
	switch (fnQueue) {
	case 0:
		queue = PURGE_TXABORT;
		ptr->obuf_tail = ptr->obuf_head;
		break;
	case 1:
		queue = PURGE_RXABORT;
		ptr->ibuf_head = ptr->ibuf_tail;
		break;
	default:
		WARN("(cid=%d,fnQueue=%d):Unknown queue\n",
		            cid, fnQueue);
		return -1;
	}

	if (!PurgeComm(ptr->handle,queue)) {
		ptr->commerror = WinError();
		return -1;
	} else {
		ptr->commerror = 0;
		return 0;
	}
}

/********************************************************************
 *	GetCommError	(USER.203)
 */
INT16 WINAPI GetCommError16(INT16 cid,LPCOMSTAT16 lpStat)
{
	int		temperror;
	struct DosDeviceStruct *ptr;
        unsigned char *stol;

	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
		return -1;
	}
        if (cid&FLAG_LPT) {
            WARN(" cid %d not comm port\n",cid);
            return CE_MODE;
        }
        stol = (unsigned char *)COM[cid].unknown + COMM_MSR_OFFSET;
	COMM_MSRUpdate( ptr->handle, stol );

	if (lpStat) {
		lpStat->status = 0;

		WaitForMultipleObjectsEx(0,NULL,FALSE,1,TRUE);

		lpStat->cbOutQue = comm_outbuf(ptr);
		lpStat->cbInQue = comm_inbuf(ptr);

    		TRACE("cid %d, error %d, stat %d in %d out %d, stol %x\n",
			     cid, ptr->commerror, lpStat->status, lpStat->cbInQue,
			     lpStat->cbOutQue, *stol);
	}
	else
		TRACE("cid %d, error %d, lpStat NULL stol %x\n",
			     cid, ptr->commerror, *stol);

	/* Return any errors and clear it */
	temperror = ptr->commerror;
	ptr->commerror = 0;
	return(temperror);
}

/*****************************************************************************
 *	SetCommEventMask	(USER.208)
 */
SEGPTR WINAPI SetCommEventMask16(INT16 cid,UINT16 fuEvtMask)
{
	struct DosDeviceStruct *ptr;
        unsigned char *stol;

    	TRACE("cid %d,mask %d\n",cid,fuEvtMask);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
	    return (SEGPTR)NULL;
	}

	ptr->eventmask = fuEvtMask;

        if ((cid&FLAG_LPT) || !ValidCOMPort(cid)) {
            WARN(" cid %d not comm port\n",cid);
            return (SEGPTR)NULL;
        }
        /* it's a COM port ? -> modify flags */
        stol = (unsigned char *)COM[cid].unknown + COMM_MSR_OFFSET;
	COMM_MSRUpdate( ptr->handle, stol );

	TRACE(" modem dcd construct %x\n",*stol);
        if (!COM[cid].seg_unknown) COM[cid].seg_unknown = MapLS( COM[cid].unknown );
        return COM[cid].seg_unknown;
}

/*****************************************************************************
 *	GetCommEventMask	(USER.209)
 */
UINT16 WINAPI GetCommEventMask16(INT16 cid,UINT16 fnEvtClear)
{
	struct DosDeviceStruct *ptr;
	WORD events;

    	TRACE("cid %d, mask %d\n", cid, fnEvtClear);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
	    return 0;
	}

        if ((cid&FLAG_LPT) || !ValidCOMPort(cid)) {
            WARN(" cid %d not comm port\n",cid);
            return 0;
        }

	events = *(WORD*)(COM[cid].unknown) & fnEvtClear;
	*(WORD*)(COM[cid].unknown) &= ~fnEvtClear;
	return events;
}

/*****************************************************************************
 *	SetCommState	(USER.201)
 */
INT16 WINAPI SetCommState16(LPDCB16 lpdcb)
{
	struct DosDeviceStruct *ptr;
	DCB dcb;

    	TRACE("cid %d, ptr %p\n", lpdcb->Id, lpdcb);
	if ((ptr = GetDeviceStruct(lpdcb->Id)) == NULL) {
		FIXME("no handle for cid = %0x!\n",lpdcb->Id);
		return -1;
	}

	memset(&dcb,0,sizeof dcb);
	dcb.DCBlength = sizeof dcb;

	/*
	 * according to MSDN, we should first interpret lpdcb->BaudRate as follows:
	 * 1. if the baud rate is a CBR constant, interpret it.
	 * 2. if it is greater than 57600, the baud rate is 115200
	 * 3. use the actual baudrate
	 * steps 2 and 3 are equivilent to 16550 baudrate divisor = 115200/BaudRate
	 * see http://support.microsoft.com/support/kb/articles/q108/9/28.asp
	 */
	switch(lpdcb->BaudRate)
	{
	case CBR_110:    dcb.BaudRate = 110;    break;
	case CBR_300:    dcb.BaudRate = 300;    break;
	case CBR_600:    dcb.BaudRate = 600;    break;
	case CBR_1200:   dcb.BaudRate = 1200;   break;
	case CBR_2400:   dcb.BaudRate = 2400;   break;
	case CBR_4800:   dcb.BaudRate = 4800;   break;
	case CBR_9600:   dcb.BaudRate = 9600;   break;
	case CBR_14400:  dcb.BaudRate = 14400;  break;
	case CBR_19200:  dcb.BaudRate = 19200;  break;
	case CBR_38400:  dcb.BaudRate = 38400;  break;
	case CBR_56000:  dcb.BaudRate = 56000;  break;
	case CBR_128000: dcb.BaudRate = 128000; break;
	case CBR_256000: dcb.BaudRate = 256000; break;
	default:
		if(lpdcb->BaudRate>57600)
		dcb.BaudRate = 115200;
        else
		dcb.BaudRate = lpdcb->BaudRate;
 	}

        dcb.ByteSize=lpdcb->ByteSize;
        dcb.StopBits=lpdcb->StopBits;

	dcb.fParity=lpdcb->fParity;
	dcb.Parity=lpdcb->Parity;

	dcb.fOutxCtsFlow = lpdcb->fOutxCtsFlow;

	if (lpdcb->fDtrflow || lpdcb->fRtsflow)
		dcb.fRtsControl = TRUE;

	if (lpdcb->fDtrDisable)
		dcb.fDtrControl = TRUE;

	ptr->evtchar = lpdcb->EvtChar;

	dcb.fInX = lpdcb->fInX;
	dcb.fOutX = lpdcb->fOutX;

	if (!SetCommState(ptr->handle,&dcb)) {
		ptr->commerror = WinError();
		return -1;
	} else {
		ptr->commerror = 0;
		return 0;
	}
}

/*****************************************************************************
 *	GetCommState	(USER.202)
 */
INT16 WINAPI GetCommState16(INT16 cid, LPDCB16 lpdcb)
{
	struct DosDeviceStruct *ptr;
	DCB dcb;

    	TRACE("cid %d, ptr %p\n", cid, lpdcb);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
		return -1;
	}
	if (!GetCommState(ptr->handle,&dcb)) {
		ptr->commerror = WinError();
		return -1;
	}

	lpdcb->Id = cid;

	COMM16_DCBtoDCB16(&dcb,lpdcb);

	lpdcb->EvtChar = ptr->evtchar;

	ptr->commerror = 0;
	return 0;
}

/*****************************************************************************
 *	TransmitCommChar	(USER.206)
 */
INT16 WINAPI TransmitCommChar16(INT16 cid,CHAR chTransmit)
{
	struct DosDeviceStruct *ptr;

    	TRACE("cid %d, data %d \n", cid, chTransmit);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
		return -1;
	}

	if (ptr->suspended) {
		ptr->commerror = IE_HARDWARE;
		return -1;
	}

	if (ptr->xmit >= 0) {
	  /* character already queued */
	  /* FIXME: which error would Windows return? */
	  ptr->commerror = CE_TXFULL;
	  return -1;
	}

	if (ptr->obuf_head == ptr->obuf_tail) {
	  /* transmit queue empty, try to transmit directly */
	  if(1!=COMM16_WriteFile(ptr->handle, &chTransmit, 1))
	  {
	    /* didn't work, queue it */
	    ptr->xmit = chTransmit;
	    comm_waitwrite(ptr);
	  }
	} else {
	  /* data in queue, let this char be transmitted next */
	  ptr->xmit = chTransmit;
	  comm_waitwrite(ptr);
	}

	ptr->commerror = 0;
	return 0;
}

/*****************************************************************************
 *	UngetCommChar	(USER.212)
 */
INT16 WINAPI UngetCommChar16(INT16 cid,CHAR chUnget)
{
	struct DosDeviceStruct *ptr;

    	TRACE("cid %d (char %d)\n", cid, chUnget);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
		return -1;
	}

	if (ptr->suspended) {
		ptr->commerror = IE_HARDWARE;
		return -1;
	}

	if (ptr->unget>=0) {
	  /* character already queued */
	  /* FIXME: which error would Windows return? */
	  ptr->commerror = CE_RXOVER;
	  return -1;
	}

	ptr->unget = chUnget;

	ptr->commerror = 0;
	return 0;
}

/*****************************************************************************
 *	ReadComm	(USER.204)
 */
INT16 WINAPI ReadComm16(INT16 cid,LPSTR lpvBuf,INT16 cbRead)
{
	int status, length;
	struct DosDeviceStruct *ptr;
	LPSTR orgBuf = lpvBuf;

    	TRACE("cid %d, ptr %p, length %d\n", cid, lpvBuf, cbRead);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
		return -1;
	}

	if (ptr->suspended) {
		ptr->commerror = IE_HARDWARE;
		return -1;
	}

	if(0==comm_inbuf(ptr))
		WaitForMultipleObjectsEx(0,NULL,FALSE,1,TRUE);

	/* read unget character */
	if (ptr->unget>=0) {
		*lpvBuf++ = ptr->unget;
		ptr->unget = -1;

		length = 1;
	} else
	 	length = 0;

	/* read from receive buffer */
	while (length < cbRead) {
	  status = ((ptr->ibuf_head < ptr->ibuf_tail) ?
		    ptr->ibuf_size : ptr->ibuf_head) - ptr->ibuf_tail;
	  if (!status) break;
	  if ((cbRead - length) < status)
	    status = cbRead - length;

	  memcpy(lpvBuf, ptr->inbuf + ptr->ibuf_tail, status);
	  ptr->ibuf_tail += status;
	  if (ptr->ibuf_tail >= ptr->ibuf_size)
	    ptr->ibuf_tail = 0;
	  lpvBuf += status;
	  length += status;
	}

	TRACE("%s\n", debugstr_an( orgBuf, length ));
	ptr->commerror = 0;
	return length;
}

/*****************************************************************************
 *	WriteComm	(USER.205)
 */
INT16 WINAPI WriteComm16(INT16 cid, LPSTR lpvBuf, INT16 cbWrite)
{
	int status, length;
	struct DosDeviceStruct *ptr;

    	TRACE("cid %d, ptr %p, length %d\n",
		cid, lpvBuf, cbWrite);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
		return -1;
	}

	if (ptr->suspended) {
		ptr->commerror = IE_HARDWARE;
		return -1;
	}

	TRACE("%s\n", debugstr_an( lpvBuf, cbWrite ));

	length = 0;
	while (length < cbWrite) {
	  if ((ptr->obuf_head == ptr->obuf_tail) && (ptr->xmit < 0)) {
	    /* no data queued, try to write directly */
	    status = COMM16_WriteFile(ptr->handle, lpvBuf, cbWrite - length);
	    if (status > 0) {
	      lpvBuf += status;
	      length += status;
	      continue;
	    }
	  }
	  /* can't write directly, put into transmit buffer */
	  status = ((ptr->obuf_tail > ptr->obuf_head) ?
		    (ptr->obuf_tail-1) : ptr->obuf_size) - ptr->obuf_head;
	  if (!status) break;
	  if ((cbWrite - length) < status)
	    status = cbWrite - length;
	  memcpy(lpvBuf, ptr->outbuf + ptr->obuf_head, status);
	  ptr->obuf_head += status;
	  if (ptr->obuf_head >= ptr->obuf_size)
	    ptr->obuf_head = 0;
	  lpvBuf += status;
	  length += status;
	  comm_waitwrite(ptr);
	}

	ptr->commerror = 0;
	return length;
}

/***********************************************************************
 *           EnableCommNotification   (USER.245)
 */
BOOL16 WINAPI EnableCommNotification16( INT16 cid, HWND16 hwnd,
                                      INT16 cbWriteNotify, INT16 cbOutQueue )
{
	struct DosDeviceStruct *ptr;

	TRACE("(%d, %x, %d, %d)\n", cid, hwnd, cbWriteNotify, cbOutQueue);
	if ((ptr = GetDeviceStruct(cid)) == NULL) {
		FIXME("no handle for cid = %0x!\n",cid);
		return -1;
	}
	ptr->wnd = WIN_Handle32( hwnd );
	ptr->n_read = cbWriteNotify;
	ptr->n_write = cbOutQueue;
	return TRUE;
}

