/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>

#include "../common/common_defs.h"
#include "../commhub/commhub.h"
#include "../commhub/client_utils.h"


int piu_fd = -1;
int hub_fd = -1;

device_test_vector piu_test_vector =
{
    .dev_id             = "rider_ui",
    .init_string        = "/0:\r/1:\r/m:09 00 03 42 5A 44 56\r",
    .n_reply_lines      = 4,
    .reply_strings      = (char *[]){NULL, NULL, "/OK:m:09 00 03 42 5A 44 56", "/M:^"},
};
                
char diag[DIAG_BUFFER_SIZE] = {0};

#define PIU_NUM_LINES	2

static char *piu_set_line_cmd[PIU_NUM_LINES] = {"/0:", "/1:"};

static piu_message def_msgs[PIU_NUM_LINES] = {{0}};
static piu_message cur_msgs[PIU_NUM_LINES] = {{0}};


int update_piu_display(int line)
{
    piu_message *msg;
    
    if( (line >= PIU_NUM_LINES) || (line < 0) )
    {
        return -1;
    }
    
    if(piu_fd < 0)
    {
        return -1;
    }

    msg = (cur_msgs[line].seconds == 0)?(def_msgs + line):(cur_msgs + line);

    write(piu_fd, piu_set_line_cmd[line], 3);		//write the command prefix
    write(piu_fd, msg->message, strlen(msg->message));	//write the actual message
    write(piu_fd, "\r", 1);				//write the CR to signal EOL

//    printf("%s%s\n",piu_set_line_cmd[line], msg->message);

    return 0;
}

message_callback_return handle_display_message(struct message_record *msg, void *param)
{
    piu_message *cmd = (piu_message *) msg->payload;
    piu_message *tgt = NULL;

    time_t now = time(NULL);	//obtain a timestamp for NOW    
    int touched = 0;

    //if we have an update for a line that doesn't exist, ignore it
    if( (cmd->line >= PIU_NUM_LINES) || (cmd->line < 0) )
    {
        return MESSAGE_HANDLED_CONT;
    }
    
    if(cmd->seconds == 0)	//if we're updating the default message
    {
        tgt = def_msgs + cmd->line;	//get a pointer to our message target
        
        //just do it...
        memcpy(tgt, cmd, sizeof(piu_message));

        //force terminate the string just in case
        tgt->message[PIU_MESSAGE_LEN - 1] = '\0';

        //priority is meaningless for default messages
        tgt->priority = 0;
        
        //if the active message on this line has expired, a change in 
        //default message will need to trigger an update
        if(cur_msgs[cmd->line].seconds < now)
        {
            touched = 1;
        }
    }
    else
    {
        tgt = cur_msgs + cmd->line;	//get a pointer to our message target
        
        //If the current message has expired or will expire this second (thus bringing up the default message) 
        //OR the new message is of higher priority then we need to do the repacement
        if( (tgt->seconds <= now) || (cmd->priority >= tgt->priority) )
        {
            //perform the copy
            memcpy(tgt, cmd, sizeof(piu_message));    
            
            //force terminate the string just in case
            tgt->message[PIU_MESSAGE_LEN - 1] = '\0';
            
            //set the expiration time on the new message to the timestamp of NOW
            //plus the desired duration
            tgt->seconds = now + cmd->seconds;
            
            touched = 1;
        }
    }

    if(touched)	//if we have made any changes...
    {
        update_piu_display(cmd->line);
    }

    return MESSAGE_HANDLED_CONT;
}

int handle_message_timeouts()
{
    int i;
    time_t now = time(NULL);
    
    //For each display line
    for(i = 0; i < PIU_NUM_LINES; i++)
    {
        //Check to see if it has an active current message
        if(cur_msgs[i].seconds != 0)
        {
            //If so, see if that message has passed its expiration time
            if(cur_msgs[i].seconds < now)
            {					//if it has...
                cur_msgs[i].seconds = 0;	//flag it inactive
                update_piu_display(i);		//and update the PIU
            }
        }
    }
    
    return 0;
}

#ifdef BEEP_WITH_MAGSTRIPE_READER

char dup_notify_str[MAX_PAYLOAD_LENGTH] = {0};
long long dup_notify_usec = 0;


