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

#include <stdarg.h>

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

gps_status      gps_stat = {0};
stop_status	stop_stat = {0};
driver_status	driver_stat = {0};
pass_status	pass_stat = {0};
bill_status	bill_stat = {0};

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

float effective_lat()
{
    if(gps_stat.gps_good)
    {
        return (float)gps_stat.lat;
    }
    else
    {
        return (float)stop_stat.lat;
    }
}

float effective_lon()
{
    if(gps_stat.gps_good)
    {
        return (float)gps_stat.lon;
    }
    else
    {
        return (float)stop_stat.lon;
    }
}

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

typedef struct message_callback_struct
{
    char mailbox[MAILBOX_NAME_MAX + 1];
    
    int ident;
    
    message_notify_func callback;
    void *param;

    struct message_callback_struct *next;

} message_callback;


static message_callback *dispatch_list_head = NULL;

//This function registers a callback to happen when a certain message is received.
//It takes four parameters:
//				mailbox = string containing name of mailbox to trigger this callback
//				ident   = a numeric identifier specifying both a unique identifier for this callback, and processing order
//				func	= a message handling callback function
//				param	= an untyped pointer used to pass any outside contect to the callback
//
int register_dispatch_callback(char *mailbox, int ident, message_notify_func func, void *param)
{
    message_callback *p, *q, *n;
    
    if(!func || !mailbox)
        return -1;

    q = NULL;
    p = dispatch_list_head;
    
    //Scan our existing list
    while(p)
    {
        if(p->ident > ident)	//stop when we find a node with an identifier greater than ident or run off the end
            break;
    
        q = p;
        p = p->next;
    }

    n = (message_callback *)malloc(sizeof(message_callback));
    
    if(n == NULL)
        return -1;
        
    memset(n, 0, sizeof(message_callback));		//clear our new node
    strncpy(n->mailbox, mailbox, MAILBOX_NAME_MAX);	//copy our match string
    n->mailbox[MAILBOX_NAME_MAX] = '\0';		//null terminate

    n->ident = ident; 				//record our identifier   
    n->callback = func;				//callback
    n->param = param;				//and parameter

    n->next = p;				//register our next pointer as per above scan's determined insert point

    if(q == NULL)	//If we're inserting at the head of the lsit
    {
        dispatch_list_head = n;		//set the list head to n
    }
    else				//otherwise
    {
        q->next = n;			//tweak the next pointer of the previous node to perform the insertion
    }

    return 0;        
}

//This function unregisters a message callback function by its mailbox name and identifier
//this allows for easy selective removal of a specific callback while still keeping the rest of the chain intact
int unregister_dispatch_callback(char *mailbox, int ident)
{
    message_callback *p, *q;
    
    if(!mailbox)	//if we have a bogus mailbox, give up
        return -1;
    
    q = NULL;
    p = dispatch_list_head;

    //Scan our callback list looking for the identifier in question
    while(p)
    {
        //if we find a mailbox with both a matching identifier and mailbox name, that's the one...
        if( (p->ident == ident) && !strncmp(mailbox, p->mailbox, MAILBOX_NAME_MAX) )
        {
            break;
        }
        
        q = p;
        p = p->next;
    }

    if(p == NULL)		//if we didn't find it, that is reported by the < 0 return value
    {
        return -1;
    }

    //If we are removing at the head...
    if(q == NULL)
    {
        dispatch_list_head = p->next;	//advance the head pointer down the list
    }
    else //otherwise
    {
        q->next = p->next;	//snip the node in question out mid-list
    }

    free(p);	//free the node in question

    return 0;
}

//This function unregisters ALL callbacks in preparation for a clean exit
int unregister_all_callbacks()
{
    message_callback *p, *q;
    
    q = NULL;
    p = dispatch_list_head;
    
    //traverse the list freeing all nodes
    while(p)
    {
        q = p;
        p = p->next;
        
        free(q);
    }

    //reset our list pointers to empty state
    dispatch_list_head = NULL;
    
    return 0;
}

