///////////
//
//  sttyl.c 
//
//      AUTHOR: Russell Lowke
//   CSCIE-215: Assignment Three
//
//
//       Usage: sttyl [-]icrnl        // map CR to NL (ala CRMOD)
//                    [-]onlcr        // map NL to CR-NL (ala CRMOD)
//                    [-]echo         // enable echoing
//                    [-]echoe        // visually erase chars
//                    [-]olcuc        // map lowercase to uppercase on output
//                    [-]tabs         // expand tabs on output
//                    [-]icano        // canonicalize input lines
//                    [-]icanon       // canonicalize input lines
//                    [-]isig         // enable signals INTR, QUIT, [D]SUSP
//                    [-]hupcl        // hang up on last close
//                    [-]cread        // enable receiver
//                    [-]brkint       // map BREAK to SIGINTR
//                    [-]inpck        // disable checking of parity errors
//                    [-]iexten       // enable FLUSHO and LNEXT
//
//                    intr  CHAR      // CHAR will send an interrupt signal
//                    erase CHAR      // CHAR will erase last character typed
//                    kill  CHAR      // CHAR will erase the current line
//                    start CHAR      // CHAR will restart output after stopping
//                    stop  CHAR      // CHAR will stop the output
//
//
// Description: Mimics the basic functionality of the Unix stty,
//              Prints or changes terminal characteristics.
//
//      Method: Reads args sent to main,
//              looks for each arg in data tables for flag args and Key args
//              gets information about arg from table
//              set flags OFF (&= ~) or  ON (|=) depending on arg
//              convert CHARs passed to ascii, reading '^' as ctrl and "^?" as del
//              set key arg to CHAR when valid
//              exit with "Unknown mode" for any invalid arg
//              if no args sent, print values of all vars in tables 
//              
//
//       Notes: sttyl only works properly in sh
//              '^' prefix means a ctrl CHAR
//              CHAR for delete is passed as "^?"
//              CHAR commands are best passed in quotes, ie  "^?"
//


// include header
#include                "sttyl.h"

//
//  tables

//
//  mFlagArgs    
//
//     struct of type flagTable
//     Holds the attribute, value, flag type and description,
//     for every argument flag accepted by sttyl
//
struct flagTable mFlagArgs[] = {
    {"icrnl" , ICRNL , INPUTFLAG  , "map CR to NL (ala CRMOD)"             },
    {"onlcr" , ONLCR , OUTPUTFLAG , "map NL to CR-NL (ala CRMOD)"          },
    {"echo"  , ECHO  , LOCALFLAG  , "enable echoing"                       },
    {"echoe" , ECHOE , LOCALFLAG  , "visually erase chars"                 },
    {"olcuc" , OLCUC , OUTPUTFLAG , "map lowercase to uppercase on output" },
    {"tabs"  , TAB3  , OUTPUTFLAG , "expand tabs on output"                },
    {"icano" , ICANON, LOCALFLAG  , "canonicalize input lines"             },
    {"icanon", ICANON, LOCALFLAG  , "canonicalize input lines"             },
    {"isig"  , ISIG  , LOCALFLAG  , "enable signals INTR, QUIT, [D]SUSP"   },
    {"hupcl" , HUPCL , CONTROLFLAG, "hang up on last close"                },
    {"cread" , CREAD , CONTROLFLAG, "enable receiver"                      },
    {"brkint", BRKINT, INPUTFLAG  , "map BREAK to SIGINTR"                 },
    {"inpck" , INPCK , INPUTFLAG  , "disable checking of parity errors"    },
    {"iexten", IEXTEN, LOCALFLAG  , "enable FLUSHO and LNEXT"              },
    { NULL   , 0     , 0          ,  NULL                                  }};


//
//  mKeyArgs
//
//     struct of type keyTable
//     Holds the attribute and value,
//     for every modifable key argument accepted by sttyl 
//
struct keyTable mKeyArgs[] = {
    {"intr"  , VINTR },
    {"erase" , VERASE},
    {"kill"  , VKILL },
    {"start" , VSTART},
    {"stop"  , VSTOP },
    { NULL   , 0     }};


//
//  main
//
//     loop through args passed into main via ac and av
//     record if arg has prefix '-'
//     check if arg is in flag table, if so set flag
//     check if arg is in key table, if so set key to next arg
//     
    
