/*
 * stty.c
 *
 * Set terminal parameters
 */

#pragma stacksize 1280
#pragma optimize 9

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/ioctl.h>
#include <gno/gno.h>
#include <unistd.h>

typedef struct opts {
    char *name;
    int item;
} opts_s;

/* B0 - B57600 are defined before these others */
#define LCRTERA_ON      16
#define LCRTERA_OFF     17
#define LCTLECH_ON      18
#define LCTLECH_OFF     19
#define CRMOD_ON        20
#define CRMOD_OFF       21
#define ECHO_ON         22
#define ECHO_OFF        23
#define RAW_ON          24
#define RAW_OFF         25
#define CBREAK_ON       26
#define CBREAK_OFF      27
#define SUSP_C          28
#define STOP_C          29
#define START_C         30
#define QUIT_C          31
#define ERASE_C         32
#define INTR_C		33
#define EOF_C		34

char *baudtbl[] = {
"0",
"50",
"75",
"110",
"134.5",
"150",
"57600",
"300",
"600",
"1200",
"1800",
"2400",
"4800",
"9600",
"19200",
"38400"};

opts_s options[] = {
    "75",	B75,
    "110",	B110,
    "134",	B134,
    "150",	B150,
    "300",	B300,
    "600",	B600,
    "1200",	B1200,
    "1800",	B1800,
    "2400",	B2400,
    "4800",	B4800,
    "9600",	B9600,
    "19200",	B19200,
    "38400",	B38400,
    "57600",	B57600,
    "lcrtera", 	LCRTERA_ON,
    "-lcrtera", LCRTERA_OFF,
    "lctlech",	LCTLECH_ON,
    "-lctlech",	LCTLECH_OFF,
    "crmod",	CRMOD_ON,
    "-crmod",	CRMOD_OFF,
    "echo",	ECHO_ON,
    "-echo",	ECHO_OFF,
    "raw",	RAW_ON,
    "-raw",	RAW_OFF,
    "cbreak",	CBREAK_ON,
    "-cbreak",	CBREAK_OFF,
    "susp",	SUSP_C,
    "stop",	STOP_C,
    "start",	START_C,
    "quit",	QUIT_C,
    "erase",	ERASE_C,
    "intr",	INTR_C,
    "eof",	EOF_C,
    "",		0
};

int lookup(char *s)
{
int i;

    for (i = 0; options[i].item; i++) {
        if (!strcmp(options[i].name,s)) return (options[i].item);
    }
    return 0;
}

void usage(void)
{
    fprintf(stderr,"usage: stty [ option ]...\n"
    		"\toption: [-]raw,[-]echo,[-]cbreak,[baud]\n"
    		"\toption c: intr, susp, stop, start, eof, erase\n"
		"\t\twhere c is ^X or \\0OCTAL or \\xHEX\n");
    exit(1);
}

char parsechar(char *s)
{
int x;

    if (s[0] == '^') {
	if (strlen(s) != 2) usage();
	if (s[1] == '?') return 0x7f;
        else return toupper(s[1])-64;
    }
    else if (s[0] == '\\') {
	    if (isdigit(s[1]) && (s[0] != 0))
        	sscanf(s+1,"%d",&x);
        else if (toupper(s[1]) == 'X')
	        sscanf(s+2,"%x",&x);
        else if (s[1] == '0')
	        sscanf(s+1,"%o",&x);
        else usage();
        printf("char: %d\n",x);
        return x;
    }
    if (strlen(s) != 1) usage();
    return s[0];
}

struct sgttyb sg;
struct tchars tc;
struct ltchars ltc;
struct winsize wz;
long localmode;

char *dash[] = {"","-"};

char *doctrl(char c)
{
static char ss[3] = "  ";

    if (c == 0x7F) {
    	ss[0] = '^';
	ss[1] = '?';
    }
    else if (c < 32) {
        ss[0] = '^';
        ss[1] = c + '@';
    }
    else {
        ss[0] = c;
        ss[1] = ' ';
    }
    return ss;
}

