/*  io.c */

#ifdef AMIGA
/* Compile with -HPreHeader.q to get "less.h"! */
#else
#include "less.h"
#endif

#include <ctype.h>
#include <string.h>
#include <intuition/intuition.h>
#include <dos.h>


#undef TRUE
#undef FALSE

extern int sigs;

static struct  ConCom     *tty = NULL;

public int     nrow = 0;         /* Terminal size, rows.         */
public int     ncol;             /* Terminal size, columns.      */

static struct Window *LessWindow;

int Wind_Spec[4] = { 0, 0, 0, 0 };
/* User-supplied option specifying window size/position:
 *
 * [0]: Left edge X - coord.
 * [1]: Top edge Y - coord.
 * [2]: Right edge X - coord.
 * [3]: Bottom edge Y - coord.
 *
 * If element 2 or 3 is negative, it is taken to be a relative value to be
 * subtracted from the maximum screen size.  If the resulting window
 * would be smaller than the minimum, it is quietly enlarged to the
 * minimum, by expanding to the lower right as far as possible, and then
 * moving the window toward the upper left if necessary.
 */
#define W_MINWIDTH 200
#define W_MINHEIGHT 60

extern char version[];

/* Prototypes for functions defined in io.c */

static struct ConCom *OpenConsole __PROTO((struct Window *window));
static void CloseConsole __PROTO((struct ConCom *tty));
static void StartTtyTimer __PROTO((void));
static void StartConRead __PROTO((struct ConCom *tty));
static int ConGetC __PROTO((struct ConCom *tty));
static int ConLookC __PROTO((struct ConCom *tty));
static void ConWrite __PROTO((struct ConCom *tty,
                              char *data,
                              int length));



/****************************************************************/
/* Code to open a console on an existing Intuition window.
   See RKM Libraries & Devices, pp 277 ff
*/

#include <exec/memory.h>
#include <devices/timer.h>

extern struct GfxBase *GfxBase;
extern struct IOStdReq * CreateStdIO();
extern struct MsgPort *CreatePort();


struct ConCom
{
    struct IOStdReq *ConWriteReq; /* I/O write request */
    struct IOStdReq *ConReadReq;  /* I/O read request */
    struct MsgPort *RdReplyPort;  /* pointer to ReplyPort for console read */
    struct timerequest *TimerMsg; /* If != NULL, timer is open */
};


static struct ConCom *OpenConsole ( struct Window *window );
static void CloseConsole ( struct ConCom *tty );
static void StartConRead ( struct ConCom *tty );
static int ConGetC ( struct ConCom *tty );
static int ConLookC ( struct ConCom *tty );
static void ConWrite ( struct ConCom *c, char *data, int length );

static struct ConCom *OpenConsole (struct Window *window)
{
    struct ConCom *tty;
    struct MsgPort *WrReplyPort, *TimerPort;

    if ( tty = (struct ConCom *)
        AllocMem(sizeof(struct ConCom), MEMF_CLEAR | MEMF_PUBLIC) )
    {

        if ( (WrReplyPort = CreatePort(0, 0)) /* reply port for write */

            && (tty->RdReplyPort = CreatePort(0, 0)) /* reply port for read */

            && (tty->ConWriteReq = CreateStdIO ( WrReplyPort ))

            && (tty->ConReadReq = CreateStdIO ( tty->RdReplyPort)) )
        {
            tty->ConWriteReq->io_Data = (APTR) window;
            tty->ConWriteReq->io_Length = sizeof(struct Window);

            if ( !(OpenDevice ( "console.device", 0, tty->ConWriteReq, 0 )) )
            {
                tty->ConReadReq->io_Device = tty->ConWriteReq->io_Device;
                tty->ConReadReq->io_Unit = tty->ConWriteReq->io_Unit;

                /* Try to set up regular interrupts for ^C check */
                TimerPort = CreatePort(NULL, 0);
                if (
                    TimerPort
                    && ( tty->TimerMsg = (struct timerequest *)
                        CreateExtIO(TimerPort, sizeof(struct timerequest)) )
                    && !OpenDevice (TIMERNAME, UNIT_VBLANK,
                        (struct IORequest *)(tty->TimerMsg), 0)
                   )
                    return tty; /* All ok! */
                /* Main console ports ok, but no timer.  We'll live
                   without it...
                */
                if ( TimerPort ) DeletePort ( TimerPort );
                if ( tty->TimerMsg )
                    DeleteExtIO( (struct IORequest *)(tty->TimerMsg),
                        (long)sizeof(struct timerequest) );
                tty->TimerMsg = NULL;
                return tty;
            }
        }

        /* If get here, something went wrong */
        if ( tty->ConReadReq ) DeleteStdIO(tty->ConReadReq);
        if ( tty->RdReplyPort ) DeletePort(tty->RdReplyPort);
        if ( tty->ConWriteReq ) DeleteStdIO(tty->ConWriteReq);
        if ( WrReplyPort ) DeletePort(WrReplyPort);
    }
    if ( tty )
        FreeMem(tty, sizeof(struct ConCom));
    return NULL;

}

