/*********************************************************************/
/*                                                                   */
/*  This program was written by Chris Moran, 3:713/708@fidonet.      */
/*  Based on pdcommo by Paul Edwards, kerravon@w3.to                 */
/*  Released to the Public Domain                                    */
/*                                                                   */
/*********************************************************************/
/*********************************************************************/
/*                                                                   */
/*  pdcommw - Com routines for WIN32                                 */
/*                                                                   */
/*********************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "error.h"
#include "pdcommw.h"

#if 0                          
static void pdcommQueryLine(PDCOMM *pdcomm, 
                            int *parity,
                            int *data,
                            int *stop);
static void pdcommDisableXON(PDCOMM *pdcomm);
#endif                            

/* The string passed to BuildCommDCB needs to be (temporarily)
   writable, see KB Q201372 */

static char dcbstring[] = "1200,N,8,1";
                            
void pdcommDefaults(PDCOMM *pdcomm)
{
    (void)pdcomm;
    return;
}

/* Supports the following syntax (e.g. to open port 3):
   3
   COM3
   COM3:
   COM3:19200
*/

void pdcommInit(PDCOMM *pdcomm, char *name)
{
    char   fileName[FILENAME_MAX];
    unsigned long speed;
    char *p;
    
    strcpy(fileName, name);
    p = strchr(fileName, ':');
    if (p != NULL)
    {
        speed = atol(p + 1);
        *p = '\0';
    }
    else
    {
        speed = 0;
        if (isdigit(*fileName))
        {
            sprintf(fileName, "COM%c", *fileName);
        }
    }
    pdcomm->hfile = CreateFile(fileName,
                GENERIC_READ | GENERIC_WRITE,
                0,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);

    if (pdcomm->hfile == INVALID_HANDLE_VALUE)
    {
        errorSet(COMERROR);
        printf("rc from CreateFile() is %lu\n", GetLastError());
    }
    else
    {
        if (speed != 0)
        {
            pdcommSetSpeed(pdcomm, speed);
        }
        pdcommSetParms(pdcomm, PDCOMM_NO_PARITY, 8, 1);
        if (ALLOK)
        {
            pdcommRaiseDTR(pdcomm);
        }
    }
    return;
}                 

void pdcommSetSpeed(PDCOMM *pdcomm, long speed)
{
    BOOL rc;
    DCB dcb;
    /* This is a hack to create a DCB the easy way */
    rc = BuildCommDCB(dcbstring, &dcb);
    if(rc != TRUE)
    {
        printf("rc from BuildCommDCB is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }
    
    // Get the current parameters
    rc = GetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from GetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }

    // Now set the DCB up correctly...
    switch(speed)
    {
        case 110:
            dcb.BaudRate = CBR_110;
            break;
        case 300:
            dcb.BaudRate = CBR_300;
            break;
        case 600:
            dcb.BaudRate = CBR_600;
            break;
        case 1200:
            dcb.BaudRate = CBR_1200;
            break;
        case 2400:
            dcb.BaudRate = CBR_2400;
            break;
        case 4800:
            dcb.BaudRate = CBR_4800;
            break;
        case 9600:
            dcb.BaudRate = CBR_9600;
            break;
        case 14400:
            dcb.BaudRate = CBR_14400;
            break;
        case 19200:
            dcb.BaudRate = CBR_19200;
            break;
        case 38400:
            dcb.BaudRate = CBR_38400;
            break;
        case 56000:
            dcb.BaudRate = CBR_56000;
            break;
        case 57600:
            dcb.BaudRate = CBR_57600;
            break;
        case 115200:
            dcb.BaudRate = CBR_115200;
            break;
        case 128000:
            dcb.BaudRate = CBR_128000;
            break;
        case 256000:
            dcb.BaudRate = CBR_256000;
            break;
        default:
            printf("Invalid baud rate: %lu\n", speed);
            errorSet(COMERROR);
            return;
    }

    rc = SetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from SetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
    }
    return;
}