message_callback_return handle_DIU_beep(struct message_record *msg, void *param)
{
    int is_dup;
    long long dup_usec_delta = 0;
    
    if(strncmp(msg->payload, dup_notify_str, MAX_PAYLOAD_LENGTH))
    {
        strncpy(dup_notify_str, msg->payload, MAX_PAYLOAD_LENGTH - 1);
        dup_notify_str[MAX_PAYLOAD_LENGTH - 1] = '\0';
        is_dup = 0;
        dup_notify_usec = 0;
    }
    else
    {
        is_dup = 1;
        dup_usec_delta = get_usec_time() - dup_notify_usec;
        dup_notify_usec = get_usec_time();    
    }

    switch(msg->payload[0])
    {
        case LOGLEVEL_EVENT:
            if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
            {
                PIU_ACK_BEEP(piu_fd);
            }
            break;
            
        case LOGLEVEL_REJECT:
            if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
            {
                PIU_ERROR_BEEP(piu_fd);
            }
            break;
            
        case LOGLEVEL_ACCEPT:
            if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
            {
                PIU_ACK_BEEP(piu_fd);
            }
            break;
    
        case LOGLEVEL_ERROR:
            if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
            {
                PIU_CRITICAL_BEEP(piu_fd);
            }
            break;
    
        default:
            break;
    }
    
    return MESSAGE_HANDLED_CONT;
}
#endif

//-------------------------

void maintain_ipc_hub_connect(char *progname)
{
    struct message_record outgoing_msg;
    
    if(hub_fd < 0)	//if we have no connection to the IPC hub
    {
        hub_fd = connect_to_message_server(progname);    //try and get one
            
        if(hub_fd >= 0)
        {
            //Subscribe to the default status messages
            subscribe_to_default_messages(hub_fd);
        
            //Subscribe to our specific message
            prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PIU_MESSAGE, strlen(MAILBOX_PIU_MESSAGE));
            send_message(hub_fd,&outgoing_msg);

            #ifdef BEEP_WITH_MAGSTRIPE_READER
                //if BEEP_WITH_MAGSTRIPE_READER is defined, that means our diu board's speaker/amp is FUCKING BROKEN and we
                //need to intercept DIU messages and induce the magstripe reader on the PIU to beep when they occur even
                //though it is a stupid idea to do so since any other activity on the magstripe reader (like swiping a card...)
                //will cause it to either miss the card or disrupt the beeping.
                prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_DRIVER_NOTIFY, strlen(MAILBOX_DRIVER_NOTIFY));
                send_message(hub_fd,&outgoing_msg);
            #endif

        }
        else
        {
            fprintf(stderr, "Cannot connect to IPC hub!\n");
        }
    }
}