static void CloseConsole (struct ConCom *tty)
{
    struct MsgPort *mp;

    AbortIO(tty->ConReadReq);    /* Abort any read in progress */
    CloseDevice(tty->ConWriteReq);   /* close console device */

    mp = tty->ConWriteReq->io_Message.mn_ReplyPort;

    DeleteStdIO(tty->ConWriteReq);
    DeletePort(mp);

    mp = tty->ConReadReq->io_Message.mn_ReplyPort;

    DeleteStdIO(tty->ConReadReq);
    DeletePort(mp);

    if (tty->TimerMsg)
    {
        AbortIO((struct IORequest *)(tty->TimerMsg));
        CloseDevice((struct IORequest *)(tty->TimerMsg));
        DeletePort ( tty->TimerMsg->tr_node.io_Message.mn_ReplyPort );
        DeleteExtIO ( (struct IORequest *)(tty->TimerMsg),
            (long)sizeof(struct timerequest) );
    }

    FreeMem(tty, sizeof(struct ConCom));
    return;
}

/*  Request a timer interrupt in 0.25 seconds.  Use this to poll for ^C */

static void StartTtyTimer (void)
{
    if ( !tty->TimerMsg ) return;
    tty->TimerMsg->tr_node.io_Command = TR_ADDREQUEST;
    tty->TimerMsg->tr_time.tv_secs = 0;
    tty->TimerMsg->tr_time.tv_micro = 250000L;
    SendIO ( (struct IORequest *)(tty->TimerMsg) );
}

static char buffer; /* buffer for incoming console character */

/* asynchronous console read request--must be called once before
   any ConGetC requests
*/

static void StartConRead (struct ConCom *tty)
{
    struct IOStdReq *conr;

    conr = tty->ConReadReq;
    conr->io_Command = CMD_READ;
    conr->io_Length = 1;
    conr->io_Data = (APTR) &buffer;
    SendIO(conr);   /* asynchronous posting of a read request */
}

/* Get a character from the console.
*/
static int ConGetC (struct ConCom *tty)
{
    struct MsgPort *mp, *tp;
    struct IOStdReq *rddata;
    int temp;
    ULONG ReadMsgBit, ClockMsgBit, MsgBits;

    mp = tty->RdReplyPort;    /* get the read reply port */
    ReadMsgBit = 1 << mp->mp_SigBit;
    if ( tty->TimerMsg )
    {
        tp = tty->TimerMsg->tr_node.io_Message.mn_ReplyPort;
        ClockMsgBit = 1 << tp->mp_SigBit;
    }
    else ClockMsgBit = 0;
    rddata = NULL;
    do /* Wait for a character.  Wake up periodically so that ^C works */
    {
        MsgBits = Wait(ReadMsgBit | ClockMsgBit);
        if ( MsgBits & ReadMsgBit )
            rddata = (struct IOStdReq *) GetMsg ( mp );
        if ( MsgBits & ClockMsgBit )
            if ( GetMsg (tp) )
            {
                StartTtyTimer();
                chkabort();
            }
    } while ( !rddata );

    /* We've got a character... */
    temp = buffer;   /* get the character */
    StartConRead ( tty ); /* set up next read */
    return temp;
}

/* See if a character is available at the console.
   If so, get it, else return -1.
*/
static int ConLookC (struct ConCom *tty)
{
    struct MsgPort *mp;
    struct IOStdReq *rddata;
    int temp;

    mp = tty->RdReplyPort;    /* get the read reply port */
    rddata = (struct IOStdReq *) GetMsg ( mp );
    if ( !rddata ) return -1;

    /* We've got a character... */
    temp = buffer;   /* get the character */
    StartConRead ( tty ); /* set up next read */
    return temp;
}

/* write a specified number of characters from a buffer to console device
*/
static void ConWrite (struct ConCom *tty, char *data, int length)
{
    struct IOStdReq *wrdata;

    wrdata = tty->ConWriteReq;
    wrdata->io_Command = CMD_WRITE;
    wrdata->io_Length = length;
    wrdata->io_Data = (APTR) data;
    DoIO(wrdata); /* waits until write completes before continuing */
}

/****************************************************************/



/*
 * This routine gets called once, to set up the
 * terminal channel.
 */