//    This function is used to translate any special "Command" messages (things addressed
//by module or PID rather than mailbox, where the payload is a mailbox name for special
//notifications (primarily MAILBOX_PING and MAILBOX_EXIT).
static int process_addressed_message(struct message_record *msg)
{
    struct message_record translated_msg;

    //If this message is special...
    if( (!strncmp((char *)msg->payload, MAILBOX_PING, MAILBOX_NAME_MAX)) ||
        (!strncmp((char *)msg->payload, MAILBOX_HUP, MAILBOX_NAME_MAX))  ||
        (!strncmp((char *)msg->payload, MAILBOX_EXIT, MAILBOX_NAME_MAX)) )
    {
        //Copy the message as a whole
        memcpy(&translated_msg, msg, sizeof(translated_msg));
        
        //Then change its mailbox name to the special mailbox specified in the payload
        strncpy(translated_msg.header.mailbox_name, (char *)msg->payload, MAILBOX_NAME_MAX);
        translated_msg.header.mailbox_name[MAILBOX_NAME_MAX] = '\0';	//this is not a bug, since mailbox_name is defined as MAILBOX_NAME_MAX + 1 bytes long
        
        //Go and feed this through the callback chain as if it originally came on on the actual mailbox...
        process_message(&translated_msg);
        
        return 0;
    }
    
    return 0;
}

//    This function actually processes a message through the dispatch list
//using the return values of the callbacks to determine whether to stop or continue
//the return value is the return value of the last handler to have read the message.
//a return value of MESSAGE_UNHANDLED means that no handler successfully handled the message.
message_callback_return process_message(struct message_record *msg)
{
    message_callback *p = dispatch_list_head;
    message_callback_return ret = MESSAGE_UNHANDLED;
    
    if(msg == NULL)	//if there is no message
    {
        return ret;	//we can't handle it
    }

    //Walk through this list of handlers (which is maintained sorted by ident)
    while(p)	
    {
        if(!strncmp(msg->header.mailbox_name, p->mailbox, MAILBOX_NAME_MAX))	//if the message's mailbox matches the callback's mailbox
        {
            ret = p->callback(msg, p->param);			//go and run the callback
            
            if(ret == MESSAGE_HANDLED_STOP)			//if this callback requested a STOP, do so
                break;
        }

        p = p->next;					//otherwise, keep going...

    }

    //If nobody has said STOP yet, see if this is an individually addressed message
    if( (msg->header.mailbox_name[0] == ':') || (msg->header.mailbox_name[0] == '>') )
    {
        process_addressed_message(msg);
    }

    return ret;
}


//------------------------------------ SOME USEFUL DEFAULT CALLBACKS -------------------------------------------

message_callback_return ignore_message(struct message_record *msg, void *param)
{
    return MESSAGE_HANDLED_STOP;
}

//------------------------------------ SYSTEM CALLBACKS -------------------------------------------------------

static message_callback_return update_gps_status(struct message_record *msg, void *param)
{
    if(msg->header.payload_length != sizeof(gps_status))	//guard against version mismatch
    {
        return MESSAGE_HANDLED_STOP;
    }  

    memcpy(&gps_stat, msg->payload, sizeof(gps_status));		//otherwise, update our structure
    
    return MESSAGE_HANDLED_CONT;
}

static message_callback_return update_stop_status(struct message_record *msg, void *param)
{
    if(msg->header.payload_length != sizeof(stop_status))	//guard against version mismatch
    {
        return MESSAGE_HANDLED_STOP;
    }  

    memcpy(&stop_stat, msg->payload, sizeof(stop_status));	//otherwise, update our structure
    
    return MESSAGE_HANDLED_CONT;
}

static message_callback_return update_driver_status(struct message_record *msg, void *param)
{
    if(msg->header.payload_length != sizeof(driver_status))	//guard against version mismatch
    {
        return MESSAGE_HANDLED_STOP;
    }  

    memcpy(&driver_stat, msg->payload, sizeof(driver_stat));	//otherwise, update our structure
    
    return MESSAGE_HANDLED_CONT;
}

static message_callback_return update_bill_status(struct message_record *msg, void *param)
{
    if(msg->header.payload_length != sizeof(bill_status))	//guard against version mismatch
    {
        return MESSAGE_HANDLED_STOP;
    }  

    memcpy(&bill_stat, msg->payload, sizeof(bill_stat));	//otherwise, update our structure
    
    return MESSAGE_HANDLED_CONT;
}

static message_callback_return update_pass_status(struct message_record *msg, void *param)
{
    if(msg->header.payload_length != sizeof(pass_status))	//guard against version mismatch
    {
        return MESSAGE_HANDLED_STOP;
    }  

    memcpy(&pass_stat, msg->payload, sizeof(pass_stat));	//otherwise, update our structure
    
    return MESSAGE_HANDLED_CONT;
}

static message_callback_return polite_exit_request_message(struct message_record *msg, void *param)
{
    request_polite_exit(EXIT_REQUEST_POLITE, "%s", (char *)msg->payload);
    return MESSAGE_HANDLED_CONT;
}