int main(int ac, char **av)
{
    struct termios info;                     // struct to hold termios info
    int    i,                                // general purpose counter
           switchIs,                         // indicates if flag is,
                                             // turning OFF (0)  or ON (1)
           item;                             // position of item in flagTable,
                                             //                  or keyTable
    char   ascii,                            // modified ascii of CHAR key
          *argName;                          // pointer to argument name
    
        
    if (tcgetattr(0, &info) < 0)             // load terminal arributes to info
        oops("Couldn't get termios");        // ...error
    

    for ( i = 1; i < ac; i++ ) {             // loop through args given to main
        
        ascii    =  0;                       // cleanup ascii and item vars
        
        switchIs = (av[i][0] == '-');        // switchIs to 0, or 1 
                                             //   depending on '-' operator
        
        argName  = &av[i][switchIs];         // pointer to argument name,
                                             //   excluding '-'
        
                                             // if argName has a flagIndex
        if ((item = flagIndex(argName)) != -1)
            setFlag(&info, item, switchIs);  // set flag [item] to [switchIs]
        
                                             // if argName has a keyIndex
                                             // and there is another argument
        else if ((item = keyIndex(argName)) != -1 
                    && i+1 < ac ) {
            
            ascii = getAscii(av[++i]);       // increment i and get ascii

            
            if (ascii)                       // if valid ascii
                info.c_cc[mKeyArgs[item].theValue] = ascii;   // set char key
            else
                fatal("Invalid argument");   // ...error
        } 
        else
            fatal("Unknown mode");           // ...error
    }
    
    tcsetattr(0, TCSANOW, &info);            // update termios

    if (ac == 1) 
        displaySettings(&info);              // if 1 arg then print
    
    return (0);
}



///////
//
//  flagIndex
//
//     return the index of attribute [str] in table mFlagArgs
//
//     args:  str          -- name of attribute being looked for
//
int flagIndex(char *str)
{
    int i;                                  // general purpose counter
    
    for ( i = 0; mFlagArgs[i].theValue ; i++ ) {        // loop through table
        if ( strcmp(mFlagArgs[i].attribute, str) == 0 )
            return (i);                                 // str found
    }

    return -1;                              // str not found
}


///////
//
//  keyIndex
//
//     return the index of attribute [str] in table mKeyArgs
//
//     args:  str          -- name of attribute being looked for
//
int keyIndex(char *str)
{
    int i;                                  // general purpose counter
    
    for ( i = 0; mKeyArgs[i].theValue ; i++ ) {        // loop through table
        if ( strcmp(mKeyArgs[i].attribute, str) == 0 )
            return (i);                                // str found
    }

    return -1;                              // str not found
}


///////
//
//  setFlag
//
//     set a flag in the [*info] struct to ON or OFF
//     two case blocks for switches,
//     flag type and arg value is read from table mFlagArgs
//
//     args: *info         --  pointer to struct being modifed.
//            item         --  index of arg in mFlagArgs table
//            switchIs     --  switch arg to OFF (0)  or ON (1)
//
void setFlag(struct termios *info, int item, int switchIs)
{
    if (switchIs) {
        
        // Switch flag OFF
        switch (mFlagArgs[item].flagType) {
            case INPUTFLAG:
                info->c_iflag &= ~mFlagArgs[item].theValue;
                break;
            case OUTPUTFLAG:
                info->c_oflag &= ~mFlagArgs[item].theValue;
                break;
            case CONTROLFLAG:
                info->c_cflag &= ~mFlagArgs[item].theValue;
                break;
            case LOCALFLAG:
                info->c_lflag &= ~mFlagArgs[item].theValue;
                break;
        }
    }
    else {
        
        // Switch flag ON
        switch (mFlagArgs[item].flagType) {
            case INPUTFLAG:
                info->c_iflag |= mFlagArgs[item].theValue;
                break;
            case OUTPUTFLAG:
                info->c_oflag |= mFlagArgs[item].theValue;
                break;
            case CONTROLFLAG:
                info->c_cflag |= mFlagArgs[item].theValue;
                break;
            case LOCALFLAG:
                info->c_lflag |= mFlagArgs[item].theValue;
                break;
            }
    }
}

    
///////
//
//  getAscii
//
//     return the char in [*str] as ascii
//     cntrl chars are identified by the prefix '^'
//     [*str] should never be more than 2 chars long
//     if [*str] is invalid or can't be read, return 0
//
//     args: *str     --  pointer to str to convert.
//
int getAscii(char *str)
{   
    int ascii = 0;
    
    if ( strlen(str) == 1 )                    // if arg length is 1
        ascii = str[0];                        // ...get ascii from 1st char
                
    else if ( strlen(str) == 2
                && str[0] == '^' ) {           // if arg length is 2 and '^'
                
        ascii = str[1];                        // ...get ascii from 2nd char
                
        if (ascii == '?')                      // convert "^?" to backspace
            ascii = 127;
        else {
            
            if (ascii >= 'a') 
                ascii = ascii - 'a' + 1;       // lower case to cntrl ascii
            else
                ascii = ascii - 'A' + 1;       // upper case to cntrl ascii

            if (ascii < 0 || ascii > 31)       // keep within ctrl chars
                ascii = 0;                     // ...bad value
        }
    }
    
    return ascii;
}


