/* STerm 1.0 -- © 1995 Dominique Lorre
 *
 * This was planned to be a very small terminal program (Small Term)
 * that I developped because a lack of place on my A600 disk
 * but it happened that I had to make it work with SAD (SAD Term).
 * Other possibilities are SerialTerm 8->, SmartTerm ;-),...
 *
 * This software is in the Public Domain. You can modify it but in
 * this case you should find a new name for the program.
 */

#include <exec/types.h>
#include <devices/console.h>
#include <devices/serial.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <clib/alib_protos.h>

static struct MsgPort *SerialPort, *conport ;
static struct IOExtSer *ReadIO, *WriteIO ;
static LONG Error ;
static ULONG SerSig ;
static ULONG ConSig ;
static ULONG WinSig ;
static ULONG signal ;
static struct FileHandle *fh ;
static UBYTE SerBuf[512] ;
static UBYTE ConBuf[512] ;
static BOOL fin=FALSE ;
static BPTR msgout ;
static struct Window *win ;
static struct IOStdReq *conreq, *outreq ;
static struct IntuiMessage *imsg ;

/* Command Datas are here */
static LONG hlen = 0, alen = 0 ;
static BOOL RawMode = TRUE ;
static LONG HexMode = 0 ;

/* SAD Datas are here */

static LONG length = 0 ;
static BOOL SADAsk = FALSE ;
static BOOL SADV39 = FALSE ;

static UBYTE ResetCmd[6] = { 0xAF, 0x10, 0xFF, 0xFF, 0xFF, 0xFF } ;
static UBYTE AbortCmd[6] = { 0xAF, 0x08, 0x00, 0x00, 0x00, 0x00 } ;
static UBYTE ReadWordCmd[6] = { 0xAF, 0x05, 0x00, 0xF8, 0x00, 0x00 } ;
static LONG rate[8] = {
    110, 300, 1200, 2400, 4800, 9600, 19200, 31250
};
static UBYTE srate[8][6] = {
    "110", "300", "1200", "2400", "4800", "9600", "19200", "31250"
};

/* Functions Prototypes */

BOOL InitDevs(void) ;
BOOL CloseDevs(WORD) ;
BOOL InitConsole(void) ;
BOOL CloseConsole(WORD) ;

BOOL SADCheckVersion(void) ;
void SADAbort(void) ;
void SADReset(void) ;
void SADSendCmd(STRPTR cmd, LONG len) ;
BOOL SADAckCmd(UBYTE ack) ;
void ConOut(STRPTR msg) ;

void SetBaudRate(long Baud) ;
void DisplayHelp(void) ;
void SendCom(STRPTR s) ;


