/* protocol.c
 *
 * Implementation of UW protocol - Host side
 *
 * Copyright (c) 1993,1994 by Ezra Story.  All rights reserved.  Permission to
 * copy this program is given provided that the copy is not sold and that
 * this copyright notice is included.
 */
static char rcsid[] = "$Id: protocol.c 1.6 1994/06/11 19:45:29 Ezra_Story Exp $";

#include "config.h"
#include <sys/types.h>
#ifdef SYSV
#include <sys/filio.h>
#endif
#include <sys/file.h>
#include <sys/ioctl.h>
#include <errno.h>
#ifdef STRING
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef SYSSELECT
#include <sys/select.h>
#endif
#include <stdio.h>
#include <sys/time.h>


#include "lists.h"
#include "uwproto.h"
#include "mydefs.h"

proto p_Startup();
proto p_ProcessInput();
proto p_SendData();
proto p_Exit();

void MyWrite();

#define AddCommand(x,y) {*y++ = P1_IAC; *y++ = x|P1_DIR_HTOC;}
#define PutCommand(x,y) {y[0] = P1_IAC; y[1] = x|P1_DIR_HTOC;}
#define META 0x80

int  pstate;
int  ttyx;
int  ttyy;
int  ttywin;

#define PS_CANPCL   1
#define PS_SETPCL   2
#define PS_TTY1     3
#define PS_TTY2     4
#define PS_TTY3     5
#define PS_TTY4     6

int  protocol;
WIN  *activeinwin;
int  activein;
int  activeout;
int  gotiac;
int  gotmeta;

p_Startup()
{
    /* send startup command */
    char cmdbuf[2];
    PutCommand(P1_FN_MAINT|P1_MF_ENTRY,cmdbuf);
    (void)MyWrite(cout,cmdbuf,2);

    pstate      = 0;
    protocol    = 0;
    activein    = 0;
    activeinwin = 0;
    activeout   = 0;
    gotiac      = 0;
    gotmeta     = 0;
}

/*
 * Gets input from stdin and dispatches it to the various
 * windows, and interprets commands for UW.
 */
p_ProcessInput()
{
    char     buf[WINBUFSZ+8];
    int      len;
    char     *b,*top;

    /* get whatever's there at the moment */
    len = read(cin,buf,WINBUFSZ);
    if (len <= 0) return;

    /* buffer parse loop */
    top = buf+len;
    for (b = buf; b < top; b++)
        {
        if (gotiac)
            {if (DoCommand(b)==0) continue;}
        else
            if (*b == P1_IAC)
                {
                gotiac = 1;
                continue;
                }

        if (gotmeta)
            {
            *b |= META;
            gotmeta = 0;
            }

        if (activeinwin)
            {
            if (activeinwin->ib == WINBUFSZ) BufstoPty();
            activeinwin->ibuf[activeinwin->ib++] = *b;
            }
        }

    /* dump any buffers filled to their pty's */
    BufstoPty();
}