static message_callback_return hup_request_message(struct message_record *msg, void *param)
{
    request_hup("HUP(%s)", (char *)msg->payload);
    return MESSAGE_HANDLED_CONT;
}

static message_callback_return ping_request_message(struct message_record *msg, void *param)
{
    struct message_record outgoing;
    
    prepare_message(&outgoing, MAILBOX_PONG, msg->payload, msg->header.payload_length);
    send_message(msg->header.from_fd, &outgoing);
  
    return MESSAGE_HANDLED_CONT;    
}

//This function unregisters the default system status callbacks
int unregister_system_status_callbacks()
{
    unregister_dispatch_callback(MAILBOX_GPS_STATUS, CALLBACK_SYSTEM);
    unregister_dispatch_callback(MAILBOX_STOP_STATUS, CALLBACK_SYSTEM);
    unregister_dispatch_callback(MAILBOX_DRIVER_STATUS, CALLBACK_SYSTEM);
    unregister_dispatch_callback(MAILBOX_PASS_STATUS, CALLBACK_SYSTEM);
    unregister_dispatch_callback(MAILBOX_BILL_STATUS, CALLBACK_SYSTEM);
    
    unregister_dispatch_callback(MAILBOX_EXIT, CALLBACK_SYSTEM);
    unregister_dispatch_callback(MAILBOX_HUP, CALLBACK_SYSTEM);
    unregister_dispatch_callback(MAILBOX_PING, CALLBACK_SYSTEM);

    return 0;
}

//This function registers the default system status callbacks
int register_system_status_callbacks()
{
    unregister_system_status_callbacks();

    register_dispatch_callback(MAILBOX_GPS_STATUS, CALLBACK_SYSTEM, update_gps_status, NULL);
    register_dispatch_callback(MAILBOX_STOP_STATUS, CALLBACK_SYSTEM, update_stop_status, NULL);
    register_dispatch_callback(MAILBOX_DRIVER_STATUS, CALLBACK_SYSTEM, update_driver_status, NULL);
    register_dispatch_callback(MAILBOX_PASS_STATUS, CALLBACK_SYSTEM, update_pass_status, NULL);
    register_dispatch_callback(MAILBOX_BILL_STATUS, CALLBACK_SYSTEM, update_bill_status, NULL);

    register_dispatch_callback(MAILBOX_EXIT, CALLBACK_SYSTEM, polite_exit_request_message, NULL);
    register_dispatch_callback(MAILBOX_HUP, CALLBACK_SYSTEM, hup_request_message, NULL);
    register_dispatch_callback(MAILBOX_PING, CALLBACK_SYSTEM, ping_request_message, NULL);
    
    return 0;
}

//This function subscribes to a handful of essential messages
int subscribe_to_default_messages(int fd)
{
    struct message_record outgoing_msg;
    
    if(fd < 0)
        return -1;
    
    //------------- things that keep us informed about the other modules
    
    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_GPS_STATUS, strlen(MAILBOX_GPS_STATUS));
    send_message(fd, &outgoing_msg);

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_STOP_STATUS, strlen(MAILBOX_STOP_STATUS));
    send_message(fd, &outgoing_msg);

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_DRIVER_STATUS, strlen(MAILBOX_DRIVER_STATUS));
    send_message(fd, &outgoing_msg);

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PASS_STATUS, strlen(MAILBOX_PASS_STATUS));
    send_message(fd, &outgoing_msg);

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_BILL_STATUS, strlen(MAILBOX_BILL_STATUS));
    send_message(fd, &outgoing_msg);

    //------------- things that every process should have a crack at

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_STATUS_REQUEST, strlen(MAILBOX_STATUS_REQUEST));
    send_message(fd, &outgoing_msg);

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_EXIT, strlen(MAILBOX_EXIT));
    send_message(fd, &outgoing_msg);

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_HUP, strlen(MAILBOX_HUP));
    send_message(fd, &outgoing_msg);

    prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PING, strlen(MAILBOX_PING));
    send_message(fd, &outgoing_msg);

    return 0;
}

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

int compress_tabs_to_spaces(char *dest, char *src, int n)
{
    int i;
    
    for(i=0; i < n; i++)
    {
    
        switch(src[i])
        {
            case '\t':
            case '\r':
            case '\n':
                dest[i] = ' ';
                break;
        
            default:
                dest[i] = src[i];
                break;
        }
        
        if(src[i] == '\0')
            break;
    }
    
    return 0;
}

