/*********************************************************************/
/*                                                                   */
/*  Amiga Serial Routines by Michael Stapleton, 3:711/934.33@fidonet */
/*  Released to the Public Domain                                    */
/*  Based on work by Paul Edwards.                                   */
/*                                                                   */
/*********************************************************************/
/*********************************************************************/
/*                                                                   */
/*  pdcomma - Com routines for Amiga                                 */
/*                                                                   */
/*********************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include <proto/dos.h>
#include <proto/exec.h>

#include "error.h"
#include "pdcomma.h"

typedef struct IORequest *IOREQ;

size_t pdcommQuery(PDCOMM * pdcomm);
int pSerError(unsigned char errnum);
void pdcommClose(PDCOMM * pdcomm);

void pdcommDefaults(PDCOMM * pdcomm)
{
    pdcomm->IO = NULL;
    pdcomm->port = NULL;
    pdcomm->flags = 0;
}

/* Supports the following syntax (e.g. to open serial.device, unit 0):
   serial.device
   serial.device,0
   serial.device,0,19200
   an empty string accepts prefs/serial defaults
*/
void pdcommInit(PDCOMM * pdcomm, char *name)
{
    char serName[FILENAME_MAX];
    unsigned long unit = 0;
    unsigned long speed = 0;
    char *p;

    strcpy(serName, name);
    p = strchr(serName, ',');
    if (p != NULL)
    {
        unit = atol(p + 1);
        *p++ = '\0';

        p = strchr(p, ',');

        if (p != NULL)
            speed = atol(p + 1);
    }
    if (*serName == '\0')
        strcpy(serName, SERIALNAME);

    if (pdcomm->port = CreatePort(NULL, 0L))
    {
        if (pdcomm->IO = (struct IOExtSer *)
            CreateExtIO(pdcomm->port, sizeof(struct IOExtSer)))
        {
            pdcomm->IO->io_RBufLen = 500;  /* ...seems to help */
            pdcomm->IO->io_SerFlags = SERF_XDISABLED;
            if (!OpenDevice(serName, unit, (IOREQ) pdcomm->IO, 0))
            {
                pdcomm->flags |= SERIALOPEN;
                if (speed)
                    pdcommSetSpeed(pdcomm, speed);
                pdcommSetParms(pdcomm, PDCOMM_NO_PARITY, 8, 1);
                if (ALLOK) pdcommRaiseDTR(pdcomm);
            }
            else
            {
                errorSet(COMERROR);
                printf("Can't open %s, unit %ld\n", serName, unit);
                pSerError(pdcomm->IO->IOSer.io_Error);
                pdcommClose(pdcomm);
            }
        }
        else
        {
            errorSet(COMERROR);
            printf("Can't create IO request\n");
            pdcommClose(pdcomm);
        }
    }
    else
    {
        errorSet(COMERROR);
        printf("Can't open message port\n");
    }
}

void pdcommSetSpeed(PDCOMM * pdcomm, long speed)
{
    pdcomm->IO->io_Baud = speed;
    pdcomm->IO->IOSer.io_Command = SDCMD_SETPARAMS;

    if (DoIO((IOREQ) pdcomm->IO))
    {
        errorSet(COMERROR);
        printf("Can't set speed\n");
        pSerError(pdcomm->IO->IOSer.io_Error);
    }
}

void pdcommSetParms(PDCOMM * pdcomm, int parity, int data, int stop)
{
    /* Set Parameters */
    if (parity)
        pdcomm->IO->io_SerFlags |= SERF_PARTY_ON;
    else
        pdcomm->IO->io_SerFlags &= ~SERF_PARTY_ON;

    pdcomm->IO->io_ReadLen = data;
    pdcomm->IO->io_WriteLen = data;
    pdcomm->IO->io_StopBits = stop;
    pdcomm->IO->IOSer.io_Command = SDCMD_SETPARAMS;

    if (DoIO((IOREQ) pdcomm->IO))
    {
        errorSet(COMERROR);
        printf("Can't set parameters\n");
        pSerError(pdcomm->IO->IOSer.io_Error);
        pdcommClose(pdcomm);
    }
}

