/*
 * 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 <wait.h>

#include "../common/common_defs.h"
#include "../commhub/commhub.h"
#include "../commhub/client_utils.h"
#include "fbutil.h"
#include "expat-2.0.1/lib/expat.h"
#include "menu.h"



char *nodetype_words[NUM_NODETYPES] = {"driver_ui", "menu", "action", "widget", "class"};
char *action_words[NUM_ACTIONS] = {"callrule", "setvar", "append", "backspace", "setmenu", "login", "logout", "setpaddle", "prevstop", "nextstop", "seteqnum", "geteqnum", "clearmessages", "shellcall"};
char *widget_words[NUM_WIDGETS] = {"init", "button", "statusbar", "messages", "input", "text", "box", "filled_box", "line", "fullstatus"};
char *font_words[NUM_FONTS] = {"small", "medium", "large"};

extern int diu_fd;
extern int hub_fd;

extern int token_diag_serial;
extern char token_diag_string[LINE_BUFFER_SIZE];

//---------------------------------------------------------------
//  This structure is used to keep a buffer of messages to the driver
//---------------------------------------------------------------
typedef struct diu_message_line_struct
{
    pixel bgcolor;
    pixel textcolor;
    int num;
    
    char message[DIU_MESSAGE_CHARS];
    
} diu_message_line;

diu_message_line diu_messages[DIU_MESSAGE_LINES] = {{0}};
int diu_message_head = 0;
int diu_message_num = 0;
//--------------------------------------------------------------

void clear_diu_messages()
{
    diu_message_head = 0;
    diu_message_num = 0;

    memset(diu_messages, 0, sizeof(diu_messages));    
}

void add_diu_message(pixel bgcolor, pixel textcolor, char *message)
{
    diu_messages[diu_message_head].bgcolor = bgcolor;
    diu_messages[diu_message_head].textcolor = textcolor;
    strncpy(diu_messages[diu_message_head].message, message, DIU_MESSAGE_CHARS - 1);
    diu_messages[diu_message_head].message[DIU_MESSAGE_CHARS - 1] = '\0';
    diu_messages[diu_message_head++].num = diu_message_num++;
    
    if(diu_message_head >= DIU_MESSAGE_LINES)
    {
        diu_message_head = 0;
    }
    
//    printf("Head = %d, msg = %s\n", diu_message_head, message);
}

void replace_diu_message(pixel bgcolor, pixel textcolor, char *message)
{
    int tmp_head = diu_message_head - 1;
    
    if(tmp_head < 0)
    {
        tmp_head = DIU_MESSAGE_LINES - 1;
    }

    diu_messages[tmp_head].bgcolor = bgcolor;
    diu_messages[tmp_head].textcolor = textcolor;
    strncpy(diu_messages[tmp_head].message, message, DIU_MESSAGE_CHARS - 1);
    diu_messages[tmp_head].message[DIU_MESSAGE_CHARS - 1] = '\0';
    diu_messages[tmp_head].num = diu_message_num++;
}



//This function does an inefficient linear search for an attribute value on a menu node
//It is rarely used since most common widget and menu parameters are pre-parsed at load time
//inline char *get_attr_val(menunode *p, char *attr)
char *get_attr_val(menunode *p, char *attr)
{
    int i;
    
    if(!p || ! attr)
    {
        return NULL;
    }
    
    for(i = 0; i < p->keys; i++)
    {
        if(!strcasecmp(p->key[i], attr))
        {
            return p->value[i];
        }
    }
    
    return NULL;
}

//inline int attr_match(menunode *p, char *attr, char *value)
int attr_match(menunode *p, char *attr, char *value)
{
    char *val = get_attr_val(p, attr);
    
    if(!val || !value)
    {
        return 0;
    }
    
    return !strcasecmp(val, value);
}

time_t paddle_req_timeout = 0;
set_paddle_req paddle_req = {0};

char *paddle_ok_menu = "";
char *paddle_fail_menu = "";

driver_status my_driver_status={0};
int update_driver_status = 0;

//This function searches the driver file and checks a driver, pin pair returning 0 on success
//or -1 on failure.
int driver_login(int drivernum, char *pinnum)
{
    FILE *f;
    char name[DRIVER_NAME_LEN]={0};
    char pin[64];
    char line[LINE_BUFFER_SIZE]={0};
    char *inc_line;
    struct message_record outgoing_msg;
  
  
    int drv;
    int retval;

    if(!drivernum || !pinnum) { return -1; }  
    
    f=fopen(DRIVERS_FILE,"rb");  			//open the driver file
  
    if(!f)
    {
        format_log_message(&outgoing_msg, LOGLEVEL_ERROR, "Cannot open %s\n", DRIVERS_FILE);
        send_message(hub_fd,&outgoing_msg);
        return -1;						//fail if we can't read it
    }
    
    while(!feof(f))					//while we haven't hit EOF
    {
        if(!fgets(line,sizeof(line)-1,f))   //read it line by line
            break;

        line[sizeof(line)-1] = '\0';          //terminating each line safely

        inc_line = line;

        while(1)				//and ignoring any leading whitespace
        {
            char c = *inc_line;
    
            if( (c == ' ') ||(c == '\t') ||(c == '\r') ||(c == '\n') )
            {
                inc_line++;
                continue;
            }
    
            break;
        }
  
        if( (inc_line[0] == '#') || (inc_line[0] == '\0'))                      //and ignoring blank/comment lines
            continue;
    
        name[0]='\0';
        pin[0]='\0';        
  
        retval=sscanf(inc_line,"%d %63[0123456789] %63[^\r\n]",&drv,pin,name);	//parse the line format (drivernum pin_num drivername)

        if(retval >= 2)						//if we get at least a driver number and pin number
        {
            if(drv != drivernum)					//check to see if we've found a match
                continue;
        
            if(!strcmp(pinnum, pin))		//if the PIN matches
            {
                my_driver_status.logged_in_driver = drivernum;
                strncpy(my_driver_status.driver_name, name, DRIVER_NAME_LEN);
                my_driver_status.driver_name[DRIVER_NAME_LEN - 1] = '\0';
                my_driver_status.equip_num = get_equip_num();
                
                format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Successful login for driver %d\n", drivernum);
                send_message(hub_fd,&outgoing_msg);

                update_driver_status |= 1;
                fclose(f);
                return 0;
            }
            else
            {
                format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Failed login for driver %d\n", drivernum);
                send_message(hub_fd,&outgoing_msg);
                fclose(f);
                return -1;
            }      
        }    
    }

    format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Failed login for unknown driver %d\n", drivernum);
    send_message(hub_fd,&outgoing_msg);
    fclose(f);
    return -1;
}

//Log a driver out and cancel any pending set paddle request
int driver_logout()
{
    struct message_record outgoing_msg;
    
    if(my_driver_status.logged_in_driver)
    {
        format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Driver logout for driver %d\n", my_driver_status.logged_in_driver);
        send_message(hub_fd,&outgoing_msg);
    }
   
    my_driver_status.logged_in_driver = 0;
    my_driver_status.driver_name[0] = '\0';
    paddle_req_timeout = 0;    
    update_driver_status |= 1;
    return 0;
}

//This gets the value of a menu variable by name.  Again not the fastest way, but it is simple
//and these operations don't occur very often
static char *menuvar_getval(menutree *mt, char *ident)
{
    menuvar *p;

    if(!mt || !ident)
    {
        return NULL;
    }
  
    p = mt->menuvars;
    
    while(p)
    {
        if(!strcasecmp(p->ident, ident))
        {
            return p->value;
        }
        
        p = p->next;
    }
    
    return NULL;
}

static int menuvar_setval(menutree *mt, char *ident, char *val, int limit)
{
    menuvar *p, *q;

    if(!mt || !ident)
    {
        return -1;
    }
  
    if(val == NULL)
    {
        val = "";
    }

    if( (limit < 1) || (limit >= MENUVAR_VALUELEN) )
    {
        limit = MENUVAR_VALUELEN - 1;
    }
  
    q = NULL;
    p = mt->menuvars;
    
    while(p)
    {
        if(!strcasecmp(p->ident, ident))
        {
            break;
        }
        
        q = p;
        p = p->next;
    }

    if(p == NULL)
    {
        p = (menuvar *)malloc(sizeof(menuvar));
        
        if(p == NULL)
        {
            return -1;
        }
        
        p->next = NULL;
        
        strncpy(p->ident, ident, MENUVAR_IDENTLEN);
        p->ident[limit] = '\0';
        
        if(q)
        {
            q->next = p;
        }
        else
        {
            mt->menuvars = p;
        }
    } 

    strncpy(p->value, val, limit);
    p->value[limit]='\0';
    return 0;   
}

static int menuvar_append(menutree *mt, char *ident, char *val, int limit)
{
    char temp[MENUVAR_VALUELEN] = {0};
    char *getret;
    
    if(!mt || !ident)
    {
        return -1;
    }
    
    if(val == NULL)
    {
        val = "";
    }

    getret = menuvar_getval(mt, ident);
    
    if(getret == NULL)
    {
        getret = "";
    }
    
    snprintf(temp, MENUVAR_IDENTLEN, "%s%s", getret, val);
    
    return menuvar_setval(mt, ident, temp, limit);
}

static int menuvar_backspace(menutree *mt, char *ident)
{
    char *getret;
    int n;
    
    if(!mt || !ident)
    {
        return -1;
    }

    //NOTE:  ---- We are counting on the fact that menuvar_getvalue returns a pointer to the ACTUAL string
    getret = menuvar_getval(mt, ident);	
    
    if(getret == NULL)
    {
        return -1;
    }
    
    n = strlen(getret);
    
    if(n > 0)
    {    
        getret[--n] = '\0';		//so we can modify it in this ridiculous way...
    }
    return 0;
}

//This function appends a node to the tail of child list of the node at the top of the stack
static int append_node(menutree *mt, menunode *p)
{
    if(mt->stack[mt->sp].n == NULL)
    {
        mt->head = p;
        return 0;
    }
    
    if(mt->stack[mt->sp].n->subtree == NULL)
    {
        mt->stack[mt->sp].n->subtree = p;
    }
    else
    {
        if(mt->stack[mt->sp].n->subtree->next == NULL)
        {
            mt->stack[mt->sp].n->subtree->next = p;
            mt->stack[mt->sp].tail = p;
        }
        else
        {
            mt->stack[mt->sp].tail->next = p;
            mt->stack[mt->sp].tail = p;
        }
    }

    return 0;
}

//This pushes the menu stack and sets the parser to expect the noted type of node
static int push_menustack(menutree *mt, menunode *p, menunode_type t)
{
    if(mt->sp < (MENU_STACK_DEPTH - 1))
    {
        mt->sp++;
        
        mt->stack[mt->sp].n = p;
        mt->stack[mt->sp].expect = t;
        mt->stack[mt->sp].tail = NULL;
            
        return 0;    
    }
    
    return -1;
}

//This function takes the attribute list in the libExpat style and allocates a local copy to live
//in the menu node (unfolding the alternate index key/value scheme in the process)
static int gather_args(menunode *p, const char **args)
{
    int count = 0;
    int i;

    if(p == NULL)
        return -1;
        
    if(args == NULL)
        return 0;
    
    count = 0;
    
    while(args[count * 2])
    {
        count++;
    }
    
    p->keys = count;

    if(count == 0)
    {
        return 0;
    }
    
    p->key = (char **)malloc(sizeof(char *) * count);
    
    if(p->key == NULL)
    {
        p->keys = 0;
        fprintf(stderr,"Cannot allocate memory for key list!\n");
        return -1;
    }
    
    p->value = (char **)malloc(sizeof(char *) * count);

    if(p->key == NULL)
    {
        free(p->key);
        p->key = NULL;
        p->keys = 0;
        fprintf(stderr,"Cannot allocate memory for value list!\n");
        return -1;
    }

    for(i=0; i < count; i++)
    {
        p->key[i] = strdup(args[i * 2]);
        
        if(p->key[i] == NULL)
        {
            int j;
            
            for(j=0; j < i; j++)
            {
                free(p->key[j]);
                free(p->value[j]);
            }
            
            free(p->key);
            free(p->value);
            
            p->key = NULL;
            p->value = NULL;
            p->keys = 0;

            fprintf(stderr,"Cannot allocate memory for key!\n");
            return -1;
        }
        
        p->value[i] = strdup(args[(i * 2) + 1]);

        if(p->value[i] == NULL)
        {
            int j;
            
            for(j=0; j < i; j++)
            {
                free(p->key[j]);
                free(p->value[j]);
            }
            
            free(p->key[i]);
            
            free(p->key);
            free(p->value);

            p->key = NULL;
            p->value = NULL;
            p->keys = 0;

            
            fprintf(stderr,"Cannot allocate memory for value!\n");
            return -1;
        }
    }
    
    return 0;
}

static menunode *alloc_menunode(menunode_type t)
{
    menunode *r;
  
    r = (menunode *)malloc(sizeof(menunode));
                
    if(r == NULL)
    {
        return NULL;
    }
    
    memset(r, 0, sizeof(menunode));
    
    r->type = t;
    
    return r;
}

#define SET_WIDGET_BINARY_FLAG(str, flag)	\
            switch((str)[0])			\
            {					\
                case '1':			\
                case 'Y':			\
                case 'y':			\
                    r->wid.flags |= (flag);	\
                    break;			\
                                                \
                case '0':			\
                case 'N':			\
                case 'n':			\
                    r->wid.flags &= ~(flag);	\
                    break;			\
                                                \
                default:			\
                    break;			\
            }					\
//----------------------------------------------


//Go and look through a list of text attributes and extract common ones into packed binary data
//structures for quick widget drawing at runtime...
static int extract_common_widget_attributes(menunode *r)
{
    if(!r)
        return -1;

    int j,k;

    for(j = 0; j < r->keys; j++)
    {
        if(!strcasecmp(r->key[j], "x"))
        {
            r->wid.x = atoi(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "y"))
        {
            r->wid.y = atoi(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "width") || !strcasecmp(r->key[j], "w"))
        {
            r->wid.width = atoi(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "height") || !strcasecmp(r->key[j], "h"))
        {
            r->wid.height = atoi(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "font"))
        {
            for(k=0; k < NUM_FONTS; k++)
            {
                if(!strcasecmp(r->value[j], font_words[k]))
                {
                    r->wid.font = k;
                    break;
                }
            }
        }
        else if(!strcasecmp(r->key[j], "label") || !strcasecmp(r->key[j], "text"))
        {
            r->wid.text = r->value[j];
        }
        else if(!strcasecmp(r->key[j], "color"))
        {
            r->wid.color = string_to_color(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "bgcolor"))
        {
            r->wid.bgcolor = string_to_color(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "textcolor"))
        {
            r->wid.textcolor = string_to_color(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "hotcolor"))
        {
            r->wid.hotcolor = string_to_color(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "disabled"))
        {
            SET_WIDGET_BINARY_FLAG(r->value[j], WF_DISABLED);
        }
        else if(!strcasecmp(r->key[j], "hidden"))
        {
            SET_WIDGET_BINARY_FLAG(r->value[j], WF_DONTDRAW);
        }
        else if(!strcasecmp(r->key[j], "shadow"))
        {
            SET_WIDGET_BINARY_FLAG(r->value[j], WF_SHADOW);
        }
        else if(!strcasecmp(r->key[j], "password"))
        {
            SET_WIDGET_BINARY_FLAG(r->value[j], WF_PASSWORD);
        }
    }

    return 0;
}

//Do the same for actions
static int extract_common_action_attributes(menunode *r)
{
    if(!r)
        return -1;

    int j;

    for(j = 0; j < r->keys; j++)
    {
        if(!strcasecmp(r->key[j], "limit"))
        {
            r->act.limit = atoi(r->value[j]);
        }
        else if(!strcasecmp(r->key[j], "var") || !strcasecmp(r->key[j], "variable"))
        {
            r->act.var = r->value[j];
        }
        else if(!strcasecmp(r->key[j], "value"))
        {
            r->act.value = r->value[j];
        }
        else if(!strcasecmp(r->key[j], "name"))
        {
            r->act.name = r->value[j];
        }
    }

    return 0;
}

static void XMLCALL start_tag(void *data, const char *element, const char **attrib)
{
    menutree *mt = (menutree *)data;
    
    menunode *r = NULL;

    if(mt->user_error)
        return;

    int i;

//    printf("[%d] BEGIN %s\n", mt->sp, element);

    if(mt->parse_flags & PARSE_IN_CLASS_TAG)
    {
        fprintf(stderr, "%d: <class> tag may not have any children!\n", (int)XML_GetCurrentLineNumber(mt->parser));
        mt->user_error = 1;
        return;
    }
    
    switch(mt->stack[mt->sp].expect)
    {
        case NODE_ROOT:
            if(!strcasecmp(element, "driver_ui"))
            {
                r = alloc_menunode(NODE_ROOT);
                
                if(r)
                {
                    if(gather_args(r, attrib))
                    {
                        free(r);
                        fprintf(stderr, "%d: Error loading attributes!\n",  (int)XML_GetCurrentLineNumber(mt->parser));
                        mt->user_error = 1;
                        return;
                    }
                    
                    append_node(mt, r);
                    push_menustack(mt, r, NODE_MENU);
                }
            }
            else
            {
                fprintf(stderr, "%d: Expecting root tag of <driver_ui>\n", (int)XML_GetCurrentLineNumber(mt->parser));
                mt->user_error = 1;
            }
            break;
            
        case NODE_MENU:
            if(!strcasecmp(element, "menu"))
            {
                r = alloc_menunode(NODE_MENU);
                
                if(r)
                {
                    if(gather_args(r, attrib))
                    {
                        free(r);
                        fprintf(stderr, "%d: Error loading attributes!\n",  (int)XML_GetCurrentLineNumber(mt->parser));
                        mt->user_error = 1;
                        return;
                    }

                    append_node(mt, r);
                    push_menustack(mt, r, NODE_WIDGET);
                }
            }
            else if(!strcasecmp(element, "class"))
            {
                r = alloc_menunode(NODE_CLASS);
                
                if(r)
                {
                    if(gather_args(r, attrib))
                    {
                        free(r);
                        fprintf(stderr, "%d: Error loading attributes!\n",  (int)XML_GetCurrentLineNumber(mt->parser));
                        mt->user_error = 1;
                        return;
                    }

                    extract_common_widget_attributes(r);
                    
                    r->next = mt->classlist;
                    mt->classlist = r;
                    mt->parse_flags |= PARSE_IN_CLASS_TAG;
                    return; 
                    
                }
                
            }
            else
            {
                fprintf(stderr, "%d: Expecting <class/> or <menu> tag!\n", (int)XML_GetCurrentLineNumber(mt->parser));
                mt->user_error = 1;
            }
            break;
            
        case NODE_ACTION:
            for(i = 0; i < NUM_ACTIONS; i++)
            {
                if(!strcasecmp(element, action_words[i]))
                {
                    r = alloc_menunode(NODE_ACTION);
                
                    if(r)
                    {
                        if(gather_args(r, attrib))
                        {
                            free(r);
                            fprintf(stderr, "%d: Error loading attributes!\n",  (int)XML_GetCurrentLineNumber(mt->parser));
                            mt->user_error = 1;
                            return;
                        }
                        
                        r->act.type = i;
         
                        extract_common_action_attributes(r);
                        
                        append_node(mt, r);
                        push_menustack(mt, r, NODE_ACTION);
                        return;                        
                    }
                    else
                    {
                        fprintf(stderr, "%d: Out of memory!\n", (int)XML_GetCurrentLineNumber(mt->parser));
                        mt->user_error = 1;
                        return;
                    }
                }
            }
            
            fprintf(stderr, "%d: Expecting action tag!\n", (int)XML_GetCurrentLineNumber(mt->parser));
            mt->user_error = 1;
            break;
            
        case NODE_WIDGET:
            for(i = 0; i < NUM_WIDGETS; i++)
            {
                if(!strcasecmp(element, widget_words[i]))
                {
                    r = alloc_menunode(NODE_WIDGET);
                
                    if(r)
                    {
                        int j;
                        
                        if(gather_args(r, attrib))
                        {
                            free(r);
                            fprintf(stderr, "%d: Error loading attributes!\n",  (int)XML_GetCurrentLineNumber(mt->parser));
                            mt->user_error = 1;
                            return;
                        }
                        
                        
                        //Scan the attribute list for a widget class specifier to pick defaults from
                        for(j = 0; j < r->keys; j++)
                        {
                            if(!strcasecmp(r->key[j], "class"))	//if we have a class specifier
                            {
                                menunode *pp = mt->classlist;	//start looking through the class pist
                                
                                while(pp)
                                {
                                    if(attr_match(pp, "name", r->value[j]))	//If the name of this class matches our class request
                                    {
                                        //copy the attribues from the class to our widget
                                        memcpy(&r->wid, &pp->wid, sizeof(struct widget_struct));
                                        break;  //break the while(pp)
                                    }
                                    
                                    pp = pp->next;    
                                }
                                
                                if(pp == NULL)	//if we exhausted the list but found no match, complain bitterly...
                                {
                                    fprintf(stderr, "%d: Widget invokes unknown class \"%s\"!\n", (int)XML_GetCurrentLineNumber(mt->parser), r->value[j]);
                                    mt->user_error = 1;
                                }
                                break;
                            }
                        }

                        //Now that the widget class data have been copied, it's safe to override the type to our actual type.
                        r->wid.type = i;
                        
                        extract_common_widget_attributes(r);
                        
                        append_node(mt, r);
                        push_menustack(mt, r, NODE_ACTION);
                        return;                        
                    }
                    else
                    {
                        fprintf(stderr, "%d: Out of memory!\n", (int)XML_GetCurrentLineNumber(mt->parser));
                        mt->user_error = 1;
                        return;
                    }
                }
            }
            
            
            fprintf(stderr, "%d: Expecting widget tag!\n", (int)XML_GetCurrentLineNumber(mt->parser));
            mt->user_error = 1;
            break;
        
        default:
            break;
    }
}

static void XMLCALL end_tag(void *data, const char *element)
{
    menutree *mt = (menutree *)data;

    if(mt->user_error)
        return;

//    printf("[%d] END %s\n", mt->sp, element);

    if(!strcasecmp(element,"class"))	//if this is the tail end of a <class/> tag, clear the in_class flag
    {
        mt->parse_flags &= ~PARSE_IN_CLASS_TAG;
    }
    else	//otherwise, pop the menu stack
    {    
        mt->sp--;
    }
}


void free_menunode(menunode *r)
{
    int i;
    
    if(r->subtree)
    {
        free_menunode(r->subtree);
    }

    if(r->next)
    {
        free_menunode(r->next);
    }

    if(r->key)
    {
        for(i=0; i < r->keys; i++)
        {
            if(r->key[i])
            {
                free(r->key[i]);
            }
        }
        
        free(r->key);
    }
    
    if(r->value)
    {
        for(i=0; i < r->keys; i++)
        {
            if(r->value[i])
            {
                free(r->value[i]);
            }
        }
        
        free(r->value);
    }

    free(r);    
}

void free_menuvars(menuvar *v)
{
    menuvar *p, *q;
    
    p = v;
    q = NULL;
    
    while(p)
    {
        q = p;
        p = p->next;
        free(q);
    }
}

void free_menutree(menutree *mt)
{
    if(!mt)
        return;

    driver_logout();

    if(mt->head)
    {
        free_menunode(mt->head);
    }
    
    if(mt->classlist)
    {
        free_menunode(mt->classlist);
    }

    if(mt->menuvars)
    {
        free_menuvars(mt->menuvars);
    }
    
    free(mt);
}


menutree *load_menutree(char *fname)
{
    menutree *mt = NULL;
    FILE *f = NULL;
    char chunk[XML_CHUNK_SIZE] = {0};

    int len = 0;
    int done = 0;

    XML_Parser p = NULL;
    
    if(!fname)
        return NULL;
        
    f = fopen(fname, "rb");
    
    if(!f)
    {
        fprintf(stderr, "Cannot read menutree file %s!\n", fname);
        return NULL;
    }
    
    p = XML_ParserCreate(NULL);   

    if(!p)
    {
        fprintf(stderr,"No memory for parser!\n");
        fclose(f);
        return NULL;
    }
    
    XML_SetElementHandler(p, start_tag, end_tag);
    
    mt = (menutree *)malloc(sizeof(menutree));
    
    if(!mt)
    {
        fprintf(stderr, "Cannot allocate memory for menutree!\n");
        fclose(f);
        XML_ParserFree(p);
        return NULL;
    }
    
    memset(mt, 0, sizeof(menutree));

    XML_SetUserData(p, (void *)mt);
    mt->parser = p;
        
    while(!done)
    {
        len = fread(chunk, 1, XML_CHUNK_SIZE, f);        
    
        if(feof(f))
        {
            done = 1;
        }
        
        if( XML_Parse(p, chunk, len, done) == XML_STATUS_ERROR )
        {
            fprintf(stderr, "Parse error at line %d: %s\n", (int)XML_GetCurrentLineNumber(p), XML_ErrorString(XML_GetErrorCode(p)));
            fclose(f);
            XML_ParserFree(p);
            free_menutree(mt);
            return NULL;
        }
        
        if(mt->user_error)
        {
            fclose(f);
            XML_ParserFree(p);
            free_menutree(mt);
            return NULL;
        }
    }

    fclose(f);   
    XML_ParserFree(p);
    mt->parser = NULL;
    
    return mt;
}


void print_menu_tree_inner(menunode *p, int indent)
{
    int i;
    for(i=0; i < indent; i++)
    {
        printf("    ");
    }

    printf("[BEGIN %s]", nodetype_words[p->type]);
    
    switch(p->type)
    {
        case NODE_ACTION:
            printf(" (%s)", action_words[p->act.type]);
            break;
    
        case NODE_WIDGET:
            printf(" (%s)", widget_words[p->wid.type]);
            break;
    
        default:
            break;
    }

    for(i = 0; i < p->keys; i++)
    {
        printf(" %s=\"%s\"", p->key[i], p->value[i]);
    }
    printf("\n");

    if(p->subtree)
    {
        print_menu_tree_inner(p->subtree, indent + 1);
    }

    for(i=0; i < indent; i++)
    {
        printf("    ");
    }
    printf("[END %s]\n", nodetype_words[p->type]);

    if(p->next)
    {
        print_menu_tree_inner(p->next, indent);
    }
}

void print_widget_class_list(menunode *classes)
{
    menunode *p = classes;
    int i;
    
    while(p)
    {
        if(p->type != NODE_CLASS)
        {
            printf("\tExpecting widget class...\n");
            p = p->next;
            continue;
        }
        
        printf("\t");
    
        for(i = 0; i < p->keys; i++)
        {
            printf(" %s=\"%s\"", p->key[i], p->value[i]);
        }
        printf("\n");
        
        p = p->next;
    }
}

void print_menu_tree(menutree *mt)
{
    printf("Widget Classes:\n");
    print_widget_class_list(mt->classlist);
    printf("\nMenu Definitions:\n");
    print_menu_tree_inner(mt->head, 0);
}


void do_menu_action(menutree *mt, menunode *act);

int setmenu(menutree *mt, char *name)
{
    
    menunode *p;
    
    if(!mt)
        return -1;
        
    p = mt->head->subtree;    

    while(p)
    {
        if(attr_match(p, "name", name))
        {
            mt->current = p;
            
            p = p->subtree;
            
            while(p)
            {
                if( (p->type == NODE_WIDGET) && (p->wid.type == WIDGET_MENUINIT) )
                {
                    do_menu_action(mt, p->subtree);    
                }
                
                p = p->next;
            }
            
            return 0;
        }
        
        p = p->next;  
    }

    return -1;
}

int init_menutree(menutree *mt)
{
    
    char *start_menu;
    
    if(!mt)
    {
        return -1;
    }

    if(!mt->head)
    {
        return -1;
    }
    
    if(!mt->head->subtree)
    {
        return -1;
    }

    start_menu = get_attr_val(mt->head, "startup");
    
    if(setmenu(mt, start_menu))
    {
        mt->current = mt->head->subtree;
    }

    return 0;
}


int make_paddle_request(int paddle, char *okmenu, char *failmenu)
{
    struct message_record outgoing_msg;
    
    paddle_req.result = 0;
    paddle_req.request = paddle;
    paddle_req_timeout = time(NULL) + PADDLE_REQ_TIMEOUT;
    
    paddle_ok_menu = okmenu;
    paddle_fail_menu = failmenu;
    
    prepare_message(&outgoing_msg, MAILBOX_SET_PADDLE, &paddle_req, sizeof(paddle_req));
    return send_message(hub_fd, &outgoing_msg);
}

int check_paddle_request(menutree *mt)
{
    struct message_record outgoing_msg;

    if(paddle_req_timeout != 0)
    {
    
        if(paddle_req.request == paddle_req.result)
        {
            format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Set Paddle %d\n", paddle_req.request);
            send_message(hub_fd, &outgoing_msg);
            paddle_req_timeout = 0;
            setmenu(mt, paddle_ok_menu);
            return 1;
        }
        else if( (paddle_req.result < 0) || (paddle_req_timeout < time(NULL)) )
        {
            format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "Failed to set Paddle %d\n", paddle_req.request);
            send_message(hub_fd, &outgoing_msg);
            paddle_req.result = 0;
            paddle_req.request = 0;
            paddle_req_timeout = 0;
            setmenu(mt, paddle_fail_menu);
            return 1;
        }
    }
    
    return 0;
}

void action_nextstop()
{
    struct message_record outgoing_msg;
    prepare_message(&outgoing_msg, MAILBOX_NEXT_STOP, "", 0);
    send_message(hub_fd, &outgoing_msg);
}

void action_prevstop()
{
    struct message_record outgoing_msg;
    prepare_message(&outgoing_msg, MAILBOX_PREV_STOP, "", 0);
    send_message(hub_fd, &outgoing_msg);
}


int draw_menu(menutree *mt)
{
    menunode *p;
    
    if(!mt->current)
    {
        return -1;
    }

    p = mt->current->subtree;

    int x, y;
    int xx, yy;

    int tmp_flags;

    set_rawcolor(FBCOLOR_WHITE);
    cls();

    while(p)
    {
        if(p->type != NODE_WIDGET)
        {
            fprintf(stderr, "Expecting Widget!\n");
            print_menu_tree_inner(p, 2);
            p = p->next;
            continue;
        }
        
        if(p->wid.flags & WF_DONTDRAW)
        {
            p = p->next;
            continue;
        }
        
        tmp_flags = p->wid.flags;
        
        if(p == mt->pressed)
        {
            tmp_flags = mt->pressed_flags;
        }
    
        x = p->wid.x * GRID;
        y = p->wid.y * GRID;
        xx = (p->wid.x + p->wid.width) * GRID;
        yy =  (p->wid.y + p->wid.height) * GRID;
                
        switch(p->wid.type)
        {
            case WIDGET_BUTTON:
                
                if(tmp_flags & WF_SHADOW)
                {
                    select_blend_mode(BLENDMODE_LIGHT);
                    set_rawcolor(FBCOLOR_BLACK | FBCOLOR_TRANSLUCENT);

                    if(tmp_flags & WF_PRESSED)
                    {
                        set_pos(x + SHADOW_OFFSET_DOWN, y + SHADOW_OFFSET_DOWN);
                        box_fill(xx + SHADOW_OFFSET_DOWN, yy + SHADOW_OFFSET_DOWN);
                    }
                    else
                    {
                        set_pos(x + SHADOW_OFFSET_UP, y + SHADOW_OFFSET_UP);
                        box_fill(xx + SHADOW_OFFSET_UP, yy + SHADOW_OFFSET_UP);
                    }
                    select_blend_mode(BLENDMODE_DARK);
                }
                
                if(tmp_flags & WF_PRESSED)
                {
                    set_rawcolor(p->wid.hotcolor);
                }
                else
                {
                    set_rawcolor(p->wid.color);
                }
               
                set_pos(x, y);
                box_fill(xx, yy);
                
                set_rawcolor(FBCOLOR_BLACK);
                
                set_pos(x, y);
                box(xx, yy);
                
                if(p->wid.text)
                {
                    set_clip_rect(x + 1, y + 1, xx, yy);
                                    
                    int strw, strh;
                    int dx, dy;
                    
                    set_font(p->wid.font);
                    
                    font_cell_size(&strw, &strh);
                    
                    strw *= strlen(p->wid.text);                  
                
                    dx = ((xx - x) - strw) / 2;
                    dy = ((yy - y) - strh) / 2;
                    
                    set_pos(x + dx, y + dy);
                    
                    set_rawcolor(p->wid.textcolor);
                    print_string(p->wid.text, 0);
                    
                    reset_clip_rect();                    
                }
                break;
                
            case WIDGET_TEXT:
                {
                    char *label = (p->wid.text)?p->wid.text:"";
                    
                    set_pos(x, y);
                    set_font(p->wid.font);
                    set_rawcolor(p->wid.textcolor);
                    print_string(label, 0);
                }
                break;
        
            case WIDGET_BOX:
                set_pos(x, y);
                set_rawcolor(p->wid.color);
                box(xx, yy);
                break;

            case WIDGET_FILLEDBOX:
                set_pos(x, y);
                set_rawcolor(p->wid.color);
                box_fill(xx, yy);
                break;

            case WIDGET_LINE:
                set_pos(x, y);
                set_rawcolor(p->wid.color);
                line_to(xx, yy);
                break;
        
            case WIDGET_MESSAGES:
                {
                    int charw, charh;
                    int numlines;
                    int idx;
                    int dy;
                    
                    int numchars;
                    int splitchars;
                    char tmptext[DIU_MESSAGE_CHARS];
                    
                    set_rawcolor(p->wid.color);
                    set_pos(x, y);
                    box_fill(xx, yy);
                
                    set_rawcolor(FBCOLOR_BLACK);
                
                    set_pos(x, y);
                    box(xx, yy);
                    
                    set_clip_rect(x + 1, y + 1, xx, yy);

                    set_font(p->wid.font);
                    font_cell_size(&charw, &charh);
                    
                    numlines = ((p->wid.height * GRID) - 2) / charh;
                    numchars = ((p->wid.width * GRID) - 2) / charw;
                    
                    splitchars = numchars - 5;
                    
                    idx = diu_message_head - 1;
                    
                    if(idx < 0) idx += DIU_MESSAGE_LINES;
                    
                    dy = (yy - 1) - charh;
                    
                    while(numlines)
                    {
//                        printf("dy = %d, idx = %d, numlines = %d\n", dy, idx, numlines);
                        if(diu_messages[idx].message[0])
                        {
                            set_rawcolor(diu_messages[idx].bgcolor);
                            set_pos(x+1, dy);
                            box_fill(xx - 1, dy + charh);
                            set_pos(x+1, dy);
                            set_rawcolor(diu_messages[idx].textcolor);
                            snprintf(tmptext, DIU_MESSAGE_CHARS, "%-*.*s (%02d)", splitchars, splitchars, diu_messages[idx].message, diu_messages[idx].num);
                            print_string(tmptext, 0);
                            
//                            printf("Printed \"%s\" at %d,%d\n", tmptext, x+1, dy);
                        }
                        
                        dy -= charh;
                        numlines--;
                        
                        idx--;
    
                        if(idx < 0) idx += DIU_MESSAGE_LINES;
                    }
                    
                    reset_clip_rect();                    
                }
                break;
            
            case WIDGET_INPUT:
                if(tmp_flags & WF_SHADOW)
                {
                    select_blend_mode(BLENDMODE_LIGHT);
                    set_rawcolor(FBCOLOR_BLACK | FBCOLOR_TRANSLUCENT);

                    if(tmp_flags & WF_PRESSED)
                    {
                        set_pos(x + SHADOW_OFFSET_DOWN, y + SHADOW_OFFSET_DOWN);
                        box_fill(xx + SHADOW_OFFSET_DOWN, yy + SHADOW_OFFSET_DOWN);
                    }
                    else
                    {
                        set_pos(x + SHADOW_OFFSET_UP, y + SHADOW_OFFSET_UP);
                        box_fill(xx + SHADOW_OFFSET_UP, yy + SHADOW_OFFSET_UP);
                    }
                    select_blend_mode(BLENDMODE_DARK);
                }
                
                set_rawcolor(p->wid.color);
                set_pos(x, y);
                box_fill(xx, yy);
                
                set_rawcolor(FBCOLOR_BLACK);
                
                set_pos(x, y);
                box(xx, yy);
                
                {
                    char *varname;                      
                    char *content;
                    char *label;
                    int strw, strh;
                    int dx, dy;
                    
                    label = (p->wid.text)?p->wid.text:"";
                    
                    varname = get_attr_val(p, "var");
                    
                    if(varname == NULL)
                    {
                        fprintf(stderr, "Input box lacks content variable!\n");
                        fflush(stderr);
                        content = "";
                    }
                    else
                    {
                        content = menuvar_getval(mt, varname);
                      
                        if(content == NULL)
                        {
                            content = "";
                        }
                    }
                    
                    set_clip_rect(x + 1, y + 1, xx, yy);
                    
                    set_font(p->wid.font);
                    
                    font_cell_size(&dx, &strh);
                    
                    strw = dx * strlen(label);
                    
                    dy = ((yy - y) - strh) / 2;
                
                    set_pos(x + dx, y + dy);
                    
                    set_rawcolor(p->wid.textcolor);
                    print_string(label, 0);
                    
                    dx += strw;
                    
                    if(p->wid.flags & WF_PASSWORD)
                    {
                        int n;
                        int i;
                        
                        char crapval[MENUVAR_VALUELEN] = {0};
                        
                        n = strlen(content);
                        
                        if(n >= MENUVAR_VALUELEN)
                        {
                            n = MENUVAR_VALUELEN - 1;
                        }
                        
                        for(i=0; i < n; i++)
                        {
                            crapval[i]='*';
                        }
                        
                        set_pos(x + dx, y + dy);
                        print_string(crapval, 0);
                        
                    }
                    else
                    {
                        set_pos(x + dx, y + dy);
                        print_string(content, 0);
                    }
                    
                    reset_clip_rect();                    
                }
                break;
        
            case WIDGET_STATUSBAR:
                {
                    char statline[DIU_MESSAGE_CHARS];
                    char date_str[32];
                    time_t t;
                    struct tm tm;

                    
                    switch(pass_stat.flush_status)
                    {
                        case FLUSH_STATUS_DOWNLOAD:
                            snprintf(statline, DIU_MESSAGE_CHARS, "DB Flush Downloading %d%%", pass_stat.progress_indicator);
                            break;
                            
                        case FLUSH_STATUS_APPLY:
                            snprintf(statline, DIU_MESSAGE_CHARS, "DB Flush Applying %d%%", pass_stat.progress_indicator);
                            break;
                            
                        case FLUSH_STATUS_WRITE:
                            snprintf(statline, DIU_MESSAGE_CHARS, "Saving New Database. Please Wait.");
                            break;

                        default:
                            /*
                            snprintf(statline, DIU_MESSAGE_CHARS, "Rte %d Trip %d Stop %d GPS %-3s Tun %-3s", 
                                  stop_stat.route, stop_stat.trip, stop_stat.stop,
                                  gps_stat.gps_good?"YES":"NO", tunnel_is_up()?"YES":"NO");
                                  */


                            t = time(NULL);
                            localtime_r(&t, &tm);
                            strftime(date_str, 32, "%Y-%m-%d", &tm);

                            snprintf(statline, DIU_MESSAGE_CHARS, "Rt %d Trp %d Stop %d GPS %-3s Tun %-3s %s",
                                  stop_stat.route, stop_stat.trip, stop_stat.stop,
                                  gps_stat.gps_good?"YES":"NO", tunnel_is_up()?"YES":"NO",
                                  date_str);

                            break;
                    }

                    set_pos(x, y);
                    set_rawcolor(p->wid.color);
                    box_fill(xx,yy);
                    set_clip_rect(x + 1, y + 1, xx, yy);
                    set_pos(x, y);
                    set_rawcolor(p->wid.textcolor);
                    set_font(p->wid.font);
                    print_string(statline, 0);
                    reset_clip_rect();                                                  
                
                }
                break;
            
            #define MAX_PKGS	(16)
            case WIDGET_FULLSTATUS:
                {
                    int i;
                    int npkgs;
                    FILE *f;
                    
                    char statline[DIU_MESSAGE_CHARS];
                    package_signature pkgs[MAX_PKGS];
                    int dy = 0, dx = 0;
                    int cellwidth, cellheight;
                    struct tm pkgtime;
                
                    set_font(p->wid.font);
                    set_rawcolor(FBCOLOR_BLACK);
                    set_pos(x, y);
                    box(xx,yy);
                    set_clip_rect(x + 1, y + 1, xx, yy);
                    set_pos(x, y);
                    font_cell_size(&cellwidth, &cellheight);
                    
                    dx = cellwidth >> 1;
                    dy = cellheight >> 1;
                    
                    snprintf(statline, DIU_MESSAGE_CHARS, "Tunnel: %-3s  GPRS: %-3s  Eq# %d  #Msg: %d", tunnel_is_up()?"YES":"NO", gprs_is_up()?"YES":"NO", get_equip_num(), bill_stat.unsynced_messages);
                    set_pos(x + dx, y + dy);
                    print_string(statline, 0);
                    dy += cellheight;

                    snprintf(statline, DIU_MESSAGE_CHARS, "Last Token Read (%02d): \"%s\"", token_diag_serial % 100, token_diag_string);
                    set_pos(x + dx, y + dy);
                    print_string(statline, 0);
                    dy += cellheight;

                    if(gps_stat.gps_good)
                    {                    
                        snprintf(statline, DIU_MESSAGE_CHARS, "GPS: nSat = %d Loc = (%11.4f,%11.4f)", gps_stat.num_sats, gps_stat.lat, gps_stat.lon);
                    }
                    else
                    {
                        snprintf(statline, DIU_MESSAGE_CHARS, "GPS: NO / Stale");
                    }
                    
                    set_pos(x + dx, y + dy);
                    print_string(statline, 0);
                    dy += cellheight;
                    
                    dy += cellheight;
                   
                    snprintf(statline, DIU_MESSAGE_CHARS, "Package  Version          Installed");
                    set_pos(x + dx, y + dy);
                    print_string(statline, 0);
                    dy += cellheight;

                    npkgs = find_packages(pkgs, MAX_PKGS);
                    for(i=0; i < npkgs; i++)
                    {
                        localtime_r(&pkgs[i].installed, &pkgtime);
                        
                        snprintf(statline, DIU_MESSAGE_CHARS, "%-8s %-16s %02d/%02d/%02d %02d:%02d:%02d",
                                                              pkgs[i].pkgname, pkgs[i].pkgver,
                                                              pkgtime.tm_mon + 1, pkgtime.tm_mday, pkgtime.tm_year + 1900,
                                                              pkgtime.tm_hour, pkgtime.tm_min, pkgtime.tm_sec);
                                                              
                        set_pos(x + dx, y + dy);
                        print_string(statline, 0);
                        dy += cellheight;
                    }
                    
                    f = fopen("/tmp/net_ids", "rb");
                    
                    if(f)
                    {
                        dy += cellheight;
                        
                        while(fgets(statline, DIU_MESSAGE_CHARS, f))
                        {
                            set_pos(x + dx, y + dy);
                            strip_crlf(statline);
                            print_string(statline, 0);
                            dy += cellheight;
                        }
                        
                        fclose(f);
                    }
                    
                    reset_clip_rect();                    
                
                }
                break;
                
        
            default:
                break;
        }
        
        p = p->next;
    }

    present_framebuffer();
    
    return 0;

}