void ttopen (void)
{
        int wl, wt;   /* window upper left corner specification */
        static struct Screen __aligned WBScreen, *wbsdata;
        static int IsV2;
        static struct NewWindow NewLessWindow =
        {
            0, 0, 640, 200,     /* position & size (may be changed) */
            -1, -1,             /* pens */
            0,                  /* IDCMP */
            WINDOWDEPTH | WINDOWSIZING | WINDOWDRAG | WINDOWCLOSE | ACTIVATE
                | SMART_REFRESH | NOCAREREFRESH,
            NULL, NULL,         /* Gadgets, Checkmark */
            "Less 1.4Z:  h for help",
            NULL, NULL,         /* Use WB screen */
            W_MINWIDTH, W_MINHEIGHT, -1, -1,
            WBENCHSCREEN
        };

        if (tty) return;


        if (GfxBase =  /* V2.0 + ? */
            (struct GfxBase *)OpenLibrary("graphics.library", 36) )
        {
            CloseLibrary ( GfxBase );
            IsV2 = 1;
            if ( !(wbsdata = LockPubScreen("Workbench")) )
            {
                error ( "Can't find Workbench screen" );
                quit();
            }
        }
        else
        {
            IsV2 = 0;
            if ( !GetScreenData ( &WBScreen, sizeof(struct Screen),
                WBENCHSCREEN, NULL ) )
            {
                error ( "Can't find Workbench screen" );
                quit();
            }
            wbsdata = &WBScreen;
        }
        if ( (wl = Wind_Spec[0]) < 0 ) wl += wbsdata->Width;
        if ( wl < 0 ) wl = 0;
        if ( wl > wbsdata->Width )
            wl = wbsdata->Width - W_MINWIDTH;
        if ( (wt = Wind_Spec[1]) < 0 ) wt += wbsdata->Height;
        if ( wt < 0 ) wt = 0;
        if ( wt > wbsdata->Height )
            wt = wbsdata->Height - W_MINHEIGHT;
        if ( wl < 0 || wt < 0 )
        {
            error ( "Window won't fit on screen" );
            quit();
        }
        NewLessWindow.LeftEdge = wl;
        NewLessWindow.TopEdge = wt;

        NewLessWindow.Width = Wind_Spec[2];
        if ( NewLessWindow.Width <= 0 )
            NewLessWindow.Width += wbsdata->Width;
        if ( NewLessWindow.Width <= 0 )
            NewLessWindow.Width = 0;
        NewLessWindow.Height = Wind_Spec[3];
        if ( NewLessWindow.Height <= 0 )
            NewLessWindow.Height += wbsdata->Height;
        if ( NewLessWindow.Height <= 0 )
            NewLessWindow.Height = 0;

        if ( NewLessWindow.Width < W_MINWIDTH )
            NewLessWindow.Width = W_MINWIDTH;
        if ( NewLessWindow.Height < W_MINHEIGHT )
            NewLessWindow.Height = W_MINHEIGHT;

        if ( NewLessWindow.LeftEdge + NewLessWindow.Width > wbsdata->Width )
            NewLessWindow.Width = wbsdata->Width - NewLessWindow.LeftEdge;
        if ( NewLessWindow.TopEdge + NewLessWindow.Height > wbsdata->Height )
            NewLessWindow.Height = wbsdata->Height - NewLessWindow.TopEdge;
        if ( NewLessWindow.Width < W_MINWIDTH )
            NewLessWindow.LeftEdge = wbsdata->Width - W_MINWIDTH;
        if ( NewLessWindow.Height < W_MINHEIGHT )
            NewLessWindow.TopEdge = wbsdata->Height - W_MINHEIGHT;
        if ( NewLessWindow.LeftEdge < 0 || NewLessWindow.TopEdge < 0 )
        {
            error ( "Window won't fit on screen" );
            quit();
        }

        if (IsV2)
            UnlockPubScreen(NULL, wbsdata);
        if (!(LessWindow = (struct Window *) OpenWindow (&NewLessWindow)) )
        {
            error ( "Can't open Less window" );
            quit();
        }
        if (!(tty = OpenConsole(LessWindow)))
        {
            error ( "Can't open console device" );
            quit();
        }
        StartConRead ( tty );
        if ( tty->TimerMsg ) StartTtyTimer();
        /* enable report of window resizeing and close gadget */
        ConWrite(tty, "\x9b\x31\x32;11{", 7);
}

/* Request size of window information
*/
void getrowcol (void)
{
    static unsigned char buf[16], *p;

    if ( !tty) ttopen();
    ConWrite(tty, "\x9b\x30\x20\x71", 4); /* request current window size */
    p = buf;
    while ( (*p = ConGetC(tty)) != 0x9b ) /* nothing */;
    p++;
    do
    {
        *p = ConGetC(tty);
    } while ( p < &(buf[15]) && *p++ != '\x72' );
    *p = '\0';
    /* buf has "En;n;n;n|", where E is 0x9b, n is a decimal number */

    p = buf+1;
    do
    {
        if ( p = strchr ( p, ';' ) ) p++; /* skip window position */
        else break;
        if ( p = strchr ( p, ';' ) ) p++;
        else break;
        nrow = atoi ( p );   /* window height */
        if ( p = strchr ( p, ';' ) ) p++;
        else break;
        ncol = atoi ( p );   /* window width */
    }   while (0); /* just once! */

    /* arbitrary data integrity checks: */
    if ( nrow < 2 || nrow > 99 || ncol < 10 || ncol > 200 )
    {
        /* Window probably too small for the font chosen.  We may not
           be able to even write an error message in the window! */
        MyRequester ( "Screen/Font mismatch" );
        quit();
    }
}