///////
//
//  displaySettings
//
//     print the showbaud and print the values of
//     all the args in both the mKeyArgs table
//                      and the mFlagArgs table
//
//     args: *info        --  pointer to struct being printed.
//
void displaySettings(struct termios *info)
{    
    int i;  
        
    showbaud( cfgetospeed( info ));                    // get and show baud rate
    printf("\n");                                      // new line

    // loop through all the values in mKeyArgs
    for ( i = 0; mKeyArgs[i].theValue ; i++ ) {
        printf("%s = ", mKeyArgs[i].attribute);        // print arg
        printASCII(info->c_cc[mKeyArgs[i].theValue]);  // print the key
    }
    printf("\n");                                      // new line
   
    // loop through all the values in mFlagArgs
    for ( i = 0; mFlagArgs[i].theValue ; i++ ) {
        printFlag( info, mFlagArgs[i].attribute,       // print arg
                         mFlagArgs[i].theValue,
                         mFlagArgs[i].flagType);
    }
    printf("\n");                                      // new line
}


///////
//
//  showbaud
//
//     prints the speed in english
//
//     args:  thespeed   --  value from cfgetospeed.
//
void showbaud( int thespeed )
{
        printf("speed ");

        // convert thespeed symbol
        switch ( thespeed ){
                case B300:      printf("300");        break;
                case B600:      printf("600");        break;
                case B1200:     printf("1200");       break;
                case B1800:     printf("1800");       break;
                case B2400:     printf("2400");       break;
                case B4800:     printf("4800");       break;
                case B9600:     printf("9600");       break;
                default:        printf("Fast");       break;
        }
        printf(" baud; ");
}


///////
//
//  printFlag
//
//     Search directory [dirName] looking for [fileName]. Matches are
//     printed using printf.  When a directory is found searchdir calls
//     itself passing that directory as the dirName. 
//
//     args: *info         --  pointer to struct being printed.
//           *attribute    --  attribute to print
//            theValue     --  value of attribute
//            flagType     --  flag type of attribute
//
void printFlag( struct termios *info,
                char           *attribute,
                int             theValue, 
                int             flagType)
{
    int infoValue;             // value of attribute stored in {*info]

    // get infoValue according to flagType
    switch (flagType) {
        case INPUTFLAG:
            infoValue = info->c_iflag;
            break;
        case OUTPUTFLAG:
            infoValue = info->c_oflag;
            break;
        case CONTROLFLAG:
            infoValue = info->c_cflag;
            break;
        case LOCALFLAG:
            infoValue = info->c_lflag;
            break;
    }

    // print attribute
    if (infoValue & theValue)
            printf("%s ", attribute);                  // attribute ON
    else
            printf("-%s ", attribute);                 // attribute OFF
}


///////
//
//  printASCII
//
//     print asci value as a char, using prefix '^' for ctrl chars
//     and "^?" for the delete char
//
//     args:  ascii        --  ascii value of char to print.
//
void printASCII(int ascii)
{
    if (ascii == 127)                                  
        printf("^?; ");                                // is delete
    else if (ascii > 0 || ascii < 32)                  
        printf("^%c; ", ascii -1 + 'A');               // is ctrl char
    else
        printf("%c; ", ascii);                         // is regular char
}