void do_menu_action(menutree *mt, menunode *act)
{
    menunode *p = act;

    while(p)
    {
        if(p->type != NODE_ACTION)
        {
            fprintf(stderr, "Expecting Action!\n");
            print_menu_tree_inner(p, 2);
            p = p->next;
            break;
        }
        
//        fprintf(stderr,"Action: %d\n", p->act.type);
//        fflush(stderr);
        
        switch(p->act.type)
        {
            case ACTION_SETMENU:

                if(p->act.name)
                {
                    setmenu(mt, p->act.name);
                }
                else
                {
                    fprintf(stderr, "setmenu expects name=\"...\"\n");
                }
                break;
        
            case ACTION_SETVAR:

                if(p->act.var == NULL)
                {
                    fprintf(stderr, "setvar expects var=\"...\"\n");
                }
                else
                {
                    menuvar_setval(mt, p->act.var, p->act.value, p->act.limit);
                }
                break;
                
            case ACTION_APPEND:
       
                if(p->act.var == NULL)
                {
                    fprintf(stderr, "append expects var=\"...\"\n");
                }
                else
                {
                    menuvar_append(mt, p->act.var, p->act.value, p->act.limit);
                }
                break;
            
            case ACTION_BKSPACE:

                if(p->act.var == NULL)
                {
                    fprintf(stderr, "backspace expects var=\"...\"\n");
                }
                else
                {
                    menuvar_backspace(mt, p->act.var);
                }
                break;
                

            case ACTION_LOGIN:
                {
                    char *driver_var, *pin_var, *okmenu, *failmenu;
                    
                    char *driver_val, *pin_val;
                    
                    driver_var = get_attr_val(p, "drivervar");
                    pin_var = get_attr_val(p, "pinvar");
                    okmenu = get_attr_val(p, "okmenu");
                    failmenu = get_attr_val(p, "failmenu");

                    if(!driver_var || !pin_var || !okmenu || !failmenu)
                    {
                        fprintf(stderr, "login expects parameters: drivervar, pinvar, okmenu, failmenu\n");
                    }
                    else
                    {
                        driver_val = menuvar_getval(mt, driver_var);
                        pin_val = menuvar_getval(mt, pin_var);
                        
                        if(!driver_val || !pin_val)
                        {
                            setmenu(mt, failmenu);
                            return;
                        }
                        else
                        {
                            if(driver_login(atoi(driver_val), pin_val))
                            {
                                setmenu(mt, failmenu);
                                return;
                            }
                            else
                            {
                                //If this is the "magic" driver name, go to admin mode menu 
                                if(!strcmp(my_driver_status.driver_name, MAGIC_DRIVER_NAME))
                                {
                                    //(named by the #define'd value of MACIC_DRIVER_MENU)
                                    setmenu(mt, MACIC_DRIVER_MENU);
                                }
                                else
                                {
                                    //otherwise, go the the config-file specified OK menu
                                    setmenu(mt, okmenu);
                                }
                                return;
                            }
                        }
                    }
                }
                break;
                
            case ACTION_SETPADDLE:
                {
                    char *paddle_var, *okmenu, *failmenu;
                    
                    char *paddle_val;
                    
                    paddle_var = get_attr_val(p, "paddlevar");
                    okmenu = get_attr_val(p, "okmenu");
                    failmenu = get_attr_val(p, "failmenu");

                    if(!paddle_var || !okmenu || !failmenu)
                    {
                        fprintf(stderr, "setpaddle expects parameters: paddlevar, okmenu, failmenu\n");
                    }
                    else
                    {
                        paddle_val = menuvar_getval(mt, paddle_var);
                        
                        if(!paddle_val || (atoi(paddle_val) <= 0) )
                        {
                            setmenu(mt, failmenu);
                            return;
                        }
                        else
                        {
                            make_paddle_request(atoi(paddle_val), okmenu, failmenu);
                        }
                    }
                }
                break;
                
            case ACTION_PREVSTOP:
                action_prevstop();
                break;
                
            case ACTION_NEXTSTOP:
                action_nextstop();
                break;
                
            case ACTION_LOGOUT:
                driver_logout();
                break;
        
            case ACTION_RULE:
                {
                    struct message_record outgoing;
                    driver_rulecall foo = {{0}};
                    
                    char *rule_var, *rule_val;
                    char *param_var, *param_val;
                    
                    rule_var = get_attr_val(p, "rulevar");
                    rule_val = get_attr_val(p, "rule");

                    if(rule_val)
                    {
                        if(rule_var)
                        {
                            fprintf(stderr, "callrule expects rulevar OR rule, not both!\n");
                            break;
                        }
                    }
                    else
                    {
                        if(rule_var)
                        {
                            rule_val =  menuvar_getval(mt, rule_var);
                            
                            if(!rule_val)
                            {
                                fprintf(stderr, "callrule expects non-null value in variable %s specified by rulevar!\n", rule_var);
                                break;
                            }
                        }
                        else
                        {
                            fprintf(stderr, "callrule expects rulevar OR rule!\n");
                            break;
                        }
                    }
                    

                    param_var = get_attr_val(p, "paramvar");
                    param_val = get_attr_val(p, "param");

                    if(param_val)
                    {
                        if(param_var)
                        {
                            fprintf(stderr, "callrule expects paramvar OR param, not both!\n");
                            break;
                        }
                    }
                    else
                    {
                        if(param_var)
                        {
                            param_val =  menuvar_getval(mt, param_var);
                            
                            if(!param_val)
                            {
                                param_val = "";
                            }
                        }
                        else
                        {
                            fprintf(stderr, "callrule expects paramvar OR param!\n");
                            break;
                        }
                    }

                    if(hub_fd >= 0)
                    {
                        strncpy(foo.rulename, rule_val, DRIVER_RULECALL_LEN - 1);
                        strncpy(foo.ruleparam, param_val, DRIVER_RULECALL_LEN - 1);
                        
                        prepare_message(&outgoing, MAILBOX_RULE_CALL, &foo, sizeof(foo));
                        send_message(hub_fd, &outgoing);
                    }                            
                }
                break;
                
            case ACTION_SETEQNUM:
                {
                    char *eqval;
                    int retval;

                    
                    if(p->act.var)
                    {          
                        eqval = menuvar_getval(mt, p->act.var);
                    
                    
                        if(eqval)
                        {
                            retval = set_equip_num(strtol(eqval, NULL, 10));
                        
                            if(retval)
                            {
                                fprintf(stderr, "Failed to set Equipment Number to %s\n", eqval);
                            }
                            else
                            {
                                system("/mnt/data2/bin/showmessage -b 'Restarting...'");
                                sleep(1);
                                system("/sbin/shutdown -r now");
                                sleep(30);
                            }
                        }
                    }
                    else
                    {
                        fprintf(stderr, "Action seteqnum expects: var\n");
                    }
                }
                break;
                
            case ACTION_GETEQNUM:
                {
                    char tmp[MENUVAR_VALUELEN];
                    int retval;
                    
                    retval = get_equip_num();
                    
                    snprintf(tmp, MENUVAR_VALUELEN, "%d", retval);
                    
                    if(p->act.var)
                    {
                        menuvar_setval(mt, p->act.var, tmp, p->act.limit);
                    }
                    else
                    {
                        fprintf(stderr, "Action geteqnum expects: var\n");
                    }
                }
                break;

            case ACTION_CLRMSGS:
                {
                     clear_diu_messages();
                }
                break;


            case ACTION_SHELLCALL:
                {
                    char tmp[SHELLCALL_BUFFER_SIZE] = {0};
                    char varname[MENUVAR_IDENTLEN] = {0};
                    
                    char *cmd_val = NULL; 
                    char *var_val = NULL;
                    
                    char *okmenu = NULL;
                    char *failmenu = NULL;
        
                    char c;
        
                    int in_idx = 0;
                    int out_idx = 0;
                    
                    int fmt_mode = 0;
                    int fmt_idx = 0;

                    int ret = 0;
                    
                    int fail_flag = 0;
                    
                    //Gather the command line template before we run it through formatting
                    cmd_val = get_attr_val(p, "cmdline");
                    
                    
                    //Bitch loudly and give up if we don't get it...
                    if(!cmd_val)
                    {
                        fprintf(stderr, "Action shellcall requires: cmdline\n");
                        break;
                    }

                    okmenu = get_attr_val(p, "okmenu");
                    failmenu = get_attr_val(p, "failmenu");
                    
                    
                    //While we still have format string to process and buffer space to put the result
                    while( (c = cmd_val[in_idx]) && (out_idx < (SHELLCALL_BUFFER_SIZE - 1)) )
                    {
                        if(fmt_mode)	//------------------------------------------ FORMAT MODE
                        {
                            if(c == SHELLCALL_VAR_ESC)	//and we just got an escape character to terminate the variable name
                            {
                                if(fmt_idx > 0)		//if we have a non-zero length variable name
                                {
                                    varname[fmt_idx] = '\0';	//nul terminate it
                                    
                                    if( (var_val = menuvar_getval(mt, varname)) ) //and look it up
                                    {
                                        //if we find it, tack its value onto our output buffer
                                        out_idx += snprintf(tmp + out_idx, SHELLCALL_BUFFER_SIZE - 1, "%s", var_val);
                                    }
                                    else	//if we don't find it, mark failure and break out
                                    {
                                        fail_flag = 1;
                                        fprintf(stderr,"Action shellcall aborted: variable ~%s~ not defined\n", varname);
                                        break;
                                    }                                    
                                }
                                else	//If we have a zero-length variable name, that just means we want the escape character
                                {
                                    tmp[out_idx++] = SHELLCALL_VAR_ESC;
                                }
                                
                                fmt_mode = 0;	//Either way, if we were in format mode and got the escape, we're in normal mode now
                            }
                            else	//if we are in format mode and processing a normal character
                            {
                                if(fmt_idx == (MENUVAR_IDENTLEN - 1))	//make sure we have room for the identifier
                                {
                                    fail_flag = 1;
                                    varname[MENUVAR_IDENTLEN - 1] = '\0';
                                    fprintf(stderr,"Action shellcall aborted: variable name too long ~%s...\n", varname);
                                    break;
                                }
                                
                                varname[fmt_idx++] = c;			//then store the character to the end...
                            }
                        }
                        else	//------------------------------------------ NORMAL MODE
                        {
                            if(c == SHELLCALL_VAR_ESC)	//if we're in normal mode and encounter an escape
                            {
                                fmt_mode = 1;	//set escape mode
                                fmt_idx = 0;	//reset variable name index
                            }
                            else
                            {
                                tmp[out_idx++] = c;	//copy the character verbatim
                            }
                        }
                        
                        in_idx++;  //no matter what, we want to advance to the next format string character
                    }
                    
                    if(fail_flag)	//if we hit a failure during format expansion, bail now...
                    {
                        break;
                    }
                    
                    if(fmt_mode)
                    {
                        varname[fmt_idx] = '\0';
                        fprintf(stderr, "Action shellcall aborted: unterminated variable expansion ~%s...\n", varname);
                        break;
                    }
                    
                    //If we've overflowed and constructed a partially formed command line, play it safe and give up now before
                    //we blow up the world and poison everybody with radioactive waste.
                    if(out_idx >= SHELLCALL_BUFFER_SIZE)
                    {
                        fprintf(stderr, "Action shellcall aborted: assembled command exceeds %d character limit!\n", SHELLCALL_BUFFER_SIZE);
                        break;
                    }
                
                    tmp[out_idx] = '\0';	//Slap the terminating nul on the newly minted shell script. Wo0t!
                
                    //Here goes nothing, we make our call to system() here to invoke the shell...    
                    ret = system(tmp);
                
                
                    if( WEXITSTATUS(ret) == 0 )	//If the shell return value is 0 (true / success in bash-ese)
                    {
                        if(okmenu)		//and the config file specifies a menu to jump to on success
                        {
                            setmenu(mt, okmenu);	//then by all means do so...
                        }
                    }
                    else			//otherwise, it's the same drill with failure
                    {				//now with 30% MORE FAIL!
                        if(failmenu)
                        {
                            setmenu(mt, failmenu);
                        }
                    }
                
                }
                break;
                
            default:
                fprintf(stderr, "Menu Action %s Currently Unimplemented...\n", action_words[p->act.type]);            
                break;
        }
    
        p = p->next;    
    }
}