void main(void)
{
struct IOExtSer *srep ;
struct IOStdReq *crep ;

    if (InitDevs()) {
        if (InitConsole()) {
            do {
                signal = Wait(SerSig|ConSig|WinSig) ;
                /* No class check because only IDCMP_CLOSEWINDOW here */
                if (signal & WinSig) {
                    while (imsg = (struct IntuiMessage *)GetMsg(win->UserPort)) {
                        ReplyMsg((struct Message *)imsg) ;
                        fin = TRUE ;
                    }
                }
                /* serial stuff */
                if (signal & SerSig) {
                    while (srep = (struct IOExtSer *)GetMsg(SerialPort)) {
                        /* Here we check for SAD[?|!|¿] commands */
                        if (!srep->IOSer.io_Error && !SADAsk) {
                            if (length == 3) {
                                if (SerBuf[0] == '!' || SerBuf[0] == '?' ||
                                        SerBuf[0] == 0xBF) {
                                    SADAsk = TRUE ;
                                    length = 0 ;
                                }
                                else
                                    length = 0 ;
                            }
                            if (!RawMode) {
                                if (length == 2)
                                    length = (SerBuf[0] == 'D')?3:0 ;
                                if (length == 1)
                                    length = (SerBuf[0] == 'A')?2:0 ;
                                if (length == 0)
                                    length = (SerBuf[0] == 'S')?1:0 ;

                                if (hlen == 2) {
                                    if ((SerBuf[0] | 0x20) == 'x') {
                                        HexMode = 1 ;
                                        ConOut("HexMode set.\n") ;
                                    }
                                    hlen = 0 ;
                                }
                                if (hlen == 1)
                                    hlen = ((SerBuf[0] | 0x20) == 'e')?2:0;
                                if (hlen == 0)
                                    hlen = ((SerBuf[0] | 0x20) == 'h')?1:0;

                                if (alen == 2) {
                                    if ((SerBuf[0] | 0x20) == 'c') {
                                        HexMode = 0 ;
                                        ConOut("HexMode unset.\n") ;
                                    }
                                    alen = 0 ;
                                }
                                if (alen == 1)
                                    alen = ((SerBuf[0] | 0x20) == 's')?2:0;
                                if (alen == 0)
                                    alen = ((SerBuf[0] | 0x20) == 'a')?1:0;

                                if (alen == 2) {
                                    if ((SerBuf[0] | 0x20) == 'h') {
                                        HexMode = 2 ;
                                        ConOut("Semi HexMode set.\n") ;
                                    }
                                    alen = 0 ;
                                }
                                if (alen == 1)
                                    alen = ((SerBuf[0] | 0x20) == 'e')?2:0;
                                if (alen == 0)
                                    alen = ((SerBuf[0] | 0x20) == 's')?1:0;
                            }
                            SendCom(SerBuf) ;
                            if (SADAsk) {
                                ConOut(" command received.\nPlease quit STerm and launch Wack TARGET 0 command.\nPress 'R' key to reset computer.\nPress any other key to abort\n") ;
                            }
                        }
                        ReadIO->IOSer.io_Command = CMD_READ;
                        ReadIO->IOSer.io_Data = (APTR)SerBuf;
                        ReadIO->IOSer.io_Length = 1;
                        SendIO((struct IORequest *)ReadIO);
                    }
                }
                /* Console stuff */
                if (signal & ConSig) {
                    while (crep = (struct IOStdReq *)GetMsg(conport)) {
                        ConBuf[crep->io_Actual] = '\0' ;
                        if (!crep->io_Error) {
                            if (SADAsk) {
                                SADAsk = FALSE ;
                                if (!CheckIO((struct IORequest *)ReadIO))
                                    AbortIO((struct IORequest *)ReadIO) ;
                                WaitIO((struct IORequest *)ReadIO) ;

                                /* ['r', 'R']  means Reset */
                                if ((ConBuf[0] | 0x20) == 'r')
                                    SADReset() ;
                                else
                                    SADAbort() ;

                                ReadIO->IOSer.io_Command = CMD_READ;
                                ReadIO->IOSer.io_Data = (APTR)SerBuf;
                                ReadIO->IOSer.io_Length = 1;
                                SendIO((struct IORequest *)ReadIO);
                            }
                            if (ConBuf[0] == 0x9B) { /* CSI: do special work */
                                if (ConBuf[2] == '~') {
                                    if (ConBuf[1] == 0x3F)
                                        DisplayHelp() ;
                                    else if (ConBuf[1] >= 0x30 &&
                                            ConBuf[1] <= 0x37) {
                                        SetBaudRate(ConBuf[1]-0x30) ;
                                    }
                                    else if (ConBuf[1] == 0x38) {
                                        if (HexMode == 2) {
                                            ConOut("HexMode unset.\n") ;
                                            HexMode = 0 ;
                                        }
                                        else if (HexMode == 0) {
                                            ConOut("HexMode set.\n") ;
                                            HexMode = 1 ;
                                        }
                                        else {
                                            ConOut("Semi-HexMode set.\n") ;
                                            HexMode = 2 ;
                                        }
                                    }
                                    else if (ConBuf[1] == 0x39) {
                                        if (RawMode) {
                                            ConOut("RawMode unset.\n") ;
                                            RawMode = FALSE ;
                                        }
                                        else {
                                            ConOut("RawMode set.\n") ;
                                            RawMode = TRUE ;
                                        }
                                    }
                                }
                            }
                            ConBuf[crep->io_Actual] = '\0' ;
                            WriteIO->IOSer.io_Command = CMD_WRITE;
                            WriteIO->IOSer.io_Data = (APTR)ConBuf;
                            WriteIO->IOSer.io_Length = strlen(ConBuf);
                            DoIO((struct IORequest *)WriteIO);

                            Write(msgout, ConBuf, strlen(ConBuf)) ;

                            conreq->io_Command = CMD_READ;
                            conreq->io_Data = (APTR)ConBuf;
                            conreq->io_Length = 512;
                            SendIO((struct IORequest *)conreq);
                        }
                    }
                }
            } while (!fin) ;
            CloseConsole(0) ;
        }
        CloseDevs(0) ;
    }
}