void printCurSettings(void)
{
    printf("old tty, speed %s baud, %d rows, %d columns\n",
    	baudtbl[sg.sg_ispeed & 0xF],wz.ws_row,wz.ws_col);
    printf("%seven %sodd %sraw %snl %secho %slcase %standem %stabs %scbreak\n",
    	dash[(sg.sg_flags & EVENP) == 0],
	dash[(sg.sg_flags & ODDP) == 0],
	dash[(sg.sg_flags & RAW) == 0],
	dash[(sg.sg_flags & NLDELAY) == 0],
	dash[(sg.sg_flags & ECHO) == 0],
	dash[(sg.sg_flags & LCASE) == 0],
	dash[(sg.sg_flags & TANDEM) == 0],
	dash[(sg.sg_flags & XTABS) == 0],
	dash[(sg.sg_flags & CBREAK) == 0]);
    printf("%stilde %sflusho %slitout %spass8 %snohang\n",
	dash[(localmode & LTILDE) == 0],
	dash[(localmode & LFLUSHO) == 0],
	dash[(localmode & LLITOUT) == 0],
	dash[(localmode & LPASS8) == 0],
	dash[(localmode & LNOHANG) == 0]);
    printf("%spendin %snoflsh\n",
	dash[(localmode & LPENDIN) == 0],
	dash[(localmode & LNOFLSH) == 0]);

    printf("erase  kill   werase rprnt  flush  lnext  susp   intr   quit   stop   eof\n");
    printf("%-7s",doctrl(sg.sg_erase));
    printf("%-7s",doctrl(sg.sg_kill));
    printf("%-7s",doctrl(ltc.t_werasc));
    printf("%-7s",doctrl(ltc.t_rprntc));
    printf("%-7s",doctrl(ltc.t_flushc));
    printf("%-7s",doctrl(ltc.t_lnextc));
    printf("%2s",doctrl(ltc.t_suspc));
    printf("/%2s  ",doctrl(ltc.t_dsuspc));
    printf("%-7s",doctrl(tc.t_intrc));
    printf("%-7s",doctrl(tc.t_quitc));
    printf("%2s",doctrl(tc.t_stopc));
    printf("/%2s  ",doctrl(tc.t_startc));
    printf("%-7s\n",doctrl(tc.t_eofc));
}

int main(int argc, char *argv[])
{
int i,item;

    ioctl(STDIN_FILENO,TIOCGETP,&sg);
    ioctl(STDIN_FILENO,TIOCGETC,&tc);
    ioctl(STDIN_FILENO,TIOCGLTC,&ltc);
    ioctl(STDIN_FILENO,TIOCLGET,&localmode);
    ioctl(STDIN_FILENO,TIOCGWINSZ,&wz);
    if (argc < 2) {
    	printCurSettings();
        exit(0);
    }

    for (i = 1; i < argc;) {
      switch (item = lookup(argv[i])) {

        case INTR_C:
	        tc.t_intrc = parsechar(argv[i+1]);
	        i++;
        	break;
	case SUSP_C:
	        ltc.t_suspc = parsechar(argv[i+1]);
	        i++;
        	break;
        case STOP_C:
            	tc.t_stopc = parsechar(argv[i+1]);
	        i++;
        	break;
	case START_C:
	        tc.t_startc = parsechar(argv[i+1]);
	        i++;
        	break;
        case QUIT_C:
	        tc.t_quitc = parsechar(argv[i+1]);
	        i++;
        	break;
	case ERASE_C:
		sg.sg_erase = parsechar(argv[i+1]);
		i++;
		break;
	case EOF_C:
		tc.t_eofc = parsechar(argv[i+1]);
		i++;
		break;
	case ECHO_ON:
	        sg.sg_flags |= ECHO;
        	break;
        case ECHO_OFF:
	        sg.sg_flags &= ~ECHO;
        	break;
        case RAW_OFF:
        	sg.sg_flags &= ~RAW;
        	break;
        case RAW_ON:
	        sg.sg_flags |= RAW;
        	break;
        case CBREAK_OFF:
        	sg.sg_flags &= ~CBREAK;
        	break;
        case CBREAK_ON:
	        sg.sg_flags |= CBREAK;
        	break;
        case CRMOD_OFF:
        	sg.sg_flags &= ~CRMOD;
        	break;
        case CRMOD_ON:
	        sg.sg_flags |= CRMOD;
        	break;
        case LCTLECH_ON:
        	localmode |= LCTLECH;
	        break;
	case LCTLECH_OFF:
		localmode &= ~LCTLECH;
        	break;
        case LCRTERA_ON:
        	localmode |= LCRTERA;
	        break;
	case LCRTERA_OFF:
		localmode &= ~LCRTERA;
		break;
        default:
        	if ((item != 0) && (item <= B38400)) {
	            sg.sg_ispeed = item;
	            sg.sg_ospeed = item;
                } else usage();
                break;
      }
      i++;
    }
    ioctl(STDIN_FILENO,TIOCSETP,&sg);
    ioctl(STDIN_FILENO,TIOCSETC,&tc);
    ioctl(STDIN_FILENO,TIOCSLTC,&ltc);
    ioctl(STDIN_FILENO,TIOCLSET,&localmode);
}