void pdcommSetParms(PDCOMM *pdcomm, int parity, int data, int stop)
{
    BOOL rc;
    DCB dcb;


    /* This is a hack to create a DCB the easy way */
    rc = BuildCommDCB(dcbstring, &dcb);
    if(rc != TRUE)
    {
        printf("rc from BuildCommDCB is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }
    
    // Get the current parameters
    rc = GetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from GetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }
    
    // Set up the DCB for the comm parameters
    dcb.ByteSize = (BYTE)data;
    switch(parity)
    {
        case PDCOMM_NO_PARITY:
            dcb.Parity = NOPARITY;
            break;
        case PDCOMM_EVEN_PARITY:
            dcb.Parity = EVENPARITY;
            break;
        case PDCOMM_ODD_PARITY:
            dcb.Parity = ODDPARITY;
            break;
        default:
            printf("Invalid parity value: %d\n", parity);
            errorSet(COMERROR);
            return;
            break;
    }
    switch(stop)
    {
        case 1:
            dcb.StopBits = 0;
            break;
        case 2:
            dcb.StopBits = 2;
            break;
        default:
            printf("Invalid stop bits value: %d\n", stop);
            errorSet(COMERROR);
            return;
            break;
    }

    rc = SetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from SetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
    }
    return;
}

#if 0
static void pdcommQueryLine(PDCOMM *pdcomm, 
                            int *parity,
                            int *data,
                            int *stop)
{
    APIRET rc;
    struct {
        BYTE data;
        BYTE parity;
        BYTE stop;
        BYTE brk;
    } lineCharacter;
    
    rc = DosDevIOCtl(pdcomm->hfile, 
                     IOCTL_ASYNC,
                     0x62, /* set extended bit rate */
                     NULL,
                     0,
                     NULL,
                     &lineCharacter,
                     sizeof lineCharacter,
                     NULL);
    if (rc != 0)
    {
        printf("rc from DosDevIoCtl is %lu\n", (unsigned long)rc);
        errorSet(COMERROR);
    }
    else
    {
        *parity = (int)lineCharacter.parity;
        *data = (int)lineCharacter.data;
        *stop = (int)lineCharacter.stop;
    }    
    return;
}
#endif

void pdcommRaiseDTR(PDCOMM *pdcomm)
{
    BOOL rc;
    DCB dcb;

    /* This is a hack to create a DCB the easy way */
    rc = BuildCommDCB(dcbstring, &dcb);
    if(rc != TRUE)
    {
        printf("rc from BuildCommDCB is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }
    
    // Get the current parameters
    rc = GetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from GetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }
    
    // Set up the DCB to Raise DTR
    dcb.fDtrControl = DTR_CONTROL_ENABLE;

    // Set the change in place
    rc = SetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from SetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
    }
    return;
}