/*
 * InitDevs
 * Alloc serial resources
 * return TRUE for success
 */
BOOL InitDevs(VOID)
{

    if (SerialPort = (struct MsgPort *)CreateMsgPort()) {
        SerSig = (1L << SerialPort->mp_SigBit);
    }
    else
        return CloseDevs(1);
    if ((ReadIO = (struct IOExtSer *)
            CreateIORequest((struct MsgPort *)SerialPort, sizeof(struct IOExtSer))) == NULL)
        return CloseDevs(2);

    ReadIO->io_SerFlags = SERF_SHARED;
    if (Error = OpenDevice(SERIALNAME, 0, (struct IORequest *)ReadIO, 0))
        return CloseDevs(3);

    if ((WriteIO = (struct IOExtSer *)
            CreateIORequest((struct MsgPort *)SerialPort, sizeof(struct IOExtSer))) == NULL)
        return CloseDevs(4);
    CopyMem(ReadIO, WriteIO, sizeof(struct IOExtSer)) ;

    ReadIO->IOSer.io_Command = CMD_READ; /* Start reading serial */
    ReadIO->IOSer.io_Data = (APTR)SerBuf;
    ReadIO->IOSer.io_Length = 1;
    SendIO((struct IORequest *)ReadIO);

    return TRUE;
}


/*
 * CloseDevs
 * Frees serial resources
 * return TRUE if called with level = 0 (no problem)
 */
BOOL CloseDevs(WORD level)
{
    if (level)
        Printf("devs:%ld\n", level);
    switch (level) {
        case 0:
            DeleteIORequest((struct IORequest *)WriteIO) ;
        case 4:
            CloseDevice((struct IORequest *)ReadIO);
        case 3:
            DeleteIORequest((struct IORequest *)ReadIO);
        case 2:
            DeleteMsgPort(SerialPort);
        case 1:
            return (BOOL)(level?FALSE:TRUE);
    }
}

/*
 * InitConsole
 * Alloc console structures
 * return TRUE for success
 */
BOOL InitConsole(VOID)
{
STRPTR msgtitle = "CON:20/170/600/30/Echo Console" ;

    msgout = Open(msgtitle, MODE_OLDFILE) ;
    if (!msgout)
        return CloseConsole(1) ;

    win = OpenWindowTags(NULL,
    WA_Left, 20,
    WA_Top, 20,
    WA_Width, 600,
    WA_Height, 140,
    WA_MaxWidth, 640,
    WA_MaxHeight, 200,
    WA_Title, "STerm V1.0 ©1994, Dominique Lorre -- Help key helps.",
    WA_CloseGadget, TRUE,
    WA_SizeGadget, TRUE,
    WA_DepthGadget, TRUE,
    WA_DragBar, TRUE,
    WA_Activate, TRUE,
    WA_SimpleRefresh, TRUE,
    WA_IDCMP, IDCMP_CLOSEWINDOW,
    TAG_DONE) ;
    if (!win)
        return CloseConsole(2) ;

    WinSig = 1 << win->UserPort->mp_SigBit ;

    conport = CreateMsgPort() ;
    if (!conport)
        return CloseConsole(3) ;

    ConSig = 1 << conport->mp_SigBit ;

    conreq = CreateStdIO(conport) ;
    if (!conreq)
        return CloseConsole(4) ;

    outreq = CreateStdIO(conport) ;
    if (!outreq)
        return CloseConsole(5) ;

    conreq->io_Data = win ;
    Error = OpenDevice("console.device", 3, (struct IORequest *)conreq, 0) ;
    if (Error)
        return CloseConsole(6) ;
    *outreq = *conreq ;

    conreq->io_Command = CMD_READ; /* Starts reading console */
    conreq->io_Data = (APTR)ConBuf;
    conreq->io_Length = 512;
    SendIO((struct IORequest *)conreq);
    return TRUE ;
}