/*
 * This function gets called just
 * before we go back home to the command interpreter.
 * On the Amiga it closes up the virtual terminal window.
 */
void ttclose (void)
{
        if (tty != (struct ConCom *) 0L) {
                CloseConsole(tty);
                CloseWindow(LessWindow);
        }
        tty = NULL;
        nrow = 0;
}


/*
 * Read a character from the terminal,
 * performing no editing and doing conditional echo
 * but interpretting window resize and close gadget reports
 */
int do_echo = 1; /* echo flag */

int ttgetc (void)
{
        unsigned char c;        /* must be unsigned! */


        while ( (c = ConGetC(tty)) == '\x9b' )
        {
            switch (c = ConGetC(tty))
            {
            case '1': /* raw input event */
                if ( (c = ConGetC(tty)) == '1' )
                    quit();  /* close gadget */
                else if ( c == '2' )  /* window resize */
                {
                    while ( (c = ConGetC(tty)) != '|') /* nothing */;
                    winch();
                }
                break;
            case '?': /* Help key */
                if ( (c = ConGetC(tty)) == '~' ) return 'h';
                break;
            case 'A':
            case 'T': /* Arrow up */
                c = 'b';
                break;
            case 'B':
            case 'S': /* Arrow down */
                c = ' ';
                break;
            case 'D': /* Arrow left */
                c = 'k';
                break;
            case 'C': /* Arrow right */
                c = '\r';
                break;
            case ' ': /* Shifted left or right */
                if ( (c = ConGetC(tty)) == 'A' ) c = 'u';
                else c = 'd';
                break;
            default:
                continue;
            }
            break;
        }
        if ( c == 3 )
        {
            sigs |= 01;
            psignals();
            /* no return */
        }
        if (do_echo)
                ttwrite(&c, 1);

        return ((int) c);
}

/*
 * Check for ^C in the tty window.  This routine will throw
 * away any characters waiting in the tty input buffer.  Returns when
 * there's nothing in the input queue or one of the following has been
 * recognized:
 *
 *  Close gadget    (exits)
 *  Window resize   (resets window and returns)
 *  ^C              (sets sigs and returns)
 */
int chk_sigs (void)
{
    int c;

    for (;;)
    {
        if ( (c = ConLookC(tty)) < 0 ) return sigs;

        switch ( c )
        {
        case '\x9b': /* raw input event */
            if ( (c = ConGetC(tty)) != '1' ) break; /* unexpected raw input */
            if ( (c = ConGetC(tty)) == '1' )
                quit();  /* close gadget */
            else if ( c == '2' )  /* window resize */
            {
                while ( (c = ConGetC(tty)) != '|') /* nothing */;
                winch();
                return sigs;
            }
            break;
        case 3:
            sigs |= 01;
            return sigs;
        }
    }
}



/*
 * Write a buffer of characters to the display.
 */
void ttwrite (char *buffer, int length)
{
        ConWrite ( tty, buffer, length );
}

/*
 * Write a string to the terminal
 */
void ttputs (char *s)
{
        ConWrite ( tty, s, strlen(s) );
}

/* fake termcap output */
/* This takes the place of the original tputs(x,y,z), using only the
   first parameter
 */
void Tputs (char *s)
{
        flush();
        if ( s ) ConWrite ( tty, s, strlen(s) );
}

/*
 * We may not have a window: display a message in a requester
 */
int MyRequester (char *s)
{
    static struct IntuiText __aligned
    Ok =
    {
        AUTOFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
        AUTOLEFTEDGE, AUTOTOPEDGE,
        AUTOITEXTFONT,
        "Ok!",
        AUTONEXTTEXT
    },
    IMsg =
    {
        AUTOFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
        AUTOLEFTEDGE, AUTOTOPEDGE + 20, /* Hope this is enough for default font */
        AUTOITEXTFONT,
        "",
        AUTONEXTTEXT
    },
    LessMsg =
    {
        AUTOFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
        AUTOLEFTEDGE, AUTOTOPEDGE,
        AUTOITEXTFONT,
        "Less 1.4Z:",
        &IMsg
    };

    IMsg.IText = s;
    return (int)AutoRequest(NULL, &LessMsg, &Ok, &Ok, NULL, NULL, 250, 80);
}
