/*
 * Copyright (c) 2019 Clementine Computing LLC.
 *
 * This file is part of PopuFare.
 * 
 * PopuFare is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * PopuFare is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with PopuFare.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <regex.h>

#include "commhub.h"

int print_replayable_message(struct message_record *msg)
{
    int i;
    
    printf("%s:",msg->header.mailbox_name);
    
    for(i=0; i < msg->header.payload_length; i++)
    {
        printf(" %02X", msg->payload[i]);
    }
    
    printf("\n");
    
    return 0;
}


int print_decoded_message(struct message_record *msg)
{
    int binflag, i;

    
    printf("%lld: %d->%s (%d bytes):", msg->header.usec_time, msg->header.sender, msg->header.mailbox_name, msg->header.payload_length);
    
    if(!strncmp(msg->header.mailbox_name, MAILBOX_GPS_STATUS, MAILBOX_NAME_MAX))
    {
        gps_status *s = (gps_status *)msg->payload;
        
        printf("Position: %f,%f %3.1f deg @ %.1f M/sec Quality: %d sats (%s)", 
                s->lat, s->lon, s->heading, s->velocity, s->num_sats, (s->gps_good?"OK":"NOT OK") );
    }
    else if(!strncmp(msg->header.mailbox_name, MAILBOX_STOP_STATUS, MAILBOX_NAME_MAX))
    {
        stop_status *s = (stop_status *)msg->payload;
        
        printf("Paddle %d Route %d Trip %d Stop %d (%f,%f) \"%s\"", s->paddle, s->route, s->trip, s->stop, s->lat, s->lon, s->stopname);
    }
    else if(!strncmp(msg->header.mailbox_name, MAILBOX_PASS_STATUS, MAILBOX_NAME_MAX))
    {
        pass_status *s = (pass_status *)msg->payload;
        char *flush_str;
        
        switch(s->flush_status)
        {
            case FLUSH_STATUS_NORMAL:
                flush_str="NORMAL";
                break;
                
            case FLUSH_STATUS_LEGACY:
                flush_str="LEGACY";
                break;
                
            case FLUSH_STATUS_DOWNLOAD:
                flush_str="DOWNLOAD";
                break;
                
            case FLUSH_STATUS_APPLY:
                flush_str="APPLY";
                break;
                
            case FLUSH_STATUS_WRITE:
                flush_str="WRITE";
                break;
                
            default:
                flush_str="???";
                break;
        }
        
        printf("Flush Status = %d (%s)  Progress = %d%% LastSync = %d LastAck = %d", s->flush_status, flush_str, s->progress_indicator, (int)s->last_sync_time, (int)s->last_ack_time);
    }
    else if(!strncmp(msg->header.mailbox_name, MAILBOX_BILL_STATUS, MAILBOX_NAME_MAX))
    {
        bill_status *s = (bill_status *)msg->payload;
        
        printf("LastSync = %d LastAck = %d", (int)s->last_sync_time, (int)s->last_ack_time);
    }
    else if(!strncmp(msg->header.mailbox_name, MAILBOX_PIU_MESSAGE, MAILBOX_NAME_MAX))
    {
        piu_message *s = (piu_message *)msg->payload;
        
        printf("Priority = %d Line = %d Duration = %d (0 = make default) Message = \"%s\"", s->priority, s->line, s->seconds, s->message);
    }
    else if(!strncmp(msg->header.mailbox_name, MAILBOX_SET_PADDLE, MAILBOX_NAME_MAX) || !strncmp(msg->header.mailbox_name, MAILBOX_PADDLE_ACK, MAILBOX_NAME_MAX))
    {
        set_paddle_req *s = (set_paddle_req *)msg->payload;
        
        printf("Requested = %d Result = %d", s->request, s->result);
    }
    else if(!strncmp(msg->header.mailbox_name, MAILBOX_RULE_CALL, MAILBOX_NAME_MAX))
    {
        driver_rulecall *s = (driver_rulecall *)msg->payload;
        
        printf("Rule = \"%s\" Parameter = \"%s\"", s->rulename, s->ruleparam);
    }
    else
    {
        binflag = 0;
        
        for(i = 0; i < msg->header.payload_length; i++)
        {
        
            switch(msg->payload[i])
            {
                case '\0':
                case '\t':
                case '\r':
                case '\n':
                    break;
            
                default:
                    if( (msg->payload[i] < ' ') || (msg->payload[i] > '~') )
                    {
                        binflag = 1;
                    } 
                    break;
            }
        }

        if(binflag)
        {        
            for(i = 0; i < msg->header.payload_length; i++)
            {
                printf("%02x ", msg->payload[i]);
            }
        }
        else
        {
            printf("\"");
            for(i = 0; i < msg->header.payload_length; i++)
            {
                switch(msg->payload[i])
                {
                    case '\0':
                        printf("\\0");
                        break;
                    case '"':
                        printf("\\\"");
                        break;
                    case '\r':
                        printf("\\r");
                        break;
                    case '\n':
                        printf("\\n");
                        break;
                    case '\t':
                        printf("\\t");
                        break;
                    case '\\':
                        printf("\\\\");
                        break;
                        
                    default:
                        printf("%c", msg->payload[i]);
                        break;
                }
            }
            printf("\"");
        }
        
    }

    printf("\n");

    return 0;
}


#define	CLIENT_MODE_WIRETAP		(0x0001)		//Enter Wiretap Mode
#define CLIENT_MODE_RAWOUT		(0x0002)		//Format output in replayable raw format
#define CLIENT_MODE_TRANSMIT_FILE	(0x0004)		//Transmit messages from a specific file
#define CLIENT_MODE_TRANSMIT_STDIN	(0x0008)		//Transmit messages from standard in
#define CLIENT_MODE_TRANSMIT_RATE	(0x0010)		//Wait between transmits
#define CLIENT_MODE_QUIT_AFTER_EOF	(0x0020)		//Quit after EOF on our transmit source
#define CLIENT_MODE_RECONNECT		(0x0040)		//Reconnect if disconnected from IPC hub
#define CLIENT_MODE_VERBOSE		(0x0080)		//In verbose mode we generate lots more commentary to stderr
#define CLIENT_MODE_MATCH_REGEX		(0x0100)		//In Match Regex mode we only display mailboxes that match regex
#define CLIENT_MODE_EXCLUDE_REGEX	(0x0200)		//In Exclude Regex mode we exclude mailboxes that match regex


//Flags setting our mode of operation (see CLIENT_MODE_* above)
//
int mode_flags = 0;				//This is properly initialized in process_command_line()
                                                

FILE *infile = NULL;				//If we are transmitting from a file, here's our handle to it...
    
long long int last_tx = 0;			//Start out with a last-transmit time of "never"
long long int tx_rate_limit = 0;		//Wait at least tx_rate_limit milliseconds between sends

int hub_fd = -1;				//File descriptor of our communication hub		

regex_t match_expr;				//Mailbox name match regex (compiled)
regex_t exclude_expr;				//Mailbox name exclude regex (compiled)


#define FLAG_WIRETAP_ON			("-W")		//Turns Wiretap mode on (default)
#define FLAG_WIRETAP_OFF		("-w")		//Turns Wiretap mode off

#define FLAG_RAWMODE_ON			("-R")		//Turns Raw Replayable mode on
#define FLAG_RAWMODE_OFF		("-r")		//Turns Raw Replayable mode off (default)

#define FLAG_TX_FILE			("-f")		//Selects a transmit file (other than stdin)
#define FLAG_TX_STDIN			("-s")		//Selects transmits to come from standard input
#define FLAG_TX_RATE			("-t")		//Selects a delay between transmits (in milliseconds)

#define FLAG_TX_QUIT_EOF		("-Q")		//Selects the behavior of quitting after EOF on our input file (default)
#define FLAG_TX_LISTEN_EOF		("-q")		//Selects the behavior of hanging out and listending after EOF on input file

#define FLAG_RECONNECT_ON		("-X")		//Reconnect when disconnected from the IPC hub.
#define FLAG_RECONNECT_OFF		("-x")		//Don't reconnect when disconnected from the IPC hub.

#define FLAG_VERBOSE_ON			("-V")		//Set verbose mode ON (all status messages to stderr)
#define FLAG_VERBOSE_OFF		("-v")		//Set verbose mode OFF (no status messages to stderr)

#define FLAG_MATCH_REGEX		("-m")		//Set mailbox name match regex
#define FLAG_EXCLUDE_REGEX		("-M")		//Set mailbox exclude regex

#define STATUS_MSG(fmt, ...)	if(mode_flags & CLIENT_MODE_VERBOSE){fprintf(stderr, fmt, ## __VA_ARGS__);}

void quit_gracefully(int exitcode)
{
    if(infile)
    {
        fclose(infile);
        infile = NULL;
    }
    
    if(hub_fd >= 0)
    {
       close(hub_fd);
       hub_fd = -1; 
    }

    if(mode_flags & CLIENT_MODE_MATCH_REGEX)
    {
        regfree(&match_expr);
    }
    
    if(mode_flags & CLIENT_MODE_EXCLUDE_REGEX)
    {
        regfree(&exclude_expr);
    }
    
    STATUS_MSG("Quitting.\n");
    
    exit(exitcode);
}

void usage(char *progname)
{
    fprintf(stderr, "Usage:	%s [-w | -W] [-r | -R] [-x | -X] [ [-q | -Q] [-s | -f filespec] [-t msec] ] [-m match_regex | -M exclude_regex]\n\n", progname);
    fprintf(stderr, "\t -W = Enable Wiretap. (default)\n");
    fprintf(stderr, "\t -w = Disable Wiretap.\n");
    fprintf(stderr, "\t -R = Enable Raw/Replayable output.\n");
    fprintf(stderr, "\t -r = Disable Raw/Replayable output. (default)\n");
    fprintf(stderr, "\t -s = Enable transmission of raw format messages from standard in.\n");
    fprintf(stderr, "\t -f filespec = Enable transmission of raw format messages from specified regular file.\n");
    fprintf(stderr, "\t -t usec = Rate limit transmitted messages by waiting usec milliseconds between transmits.\n");
    fprintf(stderr, "\t -Q = Quit after sending the last message in our input file. (default)\n");
    fprintf(stderr, "\t -q = Listen forever after sending the last message in our input file.\n");
    fprintf(stderr, "\t -X = Reconnect if disconnected from IPC hub.\n");
    fprintf(stderr, "\t -x = Don't attempt to reconnect if disconnected. (default)\n");
    fprintf(stderr, "\t -V = Set verbose mode (all status messages to stderr).\n");
    fprintf(stderr, "\t -v = Clear verbose mode (no status messages to stderr). (default)\n");
    fprintf(stderr, "\t -m match_regex = Only display messages to mailbox names that match supplied regex.\n");
    fprintf(stderr, "\t -M exclude_regex = Ignore received messages to mailbox names that match supplied regex.\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "\tThe -f and -s options are mutually exclusive.  If both the\n");
    fprintf(stderr, "-w and -W options or both the -r and -R options are specified\n");
    fprintf(stderr, "the one furthest to the right will take precedence.  The same\n");
    fprintf(stderr, "holds true for -Q/-q and -X/-x as well as -V/-v.\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "When invoked with no flags, it is equivalent to:\n");
    fprintf(stderr, "\t%s -W -r -x\n", progname);
    fprintf(stderr, "\n");
    fprintf(stderr, "\tNo transmission source is enabled by default, thus allowing\n");
    fprintf(stderr, "the task to be backgrounded without having it stop for input from the terminal.\n");
    fprintf(stderr, "It should also be noted that when not in a transmit mode, the -q and -Q flags as\n");
    fprintf(stderr, "well as the -t flag are considered invalid.\n");
    fprintf(stderr, "\n");
}

void process_command_line(int argc, char **argv)
{
    mode_flags = CLIENT_MODE_WIRETAP | CLIENT_MODE_QUIT_AFTER_EOF;
    
    int expect_file = 0;
    int expect_time = 0;
    int expect_match = 0;
    int expect_exclude = 0;
    
    int i;
    int retval;
    int qeof = 0;	//flag to see if a quit-after-eof flag is processed (either way) so we can give
                        //an error if it is not done in a transmit mode...
    
    char errorbuffer[1024]={0};
    
    for(i = 1; i < argc; i++)
    {
        if(expect_file)
        {
            infile = fopen(argv[i], "rb");
            
            if(!infile)
            {
                fprintf(stderr, "Cannot open file specified by -f (\"%s\") for reading!\n", argv[i]);
                quit_gracefully(1);
            }
            
            expect_file = 0;
        }
        else if(expect_time)
        {
            retval = sscanf(argv[i], "%lld", &tx_rate_limit);
            
            if(retval != 1)
            {
                fprintf(stderr, "Expecting integer number of milliseconds after -t, got \"%s\"!\n", argv[i]);
                quit_gracefully(1);
            }
            
            expect_time = 0;          
        }
        else if(expect_match)
        {
            retval = regcomp(&match_expr, argv[i], REG_EXTENDED | REG_NOSUB);
            
            if(retval)
            {
                regerror(retval, &match_expr, errorbuffer, sizeof(errorbuffer));
                errorbuffer[sizeof(errorbuffer) - 1] = '\0';
                
                fprintf(stderr, "Error compiling regular expression supplied by -m: %s\n", errorbuffer);
                quit_gracefully(1);
            }
            else
            {
                expect_match = 0;
                mode_flags |= CLIENT_MODE_MATCH_REGEX;
            }
        }
        else if(expect_exclude)
        {
            retval = regcomp(&exclude_expr, argv[i], REG_EXTENDED | REG_NOSUB);
            
            if(retval)
            {
                regerror(retval, &exclude_expr, errorbuffer, sizeof(errorbuffer));
                errorbuffer[sizeof(errorbuffer) - 1] = '\0';
                
                fprintf(stderr, "Error compiling regular expression supplied by -M: %s\n", errorbuffer);
                quit_gracefully(1);
            }
            else
            {
                expect_exclude = 0;
                mode_flags |= CLIENT_MODE_EXCLUDE_REGEX;
            }
        }
        else
        {
            if(!strcmp(argv[i], FLAG_WIRETAP_ON))
            {
                mode_flags |= CLIENT_MODE_WIRETAP;
            }
            else if(!strcmp(argv[i], FLAG_WIRETAP_OFF))
            {
                mode_flags &= ~CLIENT_MODE_WIRETAP;
            }
            else if(!strcmp(argv[i], FLAG_RAWMODE_ON))
            {
                mode_flags |= CLIENT_MODE_RAWOUT;
            }
            else if(!strcmp(argv[i], FLAG_RAWMODE_OFF))
            {
                mode_flags &= ~CLIENT_MODE_RAWOUT;
            }
            else if(!strcmp(argv[i], FLAG_TX_QUIT_EOF))
            {
                mode_flags |= CLIENT_MODE_QUIT_AFTER_EOF;
                qeof |= 1;
            }
            else if(!strcmp(argv[i], FLAG_TX_LISTEN_EOF))
            {
                mode_flags &= ~CLIENT_MODE_QUIT_AFTER_EOF;
                qeof |= 1;
            }
            else if(!strcmp(argv[i], FLAG_RECONNECT_ON))
            {
                mode_flags |= CLIENT_MODE_RECONNECT;
            }
            else if(!strcmp(argv[i], FLAG_RECONNECT_OFF))
            {
                mode_flags &= ~CLIENT_MODE_RECONNECT;
            }
            else if(!strcmp(argv[i], FLAG_VERBOSE_ON))
            {
                mode_flags |= CLIENT_MODE_VERBOSE;
            }
            else if(!strcmp(argv[i], FLAG_VERBOSE_OFF))
            {
                mode_flags &= ~CLIENT_MODE_VERBOSE;
            }
            else if(!strcmp(argv[i], FLAG_TX_STDIN))
            {
                if(mode_flags & CLIENT_MODE_TRANSMIT_FILE)
                {
                    fprintf(stderr, "Conflicting Flags (-f and -s)!\n");
                    usage(argv[0]);
                    quit_gracefully(1);
                }
                else
                {
                    mode_flags |= CLIENT_MODE_TRANSMIT_STDIN;
                }
            }
            else if(!strcmp(argv[i], FLAG_TX_FILE))
            {
                if(mode_flags & CLIENT_MODE_TRANSMIT_FILE)
                {
                    fprintf(stderr, "Only one -f flag is valid per command line!\n");
                    usage(argv[0]);
                    quit_gracefully(1);
                }
            
                if(mode_flags & CLIENT_MODE_TRANSMIT_STDIN)
                {
                    fprintf(stderr, "Conflicting Flags (-f and -s)!\n");
                    usage(argv[0]);
                    quit_gracefully(1);
                }
                else
                {
                    mode_flags |= CLIENT_MODE_TRANSMIT_FILE;
                    expect_file = 1;
                }
            }
            else if(!strcmp(argv[i], FLAG_TX_RATE))
            {
                mode_flags |= CLIENT_MODE_TRANSMIT_RATE;
                expect_time = 1;
            }
            else if(!strcmp(argv[i], FLAG_MATCH_REGEX))
            {
                if(mode_flags & (CLIENT_MODE_MATCH_REGEX | CLIENT_MODE_EXCLUDE_REGEX))
                {
                    fprintf(stderr, "Only one -m/-M flag allowed per command line!\n");
                    usage(argv[0]);
                    quit_gracefully(1);
                }
                
                expect_match = 1;
            }
            else if(!strcmp(argv[i], FLAG_EXCLUDE_REGEX))
            {
                if(mode_flags & (CLIENT_MODE_MATCH_REGEX | CLIENT_MODE_EXCLUDE_REGEX))
                {
                    fprintf(stderr, "Only one -m/-M flag allowed per command line!\n");
                    usage(argv[0]);
                    quit_gracefully(1);
                }
                
                expect_exclude = 1;
            }
            else
            {
                fprintf(stderr, "Unknown option \"%s\"!\n", argv[i]);
                usage(argv[0]);
                quit_gracefully(1);
            }
        }
    }
    
    if(expect_file)	//If we've hit the end of our argument list and we're left hanging looking for a filename
    {
        fprintf(stderr,"Input file expected following -f flag!\n");
        usage(argv[0]);
        quit_gracefully(1);
    }
    
    if(expect_time)	//If we're left hanging looking for a rate-limit time
    {
        fprintf(stderr,"Delay time in milliseconds expected following -t flag!\n");
        usage(argv[0]);
        quit_gracefully(1);
    }

    if(expect_match)    //If we're left hanging waiting for a regex for -m
    {
        fprintf(stderr,"Regular expression expected following -m flag!\n");
        usage(argv[0]);
        quit_gracefully(1);
    }

    if(expect_exclude)    //If we're left hanging waiting for a regex for -m
    {
        fprintf(stderr,"Regular expression expected following -M flag!\n");
        usage(argv[0]);
        quit_gracefully(1);
    }

    //If we're not in a transmit mode, certain options are not valid...
    if(!( mode_flags & (CLIENT_MODE_TRANSMIT_FILE | CLIENT_MODE_TRANSMIT_STDIN)))
    {
        if(mode_flags & CLIENT_MODE_TRANSMIT_RATE)	//Transmit rate limit isn't valid without transmit source
        {
            fprintf(stderr,"Transmit rate limit specified without transmit source!\n");
            usage(argv[0]);
            quit_gracefully(1);  
        }
        
        if(qeof)
        {
            fprintf(stderr,"The -q and -Q flags are not valid if we are not in a transmit mode!\n");
            usage(argv[0]);
            quit_gracefully(1);
        }
    }

    return;    
}


int do_server_connect(char *progname)
{
    hub_fd = connect_to_message_server(progname);
    
    if( hub_fd < 0 )
    {
        STATUS_MSG("Cannot connect to IPC hub!\n");
        
        if( !(mode_flags & CLIENT_MODE_RECONNECT) )
        {
            quit_gracefully(0);
        }
        
        return -1;
    }
    else if(mode_flags & CLIENT_MODE_WIRETAP)
    {
        struct message_record msg;
    
        STATUS_MSG("Entering WIRETAP mode!\n");
        
        prepare_message(&msg, MAILBOX_WIRETAP, "ON", 2);
        send_message(hub_fd,&msg);
    }
  
    return 0;    
}

long long int get_msec()
{
    return get_usec_time() / 1000;
}


//----------------------------------------      
#define SKIP_WHITESPACE(trav,c)		\
    while(trav && *trav)		\
    {					\
        c = *trav;			\
                                        \
        if( (c == ' ') || (c == '\t') ||\
            (c == '\r') || (c == '\n') )\
        {				\
            trav++;			\
        }				\
        else				\
        {				\
            break;			\
        }				\
    }					
//----------------------------------------      

int parse_raw_message(char *rawmsg, struct message_record *msg)
{
    char mailbox_name[MAILBOX_NAME_MAX + 1] = {0};
    unsigned char payload[MAX_PAYLOAD_LENGTH] = {0};
    int payload_len = 0;
    
    int i;
    
    char *mbx = rawmsg;
    char c;
    
    unsigned char accum;
    
    SKIP_WHITESPACE(mbx,c);
    
    if( (*mbx == '\0') || (*mbx == '#') )
    {
        return -1;
    }
    
    for(i = 0; i <= MAILBOX_NAME_MAX; i++)
    {
        mailbox_name[i] = mbx[i];
        
        if((mbx[i] == ':') || (mbx[i] == '\0') || 	//if we hit a : (field terminator), nul (end sentinel)
           (mbx[i] == ' ') || (mbx[i] == '\t') || 	//or any of the usual suspects (whitespace characters)
           (mbx[i] == '\r') || (mbx[i] == '\n'))	//we want to terminate the mailbox name and continue...
        {
            mailbox_name[i] = '\0';
            break;
        }
    }
    
    mbx += i;	//skip past the mailbox name

    SKIP_WHITESPACE(mbx,c);	//Clean off any trailing whitespace that we may have left over

    if(*mbx++ != ':')		//if the next non-whitespace character isn't the field separator...
    {
        STATUS_MSG("Malformed input line: \"%s\"\n", rawmsg);
        return -1;
    }
    
    payload_len = 0;
    accum = 0;
    i = 0;
        
    while(*mbx)
    {
        switch(*mbx)
        {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                accum <<= 4;
                accum |= (*mbx) - '0';	//ascii to numeric hex
                i++;
                mbx++;
                break;
        
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
                accum <<= 4;
                accum |= (*mbx) - 'W';	//ascii to lower case hex
                i++;
                mbx++;
                break;
        
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
                accum <<= 4;
                accum |= (*mbx) - '7';	//ascii to 
                i++;
                mbx++;
                break;
        
            case ' ':
            case '\t':
            case '\r':
            case '\n':
                mbx++;
                if(i > 0)	//if we have an octet started
                {
                    i = 2;	//whitespace ends the current octet
                }
                break;
                
            default:
                STATUS_MSG("Illegal character '%c' in raw message (expecting hex or whitespace)!\n", *mbx);
                return -1;
                break;  
                
        } //switch(*mbx)
        
        if(i >= 2)	//If we've snagged ourselves both nybbles of an octet
        {
            payload[payload_len] = accum;	//store the octet
        
            accum = 0;				//reset our accumulator
            i = 0;				//and nybble counter
        
            payload_len++;			//advance our payload_len variable to be accurate
        
            if(payload_len >= MAX_PAYLOAD_LENGTH)	//test against our maximum length...
            {
                payload_len = MAX_PAYLOAD_LENGTH;	//we've hit our maximum payload size...
                break;					//we're done accumulating this message
            }
        }
        
    }	//while(*mbx)
    
    return prepare_message(msg, mailbox_name, payload, payload_len);
}


#define FULL_POLL_TIME	(1000)

int main(int argc, char **argv)
{
    char linebuffer[TOTAL_MESSAGE_SIZE * 4];
    
    struct message_record msg;
    struct message_record outgoing;
    
    int retval;
    int polltime;
    
    int transmit_pending = 0;
    struct pollfd fds[2] = {{0}};			//File descriptor set
    int nfds = 0;
    
    int end_of_input = 0;
    
    //--------------------------------

    signal(SIGINT, quit_gracefully);   
    signal(SIGTERM, quit_gracefully);   
   
    process_command_line(argc, argv);

    do_server_connect(argv[0]);
   
    while(1)
    {
    
        if((mode_flags & CLIENT_MODE_TRANSMIT_FILE) && (infile != NULL))
        {
            if(feof(infile))
            {
                end_of_input = 1;
            }
        }
    
        //If we've hit the end of the line for our input, and we are flagged to exit at that point,
        if( end_of_input && (mode_flags & CLIENT_MODE_QUIT_AFTER_EOF) )
        {
            quit_gracefully(0);    
        }
    
        if(hub_fd < 0)
        {
            sleep(1);
            STATUS_MSG("Attempting to reconnect to IPC hub!\n");
            do_server_connect(argv[0]);
            continue;          
        }
    
        nfds = 0;
        
        fds[nfds].fd = hub_fd;
        fds[nfds].events = POLLIN;
        fds[nfds].revents = 0;
        nfds++;
 
        polltime = FULL_POLL_TIME;
        
        //If we are in rate-limited transmit mode
        if(mode_flags & CLIENT_MODE_TRANSMIT_RATE)
        {
            //Calculate how many milliseconds before our next scheduled transmit window
            long long int delta = tx_rate_limit - (get_msec() - last_tx);
            
            //    If that time is less than polltime milliseconds away, shorten polltime so we can
            //drop out of poll, add our input file descriptor, and try polling again to see if we have
            //any input we can make into a message to transmit.
            if( (delta > 0) && (delta < polltime) )
            {
                polltime = delta;
            }
        }
    
        if( !(mode_flags & CLIENT_MODE_TRANSMIT_RATE) || 		//if we are either NOT rate limiting
            ((get_msec() - last_tx) >= tx_rate_limit) 			//or we are, but it is time to transmit again
          )
        {
            if(!transmit_pending && !end_of_input)	//if there is no transmit pending and we are due one, we want to gather input...
            {						//(unless of course, we've hit EOF on our current input source)
            
                if(mode_flags & CLIENT_MODE_TRANSMIT_STDIN)	//if we are transmitting from stdin, add it to the poll
                {
                    fds[nfds].fd = STDIN_FILENO;
                    fds[nfds].events = POLLIN;
                    fds[nfds].revents = 0;
                    nfds++;
                }
                else if((mode_flags & CLIENT_MODE_TRANSMIT_FILE) && //if we are transmitting from a regular file, add it
                        (infile != NULL))			//(unless it's been closed)
                {						//even though it should always report ready, it prevents
                    fds[nfds].fd = fileno(infile);		//us having to handle a stupid special case...
                    fds[nfds].events = POLLIN;
                    fds[nfds].revents = 0;
                    nfds++;
                }
            }

            polltime = FULL_POLL_TIME;		
        }

        if(transmit_pending)	//If we have assembled an entire message to transmit,
        {
            fds[0].events |= POLLOUT;	//flag poll() to tell us when the IPC hub is ready to receive it.
        }    
 
        retval = poll(fds, nfds, polltime);	//Poll waiting for something to happen (up to 1 second)

        if(retval < 0)		//if poll() returns an error other than EINTR, that's really bad news.
        {
            int errnum = errno;
            
            if(errnum == EINTR)	//If we were interrupted by a signal
            {
                continue;	//just go and try it again...
            }
            
            STATUS_MSG("Poll returned error %d (%s)\n", errnum, strerror(errnum));
            quit_gracefully(1);
        }
        else if(retval > 0)	//If there were more than zero flagged file descriptors
        {
            if(fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
            {
                STATUS_MSG("Connection to IPC hub has been died.\n");
                close(hub_fd);
                hub_fd = -1;
                
                if(mode_flags & CLIENT_MODE_RECONNECT)	//If this client has been asked to reconnect when possible,
                {
                    continue;	//Continue the while(1) which will attempt reconnect
                }
                else	//on the other hand, if this client hasn't been asked to reconnect, that's time to quit
                {
                    quit_gracefully(0);
                }
            }
            else
            {
                if(fds[0].revents & POLLIN)			//If we have input available from the IPC hub
                {
                    retval = get_message(hub_fd, &msg);	//try and fetch it,,,
                
                    if(retval)				//if that fails
                    {
                        //complain to the user
                        STATUS_MSG("Error returned by get_message(), connection to IPC hub suspect!\n");
                    
                        //and ditch the connection
                        close(hub_fd);
                        hub_fd = -1;
                        continue;
                    }
                    else					//on success... we have a message...
                    {
                        int skip;
                      
                      
                        skip = 0;
                        
                        if(mode_flags & CLIENT_MODE_MATCH_REGEX)	//if we're in -m mode (display only matching)
                        {
                            //if the match_expr regex returns REG_NOMATCH on the mailbox name, we want to skip this message
                            if(regexec(&match_expr, msg.header.mailbox_name, 0, NULL, 0) == REG_NOMATCH)
                            {
                                skip = 1;
                            }
                        }
                    
                        if(mode_flags & CLIENT_MODE_EXCLUDE_REGEX)	//if we're int -M mode (display only non-matching)
                        {
                            //if the reject_expr regex returns 0 (match) on the mailbox name, we want to skip this message
                            if(regexec(&exclude_expr, msg.header.mailbox_name, 0, NULL, 0) == 0)
                            {
                                skip = 1;
                            }
                        }
                    
                        if(!skip)
                        {
                            if(mode_flags & CLIENT_MODE_RAWOUT)	//if we are in raw output mode, 
                            {
                                //generate the raw/replayable version of the message (machine readable)
                                print_replayable_message(&msg);
                            }
                            else				//otherwise
                            {
                                //generate the decoded version of the message (human readable)
                                print_decoded_message(&msg);
                            }
                        }
                    }
                }
                if(fds[0].revents & POLLOUT)	//if we a timeslot to transmit and we've asked to talk
                {
                    send_message(hub_fd, &outgoing);	//that means that we've filled our output message
                    transmit_pending = 0;		//flag this message as non-pending.
                    last_tx = get_msec();		//and record our last transmit time
                }
            
            }
            
            if(nfds > 1)
            {
                if(fds[1].revents & POLLIN)
                {
                    FILE *f = stdin;
                        
                    if(mode_flags & CLIENT_MODE_TRANSMIT_FILE)
                    {
                        f = infile;
                    }
                        
                    //If we both have a file to fetch from, and can fetch something besides EOF                  
                    if( (f != NULL) && (fgets(linebuffer, sizeof(linebuffer), f) != NULL) )
                    {
                        //If we can parse that into something we understand, flag it for transmission
                        transmit_pending = (parse_raw_message(linebuffer, &outgoing) == 0);
                    }
                    else
                    {
                        
                        end_of_input = 1;
                    }
                }                
                else if(fds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
                {
                    STATUS_MSG("EOF encountered reading from input.\n");
                    end_of_input = 1;
                                 
                    if(mode_flags & CLIENT_MODE_TRANSMIT_FILE)
                    {
                        fclose(infile);
                        infile = NULL;
                        continue;
                    }
                    
                }
               
            }
        
        }

    }

    return 0;
}