/*
 * CloseConsole
 * Frees console structures
 * return TRUE if called with level = 0 (no problem)
 */
BOOL CloseConsole(WORD level)
{
    if (level)
        Printf("console : %ld\n", level) ;
    switch (level) {
    case 0:
        CloseDevice((struct IORequest *)conreq) ;
    case 6:
        DeleteStdIO(outreq) ;
    case 5:
        DeleteStdIO(conreq) ;
    case 4:
        DeleteMsgPort(conport) ;
    case 3:
        CloseWindow(win) ;
    case 2:
        Close(msgout) ;
    case 1:
        return (BOOL)(level?FALSE:TRUE) ;
    }
}

/*
 * SADCheckVersion
 * Checks if SAD is running on V39 or V40+
 * return TRUE for success
 */
BOOL SADCheckVersion(void)
{
BOOL acked = FALSE ;
long i, length ;

    SADSendCmd(ReadWordCmd, 6) ;
    ReadIO->IOSer.io_Command = SDCMD_QUERY ;
    DoIO((struct IORequest *)ReadIO) ;
    length = ReadIO->IOSer.io_Actual ;

    if (!ReadIO->IOSer.io_Error) {
        ReadIO->IOSer.io_Command = CMD_READ;
        ReadIO->IOSer.io_Data = (APTR)SerBuf;
        ReadIO->IOSer.io_Length = length;
        DoIO((struct IORequest *)ReadIO);
    }

    /* This is a "defensive programming" loop. SAD seems to clear the
       serial buffers before replying.
     */
    if (!ReadIO->IOSer.io_Error) {
        for (i=0; i<length-1 && !acked ; i++) {
            acked = SerBuf[i] == 0x00 &&
                (SerBuf[i+1] == 0x05 || SerBuf[i+1] == 0x06) ;
        }
    }
    if (acked) {
        SADV39 = (SerBuf[i] == 0x06) ;
    }
    else
        ConOut("Unable to contact SAD: SAD Link lost\n") ;
    return acked ;
}

/*
 * SADReset
 * Send Reset Cmd to SAD
 */
void SADReset(void)
{
UBYTE cn ;

    if (SADCheckVersion()) {
        cn = SADV39?0x0F:0x10 ;
        ResetCmd[1] = cn ;
        SADSendCmd(ResetCmd, 6) ; /* Unable to check ack here */
        ConOut("Target Computer should be rebooting now ...\n") ;
    }
}

/*
 * SADAbort
 * Sends Abort Cmd to SAD
 */
void SADAbort(void)
{
UBYTE cn ;

    if (SADCheckVersion()) {
        cn = SADV39?0x07:0x08 ;
        AbortCmd[1] = cn ;
        SADSendCmd(AbortCmd, 6) ;
        if (!SADAckCmd(0x08))
            ConOut("Unable to Abort: SAD Link lost\n") ;
        else
            ConOut("Target Computer now has regained control...\n") ;
    }
}

/*
 * SADSendCmd
 * Sends command to serial with a little delay to allow ack checking
 */
void SADSendCmd(STRPTR cmd, LONG len)
{
    WriteIO->IOSer.io_Command = CMD_WRITE ;
    WriteIO->IOSer.io_Data = (APTR)cmd;
    WriteIO->IOSer.io_Length = len ;
    DoIO((struct IORequest *)WriteIO) ;
    Delay(10) ;                         /* Give time to Time */
}

/*
 * SADAckCmd
 * Checks if command identified by ack has succeeded
 * returns TRUE for success
 */