int main(int argc, char **argv)
{
    
    char line[LINE_BUFFER_SIZE] = {0};
    
    struct message_record incoming_msg;
    struct message_record outgoing_msg;
    
    struct pollfd fds[2];
    int nfd;
    
    int poll_return;
    int read_return;
    
    int i, j;

    configure_signal_handlers(argv[0]);

    maintain_ipc_hub_connect(argv[0]);

    //Register our defualt system message processing callbacks
    register_system_status_callbacks();
    
    //Register our module specific message processing callbacks
    register_dispatch_callback(MAILBOX_PIU_MESSAGE, CALLBACK_USER(1), &handle_display_message, NULL);
    
    #ifdef BEEP_WITH_MAGSTRIPE_READER
        register_dispatch_callback(MAILBOX_DRIVER_NOTIFY, CALLBACK_USER(2), &handle_DIU_beep, NULL);
    #endif
    
    while( exit_request_status == EXIT_REQUEST_NONE )	//loop until we get asked to exit...
    {

        RESET_WATCHDOG();

        maintain_ipc_hub_connect(argv[0]);
    
        if(piu_fd < 0)
        {
            piu_fd = open_rs232_device(PASSENGER_UI_PORT, USE_DEFAULT_BAUD, RS232_LINE); 
            
            if(piu_fd >= 0)
            {
                read_return = test_and_init_device(piu_fd, &piu_test_vector, diag);
                
                if(read_return)
                {
                    fprintf(stderr, "PIU Init Failed on %s: %s\n", PASSENGER_UI_PORT, diag);
                    close(piu_fd);
                    piu_fd = -1;
                }
                else	//If we successfully connected...
                {
                    //iterate through each line
                    for(j = 0; j < PIU_NUM_LINES; j++)
                    {
                        update_piu_display(j);		//and update the display to be current
                    }
                }
            }
            else
            {
                fprintf(stderr, "Cannot open serial port %s for PIU!\n", PASSENGER_UI_PORT);
            }
        }
    
        
        nfd = 0;
        
        if(hub_fd >= 0)
        {
            fds[nfd].fd = hub_fd;
            fds[nfd].events = POLLIN;
            fds[nfd].revents = 0;
            nfd++;
        }
        
        if(piu_fd >= 0)
        {
            fds[nfd].fd = piu_fd;
            fds[nfd].events = POLLIN;
            fds[nfd].revents = 0;
            nfd++;
        }
    
        if(nfd > 0)	//if we have any file descriptors, poll them
        {
            poll_return = poll(fds, nfd, POLL_TIMEOUT);
        }
        else		//otherwise, whistle and look busy
        {
            poll_return = 0;
            sleep(1);
        }

        //--------------------------------------------------------------------------------
        //No matter what poll() returned, we need to do some basic background work here...
        //--------------------------------------------------------------------------------

        handle_message_timeouts();

        //--------------------------------------------------------------------------------
        

        if(poll_return < 1)	//if poll didn't net us any work to do, 
        {
            continue;		//lets try again
        }    
    
        for(i = 0; i < nfd; i++)
        {
          
            if( fds[i].fd == piu_fd )		//If we're looking at the PIU...
            {

                if(fds[i].revents & (POLLHUP | POLLERR | POLLNVAL))	//if poll says our serial port has become bogus...
                {
                    fprintf(stderr, "This is very odd... Poll returned flags %d on our serial port...\n", fds[i].revents);
                    
                    close(piu_fd);	//close it
                    piu_fd = -1;	//flag it invalid
                    break;		//and break out of the for loop to allow the while to cycle
                }

                if(fds[i].revents & POLLIN)
                {
                    read_return = read(fds[i].fd, line, sizeof(line));
                    
                    if(read_return > 0)
                    {
                        char *trav = line;
                        
                        line[read_return] = '\0';
                        strip_crlf(line);
                        
                        while(*trav && (*trav != '/') )             //advance until EOL or we hit our start sentinel
                        {
                            trav++;
                        }
                        
                        //Check to see that our address header is intact...
                        if( (trav[0] == '/') && (trav[2] == ':') )
                        {
                            switch(trav[1])
                            {
                                case 'M':
                                    trav += 3;	//advance past the header
                                    
                                    //Ignore ACK message from the magstripe reader
                                    if(strcmp(trav, "^"))
                                    {
                                        prepare_message(&outgoing_msg, MAILBOX_TOKEN_MAG, trav, strlen(trav) + 1);
                                        send_message(hub_fd, &outgoing_msg);
                                    }
                                    break;
                            
                                case 'R':
                                    trav += 3;	//advance past the header
                                    
                                    //Ignore null reads.
                                    if(strcmp(trav,"0|0"))
                                    {
                                        prepare_message(&outgoing_msg, MAILBOX_TOKEN_RFID, trav, strlen(trav) + 1);
                                        send_message(hub_fd, &outgoing_msg);
                                    }
                                    break;
                            
                                case '*':	//handle warnings
                                case '#':	//debugs
                                case '!':	//and errors
                                    format_log_message(&outgoing_msg, trav[1], "PIU Reports: %s", trav + 3);	//send them all to the log server
                                    send_message(hub_fd, &outgoing_msg);			
                                    
                                    if(trav[1] == '!')	//but in the case of errors, send them to the driver too
                                    {
                                        format_driver_message(&outgoing_msg, trav[1], "PIU Reports: %s", trav + 3);
                                        send_message(hub_fd, &outgoing_msg);
                                    }
                                    break;
                            
                                default:	//ignore any message addresses that we don't know what to do with
                                    printf("Ignoring command \"%s\"\n", trav);
                                    break;
                            }
                        }
                        else
                        {
//                            printf("Ignoring non-command line \"%s\"\n", trav);
                        }
                    }
                    else
                    {
                        fprintf(stderr, "Read from %s returned %d!\n", PASSENGER_UI_PORT, read_return);
                    
                        close(piu_fd);	//close it
                        piu_fd = -1;	//flag it invalid
                        break;		//and break out of the for loop to allow the while to cycle
                    }
                }
                
            }
            else if( fds[i].fd == hub_fd )	//If we're looking at the IPC hub...
            {
                if(fds[i].revents & (POLLHUP | POLLERR | POLLNVAL))	//if poll says our connection to the IPC hub has died...
                {
                    fprintf(stderr, "The connection to the IPC hub has gone away...\n");	//complain
                    
                    close(hub_fd);	//close it
                    hub_fd = -1;	//flag it dead
                    break;		//break out of the for loop
                }
                
                if(fds[i].revents & POLLIN)	//if we have mail in any of our mailboxes...
                {
                    read_return = get_message(hub_fd, &incoming_msg);
                    
                    if(read_return < 0)
                    {
                        fprintf(stderr, "The connection to the IPC hub has gone away...\n");	//complain
                    
                        close(hub_fd);	//close it
                        hub_fd = -1;	//flag it dead
                        break;		//break out of the for loop
                    }
                    else
                    {
                        message_callback_return msg_status;
                        msg_status = process_message(&incoming_msg);
                    }
                
                }
            
            } 
        }    
    }

    printf("Exiting.\n");
    close(piu_fd);
    close(hub_fd);

    return 0;
}