DoCommand(b)
char *b;
{
    char c;
    char tbuf[20];
    char arg;

    gotiac = 0;
    c      = *b;

    if (pstate)
        {
        switch(pstate)
            {
            case PS_CANPCL:
                PutCommand(P1_FN_MAINT|P1_MF_SETPCL,tbuf);
                tbuf[2] = 0x20;
                protocol = 1;
                (void)MyWrite(cout,tbuf,3);
                OpenWindow(1,0);
                activeinwin = wins[activein = 1];
                break;
            case PS_SETPCL:
                protocol = c - 0x1f;
                OpenWindow(1,0);
                activeinwin = wins[activein = 1];
                break;
            case PS_TTY1:
                ttyx = 100 * (c - 20);
                gotiac = 1;
                pstate = PS_TTY2;
                return(0);
                break;
            case PS_TTY2:
                ttyx += c - 20;
                gotiac = 1;
                pstate = PS_TTY3;
                return(0);
                break;
            case PS_TTY3:
                ttyy = 100 * (c - 20);
                gotiac = 1;
                pstate = PS_TTY4;
                return(0);
                break;
            case PS_TTY4:
                ttyy += c - 20;
                TtyResize(wins[ttywin]->fd,ttyx,ttyy);
                break;
            default:
                break;
            }
        pstate = 0;
        return(0);
        }

    arg = c & P1_FN_ARG;
    if((c & P1_DIR) != P1_DIR_CTOH) return(0);
    switch (c & P1_FN)
        {
        case P3_FN_TTYSZ:
            ttywin = arg;
            pstate = PS_TTY1;
            gotiac = 1;
            break;

        case P1_FN_NEWW:
            if (protocol && arg && (OpenWindow(arg,0) == -1))
                {
                PutCommand(P1_FN_KILLW|arg,tbuf)
                (void)MyWrite(cout,tbuf,2);
                }
            break;

        case P1_FN_KILLW:
            if (protocol && arg && wins[arg]) CloseWindow(wins[arg]);
            if (wins[activein] == 0) {activein = 0; activeinwin = 0;}
            break;

        case P1_FN_ISELW:
            if (protocol && arg && wins[arg])
                activeinwin = wins[activein = arg];
            break;

        case P1_FN_META:
            if (protocol) gotmeta = 1;
            break;

        case P1_FN_CTLCH:
            if (protocol==0) break;
            switch (arg)
                {
                case P1_CC_IAC: *b=1; break;
                case P1_CC_XON: *b=17; break;
                case P1_CC_XOFF: *b=19; break;
                default: *b=0;
                }
            return(1);
            break;

        case P1_FN_MAINT:
            switch (arg)
                {
                case P1_MF_ENTRY:
                    break;

                case P1_MF_ASKPCL:
                    PutCommand(P1_FN_MAINT|P1_MF_CANPCL,tbuf);
                    tbuf[2] = 0x20;
                    (void)MyWrite(cout,tbuf,3);
                    break;

                case P1_MF_CANPCL:
                    pstate = PS_CANPCL;
                    gotiac = 1;
                    break;

                case P1_MF_SETPCL:
                    pstate = PS_SETPCL;
                    gotiac = 1;
                    break;

                case P1_MF_EXIT:
                    done(0,0);
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    return(0);
}

p_SendData(w)
WIN *w;
{
    fildes_t    fd = w->fd;
    int         len;
    char        buf[WINOUTSZ];
    char        bufb[OUTPUTSZ];
    int         x;
    char        *a,*b,*top;

    /* get whatever's there at the moment */
    len = read(fd,buf,wnoutsz);
    if ((len < 0) && (errno != EWOULDBLOCK))
        {
        if (activeinwin == w) {activein = 0; activeinwin = 0;}
        activeout = 0;
        PutCommand(P1_FN_KILLW|w->id,buf);
        (void)MyWrite(cout,buf,2);
        CloseWindow(w);
        return;
        }
    if (len <= 0) return;

    /* correct window? */
    if (w->id != activeout)
        {
        activeout = w->id;
        PutCommand(P1_FN_OSELW|activeout,bufb);
        (void)MyWrite(cout,bufb,2);
        }

    /* process data */
    a = buf;
    b = bufb;
    top = a + len;
    for(; a < top ; a++)
        {
        if (*a & META)
            {
            *a &= 0x7f;
            x = 1;
            }
        else
            x = 0;

        switch (*a)
            {
            case 1:
                if (x) AddCommand(P1_FN_META,b);
                AddCommand(P1_FN_CTLCH|P1_CC_IAC,b);
                break;
            case 19:
                if (x) AddCommand(P1_FN_META,b);
                AddCommand(P1_FN_CTLCH|P1_CC_XOFF,b);
                break;
            case 17:
                if (x) AddCommand(P1_FN_META,b);
                AddCommand(P1_FN_CTLCH|P1_CC_XON,b);
                break;
            default:
                if (x)
                    {
                    if (metaon) *a |= 0x80;
                    else AddCommand(P1_FN_META,b);
                    }
                *b++ = *a;
                break;
            }
        }

    x = b-bufb;
    (void)MyWrite(cout,bufb,x);


}


p_Exit()
{
/* do nothing for now */
}


void
MyWrite(fd, buf, len)
int fd, len;
char *buf;
{

    static int timedelay = 0;
    static struct timeval otv = {0,0};
    struct timeval tv;
    int diff;

    /* write and add to delay time */
    write(fd, buf, len);
    timedelay += micPerChar * len;

    if (timedelay > 50000)
        {
#ifdef SVR4
        gettimeofday(&tv);
#else
        gettimeofday(&tv,0);
#endif
        tv.tv_sec -= otv.tv_sec;
        tv.tv_usec -= otv.tv_usec;
        tv.tv_usec -= timedelay;
        if ((tv.tv_sec == 0)&&(tv.tv_usec < 0))
            {
            tv.tv_usec = -tv.tv_usec;
            if (tv.tv_usec > 1000000)
                {
                tv.tv_sec++;
                tv.tv_usec -= 1000000;
                }
            select(0, 0, 0, 0, &tv);
            }
#ifdef SVR4
        gettimeofday(&otv);
#else
        gettimeofday(&otv,0);
#endif
        timedelay = 0;
        }
}
