/*
 *  WRITE.C
 *
 *  routines used to write to the screen
 *
 */

#include    "char.h"

/*---------  external function prototypes: ---------*/

                /* look for unused key buffer */
extern char *k_alloc (void);
                /* look for key buffer */
extern char *k_seek (void);
                /* write decimal integer to ring buffer */
extern void r_puti (char);
                /* write byte to ring buffer */
extern void r_write (char);
                /* clear selected part of the screen */
extern void rawclear (void);
                /* set the video mode */
extern void rawmode (void);
                /* move the cursor */
extern void rawmv (void);   
                /* scroll the screen up */
extern void rawscroll (void);
                /* output character as raw tty */
extern void rawtty (void);
                /* output character to screen */
extern void rawwrite (void);    


/*
 *  delimiters used for quoted characters as
 *  parameters of escape sequences
 */

#define DELIM1  '\''
#define DELIM2  '"'

/*
 *  characters that require special handling
 */

#define BEL '\007'
#define BS  '\010'
#define NL  '\012'
#define CR  '\015'
#define ESC '\033'

/*
 *  color codes
 */

#define BLUE    (01)
#define GREEN   (02)
#define RED (04)
#define CYAN    (BLUE | GREEN)
#define MAGENTA (BLUE | RED)
#define YELLOW  (GREEN | RED)
#define WHITE   (BLUE | GREEN | RED)

/*
 *  macro's for turning on and off attributes or designating
 *  a color as foreground or background
 */

#define ON(x)   (attrib |= (char) (x))
#define OFF(x)  (attrib &= (char) (~(x)))
#define FORE(x) (attrib |= (char) (x))
#define BACK(x) (attrib |= (char) ((x) << 4))
 
/*
 * we don't want to use the standard 'c' isdigit(); it's
 * either implemented as a function (we don't want to use
 * osmebody else's functions that might have unpleasant side
 * effects) or a macro invoking an array of values that
 * dictate what lexical properties a given character possesses
 * (a waste of precious memory)
 */

#define isdigit(x)  (((x) >= '0') && ((x) <= '9'))

/*
 *  w_write()
 *
 *  w_write() keeps track of actually getting stuff on the screen
 *  and moving the cursor around
 */

void    w_write()
    {
    switch (outchar)
        {
        case    CR:

            /* just set the column to 0 for a carriage return */
            
            curr.loc.col = 0;
            
            /* and fall through to the backspace handler */
            
        case    BS:
        
            /* decrement the current column unless at the left
             * margin */
             
            if (curr.loc.col)
                --curr.loc.col;
                
            /* move the cursor and that's it... */
            
            rawmv();
            break;
            
        default:
        
            /* first, write the character without
               moving the cursor */
            
            rawwrite();
            
            /* then, if we're not on the right margin, bump the
             * cursor right and that's it */
             
            if ((curr.loc.col + 1) <= max.loc.col)
                {
                ++curr.loc.col;
                rawmv();
                break;
                }
            
            /* but if we were at the right margin, check the wrap
             * flag; if it's clear, just return.  if not, execute
             * a carriage return (just set the column to zero -
             * we'll do a rawmv() call later), set the current
             * character to newline, and fall into the newline
             * routine */
             
            if (!wrap)
                break;
            curr.loc.col = 0;
            outchar = NL;
        case    NL:
        
            /* if we're not at the bottom of the screen, just bump
             * the line down and call rawmv() */
             
            if (++curr.loc.line < 25)
                {
                rawmv();
                break;
                }
            
            /* but if we were at the bottom (or somehow below!),
             * make sure we're on the bottom line.  If we're in
             * one of the CGA 80x25 text modes, do our fancy
             * assembly language scroll routine, else just let
             * the BIOS handle it */
             
            curr.loc.line = 24;
            if (video_mode == 2 || video_mode == 3)
                {
                rawscroll();
                break;
                }

        case    BEL:
            
            /* do a raw tty output; it handles the cursor movement
             * too */

            rawtty();
            break;
        }
    }


/*
 *  w_buffer()
 *
 *  w_buffer() writes a byte into the escape buffer.  silently
 *  overwrites the last byte in the buffer if we get that far.  it
 *  was either that or trash the new byte
 */

void    w_buffer(c)
char    c;
    {
    if (char_cnt == BUF_LEN)
        esc_buf[ BUF_LEN - 1 ] = c;
    else
        esc_buf[ char_cnt++ ] = c;
    }



/*
 *  w_cursor()
 *
 *  w_cursor() handles the cursor left, right, up and down
 *  functions.  bumps the value by the value of the 1st parameter
 *  in the escape sequence (if there isn't one, we put a 1 in for
 *  it) in the direction specified by the delta, until it hits the
 *  specified limit.  then execute the cursor move...
 */