//This function is a printf-line interface to format user display messages:
//
//	target = blank IPC message to fill with this log entry
//	line = which line of the display to draw on (0 or 1)
//      priority = what message priority?
//	duration = how many seconds to display this (0 makes it the default message)
//	fmt = printf format
//	... = printf args
int format_piu_message(struct message_record *target, int line, int priority, int duration, const char *fmt, ...)
{
    piu_message pmsg = {0};
    va_list ap;		//argument list...
    
    pmsg.line = line;
    pmsg.priority = priority;
    pmsg.seconds = duration;
    
    va_start(ap, fmt); /* Initialize the va_list */
    vsnprintf(pmsg.message, PIU_MESSAGE_LEN, fmt, ap);
    va_end(ap);
    
    return prepare_message(target, MAILBOX_PIU_MESSAGE, &pmsg, sizeof(pmsg));  
}

//This function takes a buffer and a buffer size and puts a 
//log prefix in that contains the equipment number and the local time
int make_log_prefix(char *prefix, int max)
{
    int eqnum;
    time_t foo;
    struct tm t;
    
    foo = time(NULL);
    
    localtime_r(&foo, &t);
    
    //If the driver interface module has a current EQ num
    if(driver_stat.equip_num > 0)
    {
        eqnum = driver_stat.equip_num;	//use it so as not to thrash the file system
    }
    else
    {
        eqnum = get_equip_num();	//otherwise, get it off disk
    }

    return snprintf(prefix, max, "EQ# %d %04d-%02d-%02d %02d:%02d:%02d ", eqnum, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
}

//This function is a printf-line interface to format log messages for the server:
//
//	target = blank IPC message to fill with this log entry
//	loglevel = one of the following: LOGLEVEL_DEBUG, LOGLEVEL_WARN, LOGLEVEL_ERROR
//	fmt = printf format
//	... = printf args
//
//	NOTE:  DO NOT UNDER ANY CIRCUMSTANCES TERMINATE A LOG MESSAGE WITH A '\n' CHARACTER!
//
int format_log_message(struct message_record *target, char loglevel, const char *fmt, ...)
{
    int len = 0;
    char payload[MAX_PAYLOAD_LENGTH] = {0};
    va_list ap;		//argument list...

    if(!target || !fmt)
        return -1;
        
    switch(loglevel)
    {
        case LOGLEVEL_DEBUG:
        case LOGLEVEL_WARN:
        case LOGLEVEL_ERROR:
            payload[0] = loglevel;
            len += 1;
            break;
            
        default:
            payload[0] = LOGLEVEL_ERROR;
            len += 1;
            break;
    }
    
    len += make_log_prefix(payload + len, MAX_PAYLOAD_LENGTH - len);
    
    va_start(ap, fmt); /* Initialize the va_list */
    len += vsnprintf(payload + len, MAX_PAYLOAD_LENGTH - len, fmt, ap);
    va_end(ap);
    
    return prepare_message(target, MAILBOX_BILLING_LOG, payload, len);  
}

//This function printf-line interface to format messages for debug and trace purposes
//
int format_trace_message(struct message_record *target, const char *fmt, ...)
{
    int len = 0;
    char payload[MAX_PAYLOAD_LENGTH] = {0};
    va_list ap;		//argument list...

    if(!target || !fmt)
        return -1;
        
    va_start(ap, fmt); /* Initialize the va_list */
    len += vsnprintf(payload + len, MAX_PAYLOAD_LENGTH - len, fmt, ap);
    va_end(ap);
    
    return prepare_message(target, "DEBUG_TRACE", payload, len);  
}

//This function is a printf-line interface to format messages for the driver:
//
//	target = blank IPC message to fill with this log entry
//	loglevel = one of the following: LOGLEVEL_DEBUG, LOGLEVEL_WARN, LOGLEVEL_ERROR
//	fmt = printf format
//	... = printf args
//
//	NOTE:  DO NOT UNDER ANY CIRCUMSTANCES TERMINATE A LOG MESSAGE WITH A '\n' CHARACTER!
//
int format_driver_message(struct message_record *target, char loglevel, const char *fmt, ...)
{
    int len = 0;
    char payload[MAX_PAYLOAD_LENGTH] = {0};
    va_list ap;		//argument list...

    if(!target || !fmt)
        return -1;
        
    switch(loglevel)
    {
        case LOGLEVEL_DEBUG:
        case LOGLEVEL_WARN:
        case LOGLEVEL_ERROR:
        case LOGLEVEL_ACCEPT:
        case LOGLEVEL_REJECT:
        case LOGLEVEL_EVENT:
            payload[0] = loglevel;
            len += 1;
            break;
            
        default:
            payload[0] = LOGLEVEL_ERROR;
            len += 1;
            break;
    }
    
    va_start(ap, fmt); /* Initialize the va_list */
    len += vsnprintf(payload + len, MAX_PAYLOAD_LENGTH - len, fmt, ap);
    va_end(ap);
    
    return prepare_message(target, MAILBOX_DRIVER_NOTIFY, payload, len);  
}

//This function formats a billing log entry into a form ready to pass off to the billdb process:
//
//	target = blank IPC message to fill with this log entry
//	action = string specifying billing action (ACCEPT, REJECT, PASSBACK, etc...)
//	rule =   string specifying which rule (if any) generated this billing entry
//	ruleparam = string specifying the parameter passed to the above rule
//	reason = human readable reason for the reject or accept of this rider
//	credential = string containing the magnetic or RFID credential used to identify the rider
//	logical_card_id = rider ID from pass table
//	cash_value = number of cents paid for this fare if it is a cash fare
//
//	All other required values are supplied by the system status structures maintained by the default status callbacks:
//
//	equipment number, GPS locaction, driver, paddle, route, trip, stop, and stop name
//
int format_billing_message(struct message_record *target, char *action, char *rule, char *ruleparam, char *reason, char *credential, unsigned long long logical_card_id, int cash_value)
{
    int len = 0;
    
    char foo[MAX_PAYLOAD_LENGTH];
    char payload[MAX_PAYLOAD_LENGTH] = {0};
    
    if(!target)
        return -1;
        
    if(!action)	action = "";
    if(!rule)	rule = "";
    if(!ruleparam) ruleparam = "";
    if(!reason)	reason = "";
    if(!credential) credential = "";
    
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", driver_stat.equip_num);		//equipment number (which bus)
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", driver_stat.logged_in_driver);	//which driver
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.paddle);		//which paddle
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.route);		//which route
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.trip);			//trip
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.stop);			//and stop
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", (int)time(NULL));		//the UTC timestamp of now...

    if(gps_stat.gps_good)	//if we have a valid GPS fix, put that into the structure
    {
        len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", gps_stat.lat);		//latitude
        len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", gps_stat.lon);		//latitude
    }
    else			//otherwise, use the location recorded in the paddle
    {
        len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", stop_stat.lat);		//latitude
        len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", stop_stat.lon);		//latitude
    }

    compress_tabs_to_spaces(foo, action, BILLING_ACTION_LEN);					//billing action code
    foo[BILLING_ACTION_LEN] = '\0';
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo);

    compress_tabs_to_spaces(foo, rule, BILLING_RULE_LEN);					//billing rule code
    foo[BILLING_RULE_LEN] = '\0';
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo);

    compress_tabs_to_spaces(foo, ruleparam, BILLING_RULEPARAM_LEN);				//billing rule parameter
    foo[BILLING_RULEPARAM_LEN] = '\0';
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo);

    compress_tabs_to_spaces(foo, reason, BILLING_REASON_LEN);					//billing reason code
    foo[BILLING_REASON_LEN] = '\0';
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo);

    compress_tabs_to_spaces(foo, credential, BILLING_CREDENTIAL_LEN);				//billing credential code
    foo[BILLING_CREDENTIAL_LEN] = '\0';
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo);

    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%lld\t", logical_card_id);			//rider ID from pass table
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", cash_value);			//cash fare in pennies

    compress_tabs_to_spaces(foo, stop_stat.stopname, STOP_NAME_LEN);				//billing stop name
    foo[STOP_NAME_LEN] = '\0';
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo);	

    //This field is not used by the database, but distinguishes multiple identical cash fares per second
    len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%llu", get_usec_time());					//NOTE:  This field ends without a newline OR tab, the newline is added by the billdb module before being sent

    return prepare_message(target, MAILBOX_BILLING_LOG, payload, len);
}

/*
int main(int argc, char **argv)
{
    
    struct message_record msg;
    
    stop_stat.lat = 9999.99999f;
    stop_stat.lon = 9999.99999f;
    stop_stat.route = 99999;
    stop_stat.paddle = 99999;
    stop_stat.trip = 99;
    stop_stat.stop = 99;
    strcpy(stop_stat.stopname, "ABCDEFGIJKABCDEFGIJKABCDEFGIJKABCDEFGIJKABCDEFGIJKABCDEFGIJK123");
    driver_stat.logged_in_driver = 12345;
    driver_stat.equip_num = 12345;
        
    format_billing_message(&msg, "ACCEPT1ACCEPT2_", "RULE0123456789012345678", "PARA0123456789012345678", "R012345678900123456789001234567890", "C012345678900123456789001234567890", 9999);
    
    printf("%s", msg.payload);
    
    return 0;
}
*/