int pdcommWriteCh(PDCOMM * pdcomm, int ch)
{
    int ret;
    unsigned char buf = ch;

    pdcomm->IO->IOSer.io_Command = CMD_WRITE;
    pdcomm->IO->IOSer.io_Data = (APTR) & buf;
    pdcomm->IO->IOSer.io_Length = 1;

    if (DoIO((IOREQ) pdcomm->IO))
    {
        printf("Can't write to serial port\n");
        pSerError(pdcomm->IO->IOSer.io_Error);
        ret = EOF;
        errorSet(COMERROR);
    }
    else
        ret = ch;

    return ret;
}

size_t pdcommWriteBuf(PDCOMM * pdcomm, void *buf, size_t num)
{
    ULONG actualWritten;

    pdcomm->IO->IOSer.io_Command = CMD_WRITE;
    pdcomm->IO->IOSer.io_Data = (APTR) buf;
    pdcomm->IO->IOSer.io_Length = num;

    if (DoIO((IOREQ) pdcomm->IO))
    {
        printf("Can't write to serial port.\n");
        pSerError(pdcomm->IO->IOSer.io_Error);
        actualWritten = 0;
        errorSet(COMERROR);
    }
    else
    {
        actualWritten = pdcomm->IO->IOSer.io_Actual;
        if (actualWritten != num)
        {
            printf("only wrote %d bytes instead of %d\n", actualWritten, num);
            errorSet(COMERROR);
        }
    }

    return (size_t) actualWritten;
}

/* query # characters in receive queue */
size_t pdcommQuery(PDCOMM * pdcomm)
{
    short numWaiting = 0;

    pdcomm->IO->IOSer.io_Command = SDCMD_QUERY;

    if (DoIO((IOREQ) pdcomm->IO))
    {
        printf("Can't query serial port\n");
        pSerError(pdcomm->IO->IOSer.io_Error);
        errorSet(COMERROR);
    }
    else
        numWaiting = pdcomm->IO->IOSer.io_Actual;

    return (size_t) numWaiting;
}

int pdcommReadCh(PDCOMM * pdcomm)
{
    ULONG actualRead;
    unsigned char buf = '\0';
    int ret = EOF;

	actualRead = pdcommReadBuf(pdcomm, &buf, 1);
    if (actualRead == 1)
    	ret = buf;

    return ret;
}

size_t pdcommReadBuf(PDCOMM * pdcomm, void *buf, size_t num)
{
    ULONG actualRead = 0;
    short numWaiting;

	numWaiting = pdcommQuery(pdcomm);
    if (numWaiting == 0)
        return 0;

    if (num > numWaiting)
        num = numWaiting;

    pdcomm->IO->IOSer.io_Command = CMD_READ;
    pdcomm->IO->IOSer.io_Data = (APTR) buf;
    pdcomm->IO->IOSer.io_Length = num;

    if (DoIO((IOREQ) pdcomm->IO))
    {
        printf("Can't read from serial port\n");
        pSerError(pdcomm->IO->IOSer.io_Error);
        errorSet(COMERROR);
    }
    else
        actualRead = pdcomm->IO->IOSer.io_Actual;

    return (size_t) actualRead;
}

void pdcommClose(PDCOMM * pdcomm)
{
    if (pdcomm->flags & SERIALOPEN)
    {
        CloseDevice((IOREQ) pdcomm->IO);
        pdcomm->flags &= ~SERIALOPEN;
    }

    if (pdcomm->IO)
    {
        DeleteExtIO((IOREQ) pdcomm->IO);
        pdcomm->IO = NULL;
    }

    if (pdcomm->port)
    {
        DeletePort(pdcomm->port);
        pdcomm->port = NULL;
    }
}

void pdcommRaiseDTR(PDCOMM * pdcomm)
{
    (void) pdcomm;
}

void pdcommDropDTR(PDCOMM * pdcomm)
{
    (void) pdcomm;
}

void pdcommTerm(PDCOMM * pdcomm)
{
    pdcommDropDTR(pdcomm);
    pdcommClose(pdcomm);
}

static char *SerErr[] =
{
    "",     /* No error */
    "Device in use",
    "Baud rate not supported by hardware",
    "Unknown",
    "Failed to allocate new read buffer",
    "Bad parameter",
    "Hardware data overrun",
    "Unknown",
    "Unknown",
    "Parity error",
    "Unknown",
    "RTS/CTS Timeout",
    "Read buffer overflowed",
    "No Data Set Ready",
    "Unknown",
    "Break detected",
    "Selected unit already in use"
};

int pSerError(unsigned char errnum)
{
    return printf("Serial error %d: %s.\n", errnum, SerErr[errnum]);
}