void    w_cursor()
    {
    if (!char_cnt)
        esc_buf[ 0 ] = 1;
    while (*cur_val != limit)
        {
        *cur_val += delta;
        esc_buf[ 0 ]--;
        if (!esc_buf[ 0 ])
            break;
        }
    rawmv();
    }



/*
 *  w_putc()
 *
 *  w_putc() updates the parameters that might have changed since
 *  last time, then runs the character through the escape sequence
 *  state machine
 */

void    w_putc()
    {
        
    /* update parameters */
    
    max.loc.col = SCREEN_WIDTH - 1;
    cur_page = CURRENT_PAGE;
    curr.position = (PAGE_TABLE[ cur_page ]).position;
    if (curr.loc.col > max.loc.col)
        curr.loc.col = max.loc.col;
    if ((video_mode = CURRENT_MODE) == 7)
        video_address = MONOCHROME + SCREEN_OFFSET;
    else
        video_address = GRAPHIC + SCREEN_OFFSET;
        
    /* process the escape sequence state */

    switch (state)
        {
            
        case    HAVE_ESC:

            /* if we have an escape, we want a left bracket.
             * if we get it, change the state and return,
             * else reset back to the RAW state and fall
             * through */
         
            if (outchar == '[')
                {
                state = HAVE_LBRACE;
                break;
                }
            state = RAW;
            
        case    RAW:
        
            /* if it's an escape, change the stae, else output the
             * character */
            
            if (outchar == ESC)
                state = HAVE_ESC;
            else
                w_write();
            break;

        case    IN_NUMBER:
        
            /* if it's another digit, roll it into the value. else
             * the state falls back to HAVE_LBRACE, and we fall
             * through */
             
            if (isdigit(outchar))
                {
                tmp.value *= 10;
                tmp.value += outchar - '0';
                break;
                }
            else
                {
                state = HAVE_LBRACE;
                w_buffer(tmp.value);
                }

        case    HAVE_LBRACE:
        
            /* if we have a string delimiter, change the state and
             * save the delimiter */
             
            if (outchar == DELIM1 || outchar == DELIM2)
                {
                state = IN_STRING;
                tmp.delim = outchar;
                break;
                }

            /* else if it's 'punctuation', ignore it */
            
            if (outchar == ';' || outchar == '=' ||
                        outchar == '?')
                break;
                
            /* else if it's a digit, start a number and
               change the  state */
             
            if (isdigit(outchar))
                {
                state = IN_NUMBER;
                tmp.value = outchar - '0';
                break;
                }

            /* else it terminates the escape sequence, and 
             * identifies its purpose */
             
            switch (outchar)
                {
                case    'A':            /* cursor up */
                    limit = 0;
                    delta = (char) -1;
                    cur_val = &curr.loc.line;
                    w_cursor();
                    break;

                case    'B':            /* cursor down */
                    limit = 24;
                    delta = 1;
                    cur_val = &curr.loc.line;
                    w_cursor();
                    break;
                    
                case    'C':            /* cursor right */
                    limit = max.loc.col;
                    delta = 1;
                    cur_val = &curr.loc.col;
                    w_cursor();
                    break;
                    
                case    'D':            /* cursor left */
                    limit = 0;
                    delta = (char) -1;
                    cur_val = &curr.loc.col;
                    w_cursor();
                    break;
                    
                case    'H':
                case    'R':
                case    'f':
                
                    /* set cursor position: make sure there
                     * are at least 2 parameters stored,
                     * correct any out-of-range parameters,
                     * and execute the move.  if the
                     * character was 'R', fall through into
                     * the report position sequence */
                    
                    switch (char_cnt)
                        {
                        case    0:
                            w_buffer(1);
                        case    1:
                            w_buffer(1);
                        default:
                            break;
                        }

                    if (!esc_buf[ 0 ])
                        curr.loc.line = 1;
                    else if (esc_buf[ 0 ] > 25)
                        curr.loc.line = 25;
                    else
                        curr.loc.line = esc_buf[ 0 ];

                    if (!esc_buf[ 1 ])
                        curr.loc.col = 1;
                    else if (esc_buf[ 1 ] > max.loc.col + 1)
                        curr.loc.col = max.loc.col + 1;
                    else
                        curr.loc.col = esc_buf[ 1 ];

                    curr.loc.line--;
                    curr.loc.col--;
                    rawmv();

                    if (outchar != 'R') 
                        break;

                case    'n':
                    /* output the position; format is
                     * "\033[%.2d;%.2dR\015" */
                     
                    r_write(ESC);
                    r_write('[');
                    r_puti(curr.loc.line + 1);
                    r_write(';');
                    r_puti(curr.loc.col + 1);
                    r_write('R');
                    r_write(CR);
                    break;

                case    'J':
                    /* rawclear clears the screen from
                     * (curr.loc.line, curr.loc.col) to
                     * (max.loc.line, max.loc.col); so for
                     * clear screen, set the current
                     * position to the upper left hand
                     * corner of the screen, and the max
                     * line to the bottom of the screen */
                     
                    curr.loc.line = curr.loc.col = 0;
                    max.loc.line = 24;
                    rawclear();
                    break;
                    
                case    'K':
                    /* and clear to end of line is even
                     * simpler - just set the max line equal
                     * to the same line we're on */
                    
                    max.loc.line = curr.loc.line;
                    rawclear();
                    break;
                    
                case    'h':
                case    'l':
                    /* set and reset mode do the same thing
                     * unless the mode is 7.  easy */
                     
                    if (!char_cnt)
                        w_buffer(2);
                    if (esc_buf[ 0 ] > 7)
                        break;
                    if (esc_buf[ 0 ] == 7)
                        wrap = (char) (outchar == 'h');
                    else
                        rawmode();
                    break;
                    
                case    'm':
                    /* set graphic rendition - just do all
                     * the parameters and set/reset the
                     * appropriate bits in the attribute
                     * byte */
                    
                    while (char_cnt)
                        {
                        switch (esc_buf[ --char_cnt ])
                            {
                            case    0:
                                attrib = 0007; break;
                            case    1:
                                ON(010); break;
                            case    4:
                                OFF(07); ON(01); break;
                            case    5:
                                ON(0200); break;
                            case    7:
                                OFF(07); ON(0160);  break;
                            case    8:
                                OFF(0167);  break;
                            case    30:
                                OFF(07);    break;
                            case    31:
                                OFF(07); FORE(RED); break;
                            case    32:
                                OFF(07); FORE(GREEN); break;
                            case    33:
                                OFF(07); FORE(YELLOW); break;
                            case    34:
                                OFF(07); FORE(BLUE); break;
                            case    35:
                                OFF(07); FORE(MAGENTA); break;
                            case    36:
                                OFF(07); FORE(CYAN); break;
                            case    37:
                                OFF(07); FORE(WHITE); break;
                            case    40:
                                OFF(0160);  break;
                            case    41:
                                OFF(0160); BACK(RED); break;
                            case    42:
                                OFF(0160); BACK(GREEN); break;
                            case    43:
                                OFF(0160); BACK(YELLOW); break;
                            case    44:
                                OFF(0160); BACK(BLUE); break;
                            case    45:
                                OFF(0160); BACK(MAGENTA); break;
                            case    46:
                                OFF(0160); BACK(CYAN); break;
                            case    47:
                                OFF(0160); BACK(WHITE); break;
                            default:
                                break;
                            }
                        }
                    break;
                case    'p':
                
                    if (esc_buf[ 0 ])
                        {

    /* if the first parameter is not nul, then we're
       redefining a 'normal' key.  Clear the msb of
       keycheck to indicate this */

                        keycheck = (esc_buf[ 0 ]) &
                            0000377;

    /* check first to see if we've already allocated
       a buffer to this key; then if not, see if we have
       an unused buffer to hand out */

                        if (k_seek() || k_alloc())
                            {
                            k = 1;
                            kp->keystroke =
                                (esc_buf[ 0 ])
                                & 0000377;
                            }
                        else
                            break;
                        }
                    else
                        {

    /* first byte was nul - an extended key.  indicate
       by setting msb of keycheck to $FF */

                        keycheck = (esc_buf[ 1 ]) |
                            0177400;
                        if (k_seek() || k_alloc())
                            {
                            k = 2;
                            kp->keystroke =
                                (esc_buf[ 1 ])
                                | 0177400;
                            }
                        else
                            break;
                        }

    /* copy the parameters into the buffer, counting as we go */

                    for (*len = 0; (k < char_cnt) &&
                        (k < KEY_BUFLEN); ++*len)
                        *ptr++ = esc_buf[ k++ ];

                    break;
                    
                case    's':
                
                    /* save current position */
                    
                    saved.position = curr.position;
                    break;

                case    'u':
                
                    /* restore current position */
                    
                    curr.position = saved.position;
                    rawmv();
                    break;

                default:
                
                    /* anything else? discard the parameters
                     * and output the final character to the
                     * screen */
                     
                    w_write();
                    break;
                }
                
            /* finally, clear the buffer by resetting the count
               and fall back to the RAW state */
            
            char_cnt = 0;
            state = RAW;
            break;
            
        case    IN_STRING:
        
            /* finally, the IN_STRING case - if the character
             * isn't the delimiter we saved, then put it into
             * the buffer as it was received */
            
            if (outchar == tmp.delim)
                state = HAVE_LBRACE;
            else
                w_buffer(outchar);
            break;
        }
    }