BOOL SADAckCmd(UBYTE ack)
{
BOOL acked = FALSE ;
long i, length ;

    ReadIO->IOSer.io_Command = SDCMD_QUERY ; /* How many bytes in serial ? */
    DoIO((struct IORequest *)ReadIO) ;
    length = ReadIO->IOSer.io_Actual ;
    if (!ReadIO->IOSer.io_Error) {
        ReadIO->IOSer.io_Command = CMD_READ; /* Read them */
        ReadIO->IOSer.io_Data = (APTR)SerBuf;
        ReadIO->IOSer.io_Length = length;
        DoIO((struct IORequest *)ReadIO);

    }
    if (!ReadIO->IOSer.io_Error) {
        /* This is a "defensive programming" loop. SAD seems to clear the
           serial buffers before replying.
         */
        for (i=0; i<length-1 && !acked ; i++) { /* Check if ack is inside */
            acked = SerBuf[i] == 0x00 && (SerBuf[i+1] == ack) ;
        }
    }
    return acked ;
}

/*
 * ConOut
 * Sends msg to Serial Console
 */
void ConOut(STRPTR msg)
{
static UBYTE selon[5] = {0x9B, ';', '3', '3', 'm' };
static UBYTE seloff[5] = {0x9B, ';', '3', '1', 'm' };
    outreq->io_Command = CMD_WRITE;
    outreq->io_Data = selon ;
    outreq->io_Length = 5 ;
    DoIO((struct IORequest *)outreq);
    outreq->io_Data = msg ;
    outreq->io_Length = strlen(msg) ;
    DoIO((struct IORequest *)outreq);
    outreq->io_Data = seloff;
    outreq->io_Length = 5 ;
    DoIO((struct IORequest *)outreq);
}

void SetBaudRate(long Baud)
{
    WriteIO->IOSer.io_Command = CMD_RESET;
    DoIO((struct IORequest *)WriteIO);
    WriteIO->IOSer.io_Command = SDCMD_SETPARAMS;
    WriteIO->io_SerFlags = SERF_RAD_BOOGIE | SERF_SHARED;
    WriteIO->io_ExtFlags = 0;
    WriteIO->io_Baud = rate[Baud];
    WriteIO->io_RBufLen = 512;
    WriteIO->io_ReadLen = 8;
    WriteIO->io_WriteLen = 8;
    WriteIO->io_StopBits = 1;
    DoIO((struct IORequest *)WriteIO);
    ConOut("Rate set to ") ;
    ConOut(srate[Baud]) ;
    ConOut(" bauds.\n") ;
}

void DisplayHelp(void)
{
    ConOut("STerm V1.0, ©1995, Dominique Lorre\n") ;
    ConOut("Click window close gadget to quit.\n") ;
    ConOut("Serial rates :\n") ;
    ConOut("\t F1:   110 bauds\n") ;
    ConOut("\t F2:   300 bauds\n") ;
    ConOut("\t F3:  1200 bauds\n") ;
    ConOut("\t F4:  2400 bauds\n") ;
    ConOut("\t F5:  4800 bauds\n") ;
    ConOut("\t F6:  9600 bauds\n") ;
    ConOut("\t F7: 19200 bauds\n") ;
    ConOut("\t F8: 31250 bauds\n") ;
    ConOut("\t F9: Toggle Hex Mode\n") ;
    ConOut("\tF10: Toggle Raw Mode\n") ;
}

void SendCom(STRPTR s)
{
static char hexa[3] = {'$', '0', '0'};

    outreq->io_Command = CMD_WRITE;
    if (!HexMode || ((HexMode == 2) && s[0] >= 0x20 && s[0] < 0x7F)) {
        outreq->io_Data = (APTR)s ;
        outreq->io_Length = 1;
        DoIO((struct IORequest *)outreq);
    }
    else {
        hexa[1] = (s[0] & 0xF0) >> 4 ;
        hexa[2] = s[0] & 0x0F ;
        if (hexa[1] > 9)
            hexa[1] += 'A' - 10 ;
        else
            hexa[1] += '0' ;
        if (hexa[2] > 9)
            hexa[2] += 'A' - 10 ;
        else
            hexa[2] += '0' ;
        outreq->io_Data = (APTR)hexa ;
        outreq->io_Length = 3 ;
        DoIO((struct IORequest *)outreq);
    }
}