void pdcommDropDTR(PDCOMM *pdcomm)
{
    BOOL rc;
    DCB dcb;

    /* This is a hack to create a DCB the easy way */
    rc = BuildCommDCB(dcbstring, &dcb);
    if(rc != TRUE)
    {
        printf("rc from BuildCommDCB is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }
    
    // Get the current parameters
    rc = GetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from GetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
        return;
    }
    
    // Set up the DCB to Raise DTR
    dcb.fDtrControl = DTR_CONTROL_DISABLE;

    // Set the change in place
    rc = SetCommState(pdcomm->hfile, &dcb);
    if (rc != TRUE)
    {
        printf("rc from SetCommState is %lu\n", GetLastError());
        errorSet(COMERROR);
    }
    return;
}

#if 0
static void pdcommDisableXON(PDCOMM *pdcomm)
{
    APIRET rc;
    struct {
        unsigned short write;
        unsigned short read;
        BYTE flags1;
        BYTE flags2;
        BYTE flags3;
        BYTE err;
        BYTE brk;
        BYTE xon;
        BYTE xoff;
    } parm;
    
    rc = DosDevIOCtl(pdcomm->hfile, 
                     IOCTL_ASYNC,
                     0x73, 
                     NULL,
                     0,
                     NULL,
                     &parm,
                     sizeof parm,
                     NULL);
    if (rc != 0)
    {
        printf("rc from DosDevIoCtl is %lu\n", (unsigned long)rc);
        errorSet(COMERROR);
    }
    else
    {
        parm.flags2 &= ~0x03;
        rc = DosDevIOCtl(pdcomm->hfile, 
                         IOCTL_ASYNC,
                         0x53, 
                         &parm,
                         sizeof parm,
                         NULL,
                         NULL,
                         0,
                         NULL);
        if (rc != 0)
        {
            printf("rc from DosDevIoCtl is %lu\n", (unsigned long)rc);
            errorSet(COMERROR);
        }
    }
    return;
}
#endif

int pdcommWriteCh(PDCOMM *pdcomm, int ch)
{
    DWORD actualWritten;
    BOOL rc;
    int ret;
    unsigned char buf[1];
    
    buf[0] = (unsigned char)ch;
    rc = WriteFile(pdcomm->hfile, buf, (DWORD)1, &actualWritten, NULL);
    if (rc != TRUE)
    {
        printf("rc from WriteFile is %lu\n", GetLastError());
        ret = EOF;
        errorSet(COMERROR);
    }
    else
    {
        ret = ch;
    }
    return (ret);
}    

size_t pdcommWriteBuf(PDCOMM *pdcomm, void *buf, size_t num)
{
    DWORD actualWritten;
    int rc;
    
    rc = WriteFile(pdcomm->hfile, buf, (DWORD)num, &actualWritten, NULL);
    if (rc != TRUE)
    {
        printf("rc from WriteFile is %lu\n", GetLastError());
        errorSet(COMERROR);
        actualWritten = 0;
    }
    else if (actualWritten != num)
    {
        printf("only wrote %d bytes instead of %d\n", actualWritten, num);
        errorSet(COMERROR);
    }
    return ((size_t)actualWritten);
}

int pdcommReadCh(PDCOMM *pdcomm)
{
    DWORD actualRead;
    unsigned char buf[1];
    BOOL rc;
    int ret = EOF;
    DWORD numWaiting;
    COMSTAT cst;
    DWORD  cerrors;

    numWaiting = 0;

    /* query # characters in receive queue */
    rc = ClearCommError(pdcomm->hfile, &cerrors, &cst);
    if (rc == 0)
    {
        printf("rc from ClearCommError is %lu\n", GetLastError());
        errorSet(COMERROR);
        return (0);
    }
    numWaiting = cst.cbInQue;

    if (numWaiting == 0)
    {
        return (EOF);
    }
    rc = ReadFile(pdcomm->hfile, buf, 1, &actualRead, NULL);
    if (rc != TRUE)
    {
        printf("rc from ReadFile is %lu\n", GetLastError());
    }
    else
    {
        if (actualRead == 1)
        {
            ret = buf[0];
        }
    }
    return (ret);
}    

size_t pdcommReadBuf(PDCOMM *pdcomm, void *buf, size_t num)
{
    DWORD  actualRead;
    BOOL    rc;
    DWORD  numWaiting;
    COMSTAT cst;
    DWORD  cerrors;

    numWaiting = 0;
    /* query # characters in receive queue */
    rc = ClearCommError(pdcomm->hfile, &cerrors, &cst);
    if (rc == 0)
    {
        printf("rc from ClearCommError is %lu\n", GetLastError());
        errorSet(COMERROR);
        return (0);
    }
    numWaiting = cst.cbInQue;

    if (numWaiting == 0)
    {
        return (0);
    }
    if (num > numWaiting) 
    {
        num = numWaiting;
    }
    rc = ReadFile(pdcomm->hfile, buf, num, &actualRead, NULL);
    if (rc != TRUE)
    {
        printf("rc from ReadFile is %lu\n", GetLastError());
        return (0);
    }
    return ((size_t)actualRead);
}    

void pdcommTerm(PDCOMM *pdcomm)
{
    BOOL rc;
    
    pdcommDropDTR(pdcomm);
    rc = CloseHandle(pdcomm->hfile);
    if (rc != TRUE)
    {
        printf("rc from CloseHandle is %lu\n", GetLastError());
        errorSet(COMERROR);
    }
    return;
}