int process_pen(menutree *mt, int x, int y, int dwn)
{
    int return_code = 0;

    menunode *p;
    menunode *pp = mt->pressed;

    int tx,ty,txx,tyy;

    if(!mt->current)
    {
        mt->pressed = NULL;
        mt->pressed_flags = 0;
        mt->down = 0;
        mt->down_x = 0;
        mt->down_y = 0;
        
        return -1;
    }

    p = mt->current->subtree;


    if(dwn)		//if the pen is down
    {
        mt->pressed = NULL;
        mt->pressed_flags = 0;

        if(!mt->down)	//and this is news...
        {
            mt->down = 1;	//record a pen down event
            mt->down_x = x;
            mt->down_y = y;
        }    
        
        while(p)
        {
            if(p->type != NODE_WIDGET)
            {
                fprintf(stderr, "Expecting Widget!\n");
                print_menu_tree_inner(p, 2);
                p = p->next;
                continue;
            }
        
            switch(p->wid.type)
            {
                case WIDGET_BUTTON:
                    break;
            
                default:
                    p = p->next;
                    continue;
            }

            if(p->wid.flags & WF_DISABLED)
            {
                p = p->next;
                continue;
            }
          
            tx = p->wid.x * GRID;
            ty = p->wid.y * GRID;
            txx = tx + p->wid.width * GRID;
            tyy = ty + p->wid.height * GRID;    

//            printf("%d %d %d %d\n", p->wid.x, p->wid.y, p->wid.width, p->wid.height);
//            printf("(%d,%d)-(%d,%d) %s / (%d,%d) (%d,%d)\n", tx, ty, txx, tyy, p->wid.text, x, y, mt->down_x, mt->down_y);


            if( (x > tx) && (x < txx) && (y > ty) && (y < tyy) &&
                (mt->down_x > tx) && (mt->down_x < txx) && (mt->down_y > ty) && (mt->down_y < tyy) )
            {
                mt->pressed = p;
                mt->pressed_flags = p->wid.flags | WF_PRESSED; 
            }
        
            p = p->next;
        }
    }
    else
    {
        if(mt->down)
        {
            while(p)
            {
                if(p->type != NODE_WIDGET)
                {
                    fprintf(stderr, "Expecting Widget!\n");
                    print_menu_tree_inner(p, 2);
                    p = p->next;
                    continue;
                }
                
                if(p == mt->pressed)
                    break;
                    
                p = p->next;
            }
            
            if(p != NULL)
            {
//                printf("Button: %s\n", p->wid.text);
                DIU_BUTTON_BEEP(diu_fd);
                do_menu_action(mt, p->subtree);
            }
        }
        else
        {
            //take no action on a redundant pen up        
        }    
    
        mt->pressed = NULL;
        mt->pressed_flags = 0;
        mt->down = 0;
        mt->down_x = 0;
        mt->down_y = 0;
    }

    if(pp != mt->pressed)
    {
        return_code |= 1;
    }

    return return_code;    
}