/////////
//
/* All the flags in the termios.h file
//  formatted for
// 
//  struct flaginfo { int   fl_value; char  *fl_name; };

//
// Input flags - software input processing
//
struct flaginfo inputFlags[] = {
                  {IGNBRK    , "ignore BREAK condition"},
                  {BRKINT    , "map BREAK to SIGINTR"},
                  {IGNPAR    , "ignore (discard) parity errors"},
                  {PARMRK    , "mark parity and framing errors"},
                  {INPCK     , "disable checking of parity errors"},
                  {ISTRIP    , "strip 8th bit off chars"},
                  {INLCR     , "map NL into CR"},
                  {IGNCR     , "ignore CR"},
                  {ICRNL     , "map CR to NL (ala CRMOD)"},
                  {IXON      , "enable output flow control"},
                  {IXOFF     , "enable input flow control"},
                  {IXANY     , "any char will restart after stop"},
                  {IUCLC     , "DUMMY VALUE Map upper to lower case on input"},
                  {IFLOW     , "enable output flow control"},
                  {ITANDEM   , "enable input flow control"},
                  {IMAXBEL   , "ring bell on input queue full"},
                  {0         ,  NULL }};
//
// Output flags - software output processing
//
struct flaginfo outputFlags[] = {
                     {OPOST     , "enable following output processing"},
                     {ONLCR     , "map NL to CR-NL (ala CRMOD)"},
                     {OLCUC     , "Map lower case to upper on output"},
                     {OCRNL     , "Map CR to NL on output"},
                     {ONOCR     , "No CR output at column 0"},
                     {ONLRET    , "NL performs CR function"},
                     {OFILL     , "Use fill characters for delay"},
                     {OFDEL     , "fill is DEL, else NUL"},
                     {NLDLY     , "\\n delay"},
                     {NL0       , "NL0"},
                     {NL1       , "NL1 tty 37"},
                     {NL2       , "NL2 vt05"},
                     {NL3       , "NL3"},
                     {TABDLY    , "horizontal tab delay"},
                     {TAB0      , "TAB0"},
                     {TAB1      , "TAB1 tty 37"},
                     {TAB2      , "TAB2"},
                     {TAB3      , "TAB3 expand tabs on output"},
                     {CRDLY     , "\\r delay"},
                     {CR0       , "CR0"},
                     {CR1       , "CR1 tn 300"},
                     {CR2       , "CR2 tty 37"},
                     {CR3       , "CR3 concept 100"},
                     {FFDLY     , "Form feed delay"},
                     {FF0       , "FF0"},
                     {FF1       , "FF1"},
                     {BSDLY     , "\\b delay"},
                     {BS0       , "BS0"},
                     {BS1       , "BS1"},
                     {VTDLY     , "vertical tab delay"},
                     {VT0       , "VT0"},
                     {VT1       , "VT1 tty 37"},
                     {ONLCRNL   , "ONLCRNL"},
                     {OXTABS    , "expand tabs to spaces"},
                     {ONOEOT    , "discard EOT's (^D) on output)"},
                     {0         ,  NULL }};                  
//
// Control flags - hardware control of terminal
//
struct flaginfo cntrFlags[] = {
                     {CSIZE     , "character size mask"},
                     {CSTOPB    , "send 2 stop bits"},
                     {CREAD     , "enable receiver"},
                     {PARENB    , "parity enable"},
                     {PARODD    , "odd parity, else even"},
                     {HUPCL     , "hang up on last close"},
                     {CLOCAL    , "ignore modem status lines"},
                     {CRTSCTS   , "RTS/CTS flow control"},
                     {0         ,  NULL }};

                     //{CS5       , "5 bits (pseudo)"},
                     //{CS6       , "6 bits"},
                     //{CS7       , "7 bits"},
                     //{CS8       , "8 bits"},

// 
// "Local" flags - dumping ground for other state
//
// Warning: some flags in this structure begin with
// the letter "I" and look like they belong in the
// input flag.
//
struct flaginfo localFlags[] = {
                     {ECHOE     , "visually erase chars"},
                     {ECHOK     , "echo NL after line kill"},
                     {ECHO      , "enable echoing"},
                     {ECHONL    , "echo NL even if ECHO is off"},
                     {ISIG      , "enable signals INTR, QUIT, [D]SUSP"},
                     {ICANON    , "canonicalize input lines"},
                     {IEXTEN    , "enable FLUSHO and LNEXT"},
                     {NOFLSH    , "don't flush after interrupt"},
                     {TOSTOP    , "stop background jobs from output"},
                     {XCASE     , "Cononical upper/lower presentation"},
                     {ECHOKE    , "visual erase for line kill"},
                     {ECHOPRT   , "visual erase mode for hardcopy"},
                     {ECHOCTL   , "echo control chars as ^(Char)"},
                     {ALTWERASE , "use alternate WERASE algorithm"},
                     {MDMBUF    , "flow control output via Carrier"},
                     {FLUSHO    , "output being flushed (state)"},
                     {NOHANG    , "this should go away"},
                     {PENDIN    , "retype pending input (state)"},
                     {NOKERNINFO, "Disable printing kernel info"},
                     {0         , NULL }};

*/
