
#define _SUPPRESS_PLIB_WARNING
#define _DISABLE_OPENADC10_CONFIGPORT_WARNING
#include <plib.h>
#include "menu.h"
#include "mainmenu.h"
#include "Infrared.h"
#include "util.h"
#include "gpsserial.h"
#include "Timezone/timezones.h"
#include "Timezone/tz_coords.h"
#include <string.h>
#include <stdio.h>

__attribute__((mips16)) static void swap6chars(char* a, char* b) {
    char temp[6];
    memcpy(temp, a, 6);
    memcpy(a, b, 6);
    memcpy(b, temp, 6);
}

__attribute__((mips16)) static void menu_update_alarm_groups() {
    int weekday_on = 0, weekend_on = 0, all_on = 0;
    int i;
    for( i = 0; i < 7; ++i ) {
        char c = alarm_submenu[i+1].name[5];
        all_on += (c == 'N');
        if( i < 5 )
            weekday_on += (c == 'N');
        else
            weekend_on += (c == 'N');
    }
    memcpy(alarm_submenu[ 8].name+3, weekday_on == 5 ? " ON" : (weekday_on == 0 ? "OFF" : "   "), 3);
    memcpy(alarm_submenu[ 9].name+3, weekend_on == 2 ? " ON" : (weekend_on == 0 ? "OFF" : "   "), 3);
    memcpy(alarm_submenu[10].name+3,     all_on == 7 ? " ON" :     (all_on == 0 ? "OFF" : "   "), 3);
}

__attribute__((mips16)) void menu_init(menuitem* menus, menustate* state) {
    state->level = 0;
    state->sub[0] = 0;
    menu_update_alarm_groups();
}

__attribute__((mips16)) menuitem* menu_get_submenu(menuitem* menus, menustate* state, unsigned char* editing) {
    int i;
    for( i = 0; i < state->level; ++i ) {
        if( menus[state->sub[i]].type != submenu ) {
            *editing = 1;
            break;
        }
        menus = menus[state->sub[i]].data.submenu;
    }
    return menus;
}

__attribute__((mips16)) static int get_num_decimal_digits(int val) {
    int ret = 1;
    if( val < 0 )
        val *= -1;
    while( val > 9 ) {
        ++ret;
        val /= 10;
    }
    return ret;
}

__attribute__((mips16)) static int menu_item_get_max_digits(menuitem* item) {
    return max(get_num_decimal_digits(item->data.b.c.num.max),get_num_decimal_digits(item->data.b.c.num.min));
}

extern union {
    struct {
        unsigned short max, min;
    } vals;
    unsigned int val;
} LDR;
extern unsigned short LDR_minmax_updated;

__attribute__((mips16)) void menu_show(menuitem* menus, menustate* state, char* tohere, unsigned char* new_dps, char* flashing) {
    unsigned char editing = 0;
    menuitem* menu = menu_get_submenu(menus, state, &editing);
    memset(flashing, 0, 6);
    if( editing ) {
        menuitem* item = &menu[state->sub[state->level-1]];
        switch( item->type ) {
            case updownlist:
                memcpy(tohere, item->data.a.cycleitems[state->sub[state->level]].altname, 6);
                break;
            case numeric_tz:
            case numeric:
            {
                int max_digits = menu_item_get_max_digits(item);
                int i = 6-max_digits;
                if( max_digits < 6 && state->edit_num < 0 ) {
                    sprintf(tohere, "%6d", (int)-state->edit_num);
                    while( i < 6 && tohere[i] == ' ' ) {
                        tohere[i] = '0';
                        ++i;
                    }
                    tohere[5-max_digits] = '-';
                } else {
                    sprintf(tohere, "%6d", (int)state->edit_num);
                    while( i < 6 && tohere[i] == ' ' ) {
                        tohere[i] = '0';
                        ++i;
                    }
                }
                if( item->data.b.step )
                    flashing[state->sub[state->level]] = '*';
                break;
            }
            case alarm_time:
            case alarm_time_group:
            case numeric_any:
            case set_time:
            case set_date:
                sprintf(tohere, "%06d", (int)state->edit_num);
                if( item->data.b.step )
                    flashing[state->sub[state->level]] = '*';
                break;
            case numeric_sec:
                sprintf(tohere, "%02d SEC", (int)state->edit_num);
                if( item->data.b.step )
                    flashing[state->sub[state->level]-4] = '*';
                break;
            case numeric_hz:
                sprintf(tohere, "%03d HZ", (int)state->edit_num);
                if( item->data.b.step )
                    flashing[state->sub[state->level]-3] = '*';
                break;
            case numeric_pct:
                sprintf(tohere, "%03d PC", (int)state->edit_num);
                if( item->data.b.step )
                    flashing[state->sub[state->level]-3] = '*';
                break;
            case show_tznum:
                sprintf(tohere, "%6d", timezone_found);
                break;
            case show_tzname:
            {
                int temp = timezone_found;
                if( temp >= 0 && temp <= sizeof(tz_map)/sizeof(*tz_map) )
                    temp = tz_map[temp];
                else
                    temp = -1;

                if( temp >= 0 && temp+1 < sizeof(tz_cycle)/sizeof(*tz_cycle)-1 )
                    memcpy(tohere, tz_cycle[temp+1].altname, 6);
                else
                    memcpy(tohere, " NONE ", 6);
                break;
            }
            case show_lat:
                if( gps_fix ) {
                    int temp = gps_lat;
                    sprintf(tohere, "%5d%c", (temp >= 0 ? temp : -temp)/100, temp >= 0 ? 'N' : 'S');
                    *new_dps |= 4;
                } else {
                    memcpy(tohere, "NO FIX", 6);
                }
                break;
            case show_lon:
                if( gps_fix ) {
                    int temp = gps_lon;
                    sprintf(tohere, "%5d%c", (temp >= 0 ? temp : -temp)/100, temp >= 0 ? 'E' : 'U');
                    *new_dps |= 4;
                } else {
                    memcpy(tohere, "NO FIX", 6);
                }
                break;
            case show_num_sats:
                sprintf(tohere, "%2d SAT", gps_numsats);
                break;
            case show_autodim_vals:
                sprintf(tohere, "%02X%02X%02X", LDR.vals.min>>2, get_LDR_val()>>2, LDR.vals.max>>2);
                if( LDR_minmax_updated )
                    *new_dps |= 8;
                break;
        }
    } else {
        memcpy(tohere, menu[state->sub[state->level]].name, 6);
    }
}

__attribute__((mips16)) static void menu_alarm_group_changed(menuitem* mitem) {
    int i;
    for( i = 0; i < 7; ++i ) {
        menuitem* item = &alarm_submenu[i+1];
        if( i < 5 ) {
            if( mitem->name[2] == 'E' )
                continue;
        } else {
            if( mitem->name[2] == 'D' )
                continue;
        }
        if( item->name[5] != mitem->name[5] ) {
            if( item->name[5] == 'F' ) {
                item->name[5] = 'N';
                if( item->name[4] == 'F' ) {
                    item->name[3] = ' ';
                    item->name[4] = 'O';
                }
            } else {
                if( item->name[3] == ' ' ) {
                    item->name[3] = 'O';
                    item->name[4] = 'F';
                }
                item->name[5] = 'F';
            }
        }
    }
    menu_update_alarm_groups();
}

__attribute__((mips16)) int menu_get_cycle_item_index(cycleitem* items, const char* string) {
    int index = 0;
    while( items->altname[0] != '\0' ) {
        if( !memcmp(items->altname, string, 6) )
            return index;
        ++index;
        ++items;
    }
    return -1;
}

__attribute__((mips16)) static int* _menu_save_state(const menuitem* menus, int* tohere) { // requires ?? integers of storage
    while( menus->name[0] != '\0' ) {
        switch(menus->type) {
            case submenu:
                if( menus->data.submenu != tz_submenu && menus->data.submenu != dst_submenu )
                    tohere = _menu_save_state(menus->data.submenu, tohere);
                break;
            case toggle:
            case alarm_toggle:
                if( menus->name[0] != menus->data.altname[0] )
                    *tohere = menus->name[0];
                else
                    *tohere = menus->name[5];
                ++tohere;
                break;
            case cycle:
            {
                cycleitem* c = menus->data.a.cycleitems;
                while( c->altname[0] != '\0' && memcmp(c->altname, menus->name, 6) )
                    ++c;
                *tohere = c->altname[0] == '\0' ? 0 : (c - menus->data.a.cycleitems);
                ++tohere;
                break;
            }
            case updownlist:
                *tohere = menus->data.a.selected;
                ++tohere;
                break;
            case numeric:
            case numeric_tz:
            case numeric_any:
            case numeric_sec:
            case numeric_hz:
            case numeric_pct:
            case ircode:
                *tohere = menus->data.b.c.val;
                ++tohere;
                break;
            case alarm_time:
                *tohere = menus->data.b.c.val | (menus->name[5] == 'N' ? 0x00100000 : 0);
                ++tohere;
                break;
        }
        ++menus;
    }
    return tohere;
}

__attribute__((mips16)) int menu_save_state(const menuitem* menus, int* tohere) {
    return _menu_save_state(menus, tohere) - tohere;
}

__attribute__((mips16)) static const int* _menu_restore_state(menuitem* menus, const int* fromhere) {
    while( menus->name[0] != '\0' ) {
        switch(menus->type) {
            case submenu:
                if( menus->data.submenu != tz_submenu && menus->data.submenu != dst_submenu )
                    fromhere = _menu_restore_state(menus->data.submenu, fromhere);
                break;
            case toggle:
            case alarm_toggle:
                if( (menus->name[0] != menus->data.altname[0] ? (menus->name[0] != *fromhere) : (menus->name[5] != *fromhere)) )
                    swap6chars(menus->name, menus->data.altname);
                ++fromhere;
                break;
            case cycle:
                memcpy(menus->name, &menus->data.a.cycleitems[*fromhere].altname, 6);
                ++fromhere;
                break;
            case updownlist:
                menus->data.a.selected = *fromhere;
                ++fromhere;
                break;
            case numeric:
            case numeric_tz:
            case numeric_any:
            case numeric_sec:
            case numeric_hz:
            case numeric_pct:
            case ircode:
                menus->data.b.c.val = *fromhere;
                ++fromhere;
                break;
            case alarm_time:
                menus->data.b.c.val = *fromhere & 0x000FFFFF;
                if( (menus->name[5] == 'N') != ((*fromhere&0x00100000) != 0) ) {
                    if( menus->name[5] == 'F' ) {
                        menus->name[5] = 'N';
                        if( menus->name[4] == 'F' ) {
                            menus->name[3] = ' ';
                            menus->name[4] = 'O';
                        }
                    } else {
                        if( menus->name[3] == ' ' ) {
                            menus->name[3] = 'O';
                            menus->name[4] = 'F';
                        }
                        menus->name[5] = 'F';
                    }
                }
                ++fromhere;
                break;
        }
        ++menus;
    }
    return fromhere;
}
__attribute__((mips16)) int menu_restore_state(menuitem* menus, const int* fromhere) {
    return _menu_restore_state(menus, fromhere) - fromhere;
}

__attribute__((mips16)) static unsigned char dec_to_bcd(unsigned char dec) {
    return ((dec/10)<<4)|(dec%10);
}
__attribute__((mips16)) static unsigned char bcd_to_dec(unsigned char bcd) {
    return (bcd>>4)*10 + (bcd&0x0F);
}

static const unsigned char days_in_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
__attribute__((mips16)) int fixup_date(int val) {
    unsigned char a = val / 10000;
    unsigned char b = (val - a * 10000) / 100;
    unsigned char c = (val - a * 10000 - b * 100);
    unsigned char m;

    // check month first
    if( DATE_FMT_DDMMYY || DATE_FMT_YYMMDD ) {
        if( b < 1 ) {
            val += 100;
            m = 1;
        } else if( b > 12 ) {
            val -= (b-12) * 100;
            m = 12;
        } else {
            m = b;
        }
    } else {
        if( a < 1 ) {
            val += 10000;
            m = 1;
        } else if( a > 12 ) {
            val -= (a-12) * 10000;
            m = 12;
        } else {
            m = a;
        }
    }
    if( DATE_FMT_DDMMYY ) {
        if( a < 1 ) {
            val += 10000;
        } else if( a > days_in_month[m-1] ) {
            val -= 10000 * (a - days_in_month[m-1]);
        }
    } else if( DATE_FMT_YYMMDD ) {
        if( c < 1 ) {
            val += 1;
        } else if( c > days_in_month[m-1] ) {
            val -= (c - days_in_month[m-1]);
        }
    } else {
        if( b < 1 ) {
            val += 100;
        } else if( b > days_in_month[m-1] ) {
            val -= 100 * (b - days_in_month[m-1]);
        }
    }
    return val;
}

extern clockmode curmode;
extern unsigned char time_set, date_set;
extern signed long* tz_menu_tzinfo;
extern daylight_savings_info* tz_menu_dstinfo;

__attribute__((mips16)) int menu_get_max_digits_for_item(menuitem* item) {
    int max_digits;
    if( item->type != numeric_any && item->type != alarm_time && item->type != alarm_time_group && item->type != set_time && item->type != set_date ) {
        max_digits = menu_item_get_max_digits(item);
        if( max_digits > 5 )
            max_digits = 5;
    } else {
        max_digits = 6;
    }
    return max_digits;
}

__attribute__((mips16)) int menu_handle_keypress(menuitem* menus, menustate* state, int key, unsigned char emulated, menu_callback callback) {
    unsigned char editing = 0;
    menuitem* menu = menu_get_submenu(menus, state, &editing);

    if( key == IRCODE_UP ) {
        if( editing ) {
            menuitem* item = &menu[state->sub[state->level-1]];
            switch( item->type ) {
                case updownlist:
                    if( state->sub[state->level] > 0 ) {
                        --state->sub[state->level];
                    } else {
                        state->sub[state->level] = 0;
                        while( item->data.a.cycleitems[state->sub[state->level]+1].altname[0] )
                            ++state->sub[state->level];
                    }
                    break;
                case numeric:
                case numeric_sec:
                case numeric_hz:
                case numeric_pct:
                case numeric_tz:
                case numeric_any:
                case alarm_time:
                case alarm_time_group:
                case set_time:
                case set_date:
                {
                    int val, temp, max_val;
                    if( item->data.b.step != 1 ) {
                        val = item->data.b.step;
                    } else {
                        val = 1;
                        temp = 5 - state->sub[state->level];
                        while( temp-- )
                            val *= 10;
                    }
                    val += (int)state->edit_num;
                    if( item->type == numeric_tz && ((val > 0 && (val%100) >= 60) || val < 0 && (val%100) <= -60) )
                        val += 40;
                    if( item->type != numeric_any && item->type != alarm_time && item->type != alarm_time_group && item->type != set_time && item->type != set_date )
                        max_val = item->data.b.c.num.max;
                    else
                        max_val = 999999;
                    if( val > max_val )
                        val = max_val;
                    if( item->type == alarm_time || item->type == alarm_time_group || item->type == set_time ) {
                        if( val%100 >= 60 )
                            val += 40;
                        if( val%10000 >= 6000 )
                            val += 4000;
                        if( val > 235959 )
                            val = 235959;
                    } else if( item->type == set_date ) {
                        val = fixup_date(val);
                    }
                    state->edit_num = val;
                    break;
                }
            }
        } else {
            if( state->sub[state->level] > 0 ) {
                --state->sub[state->level];
            } else {
                state->sub[state->level] = 0;
                while( menu[state->sub[state->level]+1].name[0] )
                    ++state->sub[state->level];
            }
        }
    } else if( key == IRCODE_DOWN ) {
        if( editing ) {
            menuitem* item = &menu[state->sub[state->level-1]];
            switch( item->type ) {
                case updownlist:
                    ++state->sub[state->level];
                    if( !item->data.a.cycleitems[state->sub[state->level]].altname[0] )
                        state->sub[state->level] = 0;
                    break;
                case numeric:
                case numeric_sec:
                case numeric_hz:
                case numeric_pct:
                case numeric_tz:
                case numeric_any:
                case alarm_time:
                case alarm_time_group:
                case set_time:
                case set_date:
                {
                    int val, temp, min_val;
                    if( item->data.b.step != 1 ) {
                        val = -item->data.b.step;
                    } else {
                        val = -1;
                        temp = 5 - state->sub[state->level];
                        while( temp-- )
                            val *= 10;
                    }
                    val += (int)state->edit_num;
                    if( item->type == numeric_tz && ((val > 0 && (val%100) >= 60) || val < 0 && (val%100) <= -60) )
                        val -= 40;
                    if( item->type != numeric_any && item->type != alarm_time && item->type != alarm_time_group && item->type != set_time && item->type != set_date )
                        min_val = item->data.b.c.num.min;
                    else
                        min_val = 0;
                    if( val < min_val )
                        val = min_val;
                    if( item->type == alarm_time || item->type == alarm_time_group || item->type == set_time ) {
                        if( val%100 >= 60 )
                            val -= 40;
                        if( val%10000 >= 6000 )
                            val -= 4000;
                        if( val < 0 )
                            val = 0;
                        else if( val > 235959 )
                            val = 235959;
                    } else if( item->type == set_date ) {
                        val = fixup_date(val);
                    }
                    state->edit_num = val;
                    break;
                }
            }
        } else {
            ++state->sub[state->level];
            if( !menu[state->sub[state->level]].name[0] )
                state->sub[state->level] = 0;
        }
    } else if( key == IRCODE_LEFT ) {
        if( editing ) {
            menuitem* item = &menu[state->sub[state->level-1]];
            switch( item->type ) {
                case numeric:
                case numeric_sec:
                case numeric_hz:
                case numeric_pct:
                case numeric_tz:
                case numeric_any:
                case alarm_time:
                case alarm_time_group:
                case set_time:
                case set_date:
                    if( item->data.b.step == 1 ) {
                        int max_digits = menu_get_max_digits_for_item(item);
                        if( state->sub[state->level] > 6-max_digits )
                            --state->sub[state->level];
                    }
                    break;
            }
        }
    } else if( key == IRCODE_RIGHT ) {
        if( editing ) {
            menuitem* item = &menu[state->sub[state->level-1]];
            switch( item->type ) {
                case numeric:
                case numeric_sec:
                case numeric_hz:
                case numeric_pct:
                case numeric_tz:
                case numeric_any:
                case alarm_time:
                case alarm_time_group:
                case set_time:
                case set_date:
                    if( item->data.b.step == 1 ) {
                        if( state->sub[state->level] < 5 ) {
                            ++state->sub[state->level];
                        } else if( emulated ) {
                            int max_digits = menu_get_max_digits_for_item(item);
                            state->sub[state->level] = 6-max_digits;
                        }
                    }
                    break;
            }
        }
    } else if( key == IRCODE_SELECT ) {
        menuitem* item = &menu[state->sub[state->level-editing]];
        switch( item->type ) {
            case submenu:
                state->sub[++state->level] = 0;
                break;
            case updownlist:
                if( editing ) {
                    item->data.a.selected = state->sub[state->level];
                    if( callback )
                        callback(menu_updownlist_change, item);
                    --state->level;
                } else {
                    state->sub[++state->level] = item->data.a.selected;
                }
                break;
            case set_clockmode:
                curmode = item->data.b.c.mode;
                break;
            case toggle:
            case alarm_toggle:
                swap6chars(item->name, item->data.altname);
                break;
            case cycle:
            {
                cycleitem* c = item->data.a.cycleitems;
                while( c->altname[0] != '\0' && memcmp(c->altname, item->name, 6) )
                    ++c;
                if( c[0].altname[0] == '\0' || c[1].altname[0] == '\0' )
                    c = item->data.a.cycleitems - 1;
                memcpy(item->name, c[1].altname, 6);
                if( callback )
                    callback(menu_cycle_change, item);
                break;
            }
            case numeric:
            case numeric_tz:
            case numeric_sec:
            case numeric_hz:
            case numeric_pct:
                if( editing ) {
                    item->data.b.c.num.val = state->edit_num;
                    --state->level;
                } else {
                    state->edit_num = item->data.b.c.num.val;
                    state->sub[++state->level] = 6 - menu_item_get_max_digits(item);
                }
                break;
            case numeric_any:
            case alarm_time:
                if( editing ) {
                    item->data.b.c.val = state->edit_num;
                    --state->level;
                } else {
                    state->edit_num = item->data.b.c.val;
                    state->sub[++state->level] = 0;
                }
                break;
            case set_time:
                if( editing ) {
                    rtccTime newtime;
                    newtime.l = RtccGetTime();
                    newtime.hour = dec_to_bcd(state->edit_num/10000);
                    newtime.min = dec_to_bcd((state->edit_num/100)%100);
                    newtime.sec = dec_to_bcd(state->edit_num%100);
                    if( tz_override != -1 ) {
                        rtccTime time;
                        rtccDate date;
                        int gtime, gdate, ignore;
                        time.l = RtccGetTime();
                        date.l = RtccGetDate();
                        bcd_time_date_to_decimal(&gtime, &gdate, &time, &date);
                        apply_timezone(&gtime, &gdate, tz_override, tz_menu_tzinfo, tz_menu_dstinfo);
                        bcd_time_date_to_decimal(&gtime, &ignore, &newtime, &date);
                        unapply_timezone(&gtime, &gdate, tz_override, tz_menu_tzinfo, tz_menu_dstinfo);
                        decimal_time_date_to_bcd(&time, &date, gtime, gdate);
                        RtccFixupDOW(&date);
                        RtccSetTimeDate(time.l, date.l);
                    } else {
                        RtccSetTime(newtime.l);
                    }
                    time_set = 1;
                    if( callback )
                        callback(menu_exit, menu);
                    --state->level;
                    return 0;
                } else {
                    rtccTime time;
                    if( time_set ) {
                        time.l = RtccGetTime();
                    } else {
                        time.l = 0;
                        time.hour = 0x12;
                    }
                    state->edit_num = bcd_to_dec(time.hour) * 10000 + bcd_to_dec(time.min) * 100 + bcd_to_dec(time.sec);
                    state->sub[++state->level] = 0;
                }
                break;
            case set_date:
                if( editing ) {
                    rtccDate newdate;
                    newdate.l = RtccGetDate();
                    if( DATE_FMT_MMDDYY ) {
                        newdate.mon = dec_to_bcd(state->edit_num/10000);
                        newdate.mday = dec_to_bcd((state->edit_num/100)%100);
                        newdate.year = dec_to_bcd(state->edit_num%100);
                    } else {
                        newdate.mon = dec_to_bcd((state->edit_num/100)%100);;
                        if( DATE_FMT_YYMMDD ) {
                            newdate.year = dec_to_bcd(state->edit_num/10000);
                            newdate.mday = dec_to_bcd(state->edit_num%100);
                        } else {
                            newdate.year = dec_to_bcd(state->edit_num%100);
                            newdate.mday = dec_to_bcd(state->edit_num/10000);
                        }
                    }
                    if( tz_override != -1 ) {
                        rtccTime time;
                        rtccDate date;
                        int gtime, gdate, ignore;
                        time.l = RtccGetTime();
                        date.l = RtccGetDate();
                        bcd_time_date_to_decimal(&gtime, &gdate, &time, &date);
                        apply_timezone(&gtime, &gdate, tz_override, tz_menu_tzinfo, tz_menu_dstinfo);
                        bcd_time_date_to_decimal(&ignore, &gdate, &time, &newdate);
                        unapply_timezone(&gtime, &gdate, tz_override, tz_menu_tzinfo, tz_menu_dstinfo);
                        decimal_time_date_to_bcd(&time, &date, gtime, gdate);
                        RtccFixupDOW(&date);
                        RtccSetTimeDate(time.l, date.l);
                    } else {
                        RtccFixupDOW(&newdate);
                        RtccSetDate(newdate.l);
                    }
                    date_set = 1;
                    if( callback )
                        callback(menu_exit, menu);
                    --state->level;
                    return 0;
                } else {
                    rtccDate date;
                    if( date_set ) {
                        date.l = RtccGetDate();
                    } else {
                        date.year = 0x16;
                        date.mon = 0x01;
                        date.mday = 0x01;
                        date.wday = 5;
                    }
                    if( DATE_FMT_MMDDYY ) {
                        state->edit_num = bcd_to_dec(date.mon) * 10000 + bcd_to_dec(date.mday) * 100 + bcd_to_dec(date.year);
                    } else if( DATE_FMT_DDMMYY ) {
                        state->edit_num = bcd_to_dec(date.mday) * 10000 + bcd_to_dec(date.mon) * 100 + bcd_to_dec(date.year);
                    } else {
                        state->edit_num = bcd_to_dec(date.year) * 10000 + bcd_to_dec(date.mon) * 100 + bcd_to_dec(date.mday);
                    }
                    state->sub[++state->level] = 0;
                }
                break;
            case alarm_time_group:
                if( editing ) {
                    int i;
                    for( i = 0; i < 7; ++i ) {
                        if( i < 5 ) {
                            if( item->name[2] == 'E' )
                                continue;
                        } else {
                            if( item->name[2] == 'D' )
                                continue;
                        }
                        alarm_submenu[i+1].data.b.c.val = state->edit_num;
                    }
                    --state->level;
                } else {
                    state->edit_num = alarm_submenu[item->name[2] == 'E' ? 6 : 1].data.b.c.val;
                    state->sub[++state->level] = 0;
                }
                break;
            case ircode:
                curmode = set_ir_code;
                break;
            case show_tznum:
            case show_tzname:
            case show_lat:
            case show_lon:
            case show_num_sats:
            case show_autodim_vals:
                if( editing )
                    --state->level;
                else
                    ++state->level;
                break;
        }
    } else if( key == IRCODE_ALA_ON ) {
        menuitem* item = &menu[state->sub[state->level-editing]];
        switch( item->type ) {
            case alarm_toggle:
                swap6chars(item->name, item->data.altname);
                break;
            case alarm_time:
            case alarm_time_group:
            {
                if( item->name[5] == 'F' || item->name[5] == ' ' ) {
                    item->name[5] = 'N';
                    if( item->name[4] == 'F' || item->name[4] == ' ' ) {
                        item->name[3] = ' ';
                        item->name[4] = 'O';
                    }
                } else {
                    if( item->name[3] == ' ' ) {
                        item->name[3] = 'O';
                        item->name[4] = 'F';
                    }
                    item->name[5] = 'F';
                }
                if( item->type == alarm_time )
                    menu_update_alarm_groups();
                else
                    menu_alarm_group_changed(item);
                break;
            }
        }
    } else if( key == IRCODE_ESCAPE ) {
        if( callback )
            callback(menu_exit, menu);
        if( state->level > 0 ) {
            --state->level;
        } else {
            return 0;
        }
    } else if( editing ) {
        unsigned char keynum = 10;
        if( key == IRCODE_0 ) {
            keynum = 0;
        } else if( key == IRCODE_1 ) {
            keynum = 1;
        } else if( key == IRCODE_2 ) {
            keynum = 2;
        } else if( key == IRCODE_3 ) {
            keynum = 3;
        } else if( key == IRCODE_4 ) {
            keynum = 4;
        } else if( key == IRCODE_5 ) {
            keynum = 5;
        } else if( key == IRCODE_6 ) {
            keynum = 6;
        } else if( key == IRCODE_7 ) {
            keynum = 7;
        } else if( key == IRCODE_8 ) {
            keynum = 8;
        } else if( key == IRCODE_9 ) {
            keynum = 9;
        }
        if( keynum < 10 ) {
            menuitem* item = &menu[state->sub[state->level-1]];

            switch( item->type ) {
                case numeric:
                case numeric_sec:
                case numeric_hz:
                case numeric_pct:
                case numeric_tz:
                case numeric_any:
                case alarm_time:
                case alarm_time_group:
                case set_time:
                case set_date:
                    if( item->data.b.step == 1 ) {
                        int val, temp;
                        val = 1;
                        temp = 5 - state->sub[state->level];
                        while( temp-- )
                            val *= 10;
                        if( state->edit_num < 0 )
                            val = state->edit_num - (keynum + (state->edit_num/val)%10)*val;
                        else
                            val = state->edit_num + (keynum - (state->edit_num/val)%10)*val;

                        if( item->type == alarm_time || item->type == alarm_time_group || item->type == set_time ) {
                            if( val%100 >= 60 )
                                val += 40;
                            if( val%10000 >= 6000 )
                                val += 4000;
                            if( val > 235959 )
                                val = 235959;
                        } else if( item->type == set_date ) {
                            val = fixup_date(val);
                        } else if( item->type == numeric_any ) {
                            if( val > 999999 )
                                val = 999999;
                        } else {
                            if( val > item->data.b.c.num.max )
                                val = item->data.b.c.num.max;
                            else if( val < item->data.b.c.num.min )
                                val = item->data.b.c.num.min;
                        }
                        state->edit_num = val;

                        if( state->sub[state->level] == 5 )
                            return menu_handle_keypress(menus, state, IRCODE_SELECT, emulated, callback);
                        else
                            ++state->sub[state->level];
                    }
                    break;
            }
        }
    }
    return 1;
}

static int last_menu_state[MAX_MENU_STATE_SIZE];

__attribute__((mips16)) void menu_load_settings_from_flash(menuitem* menus, int start_addr) {
    int i;

    menu_save_state(menus, last_menu_state);
    for( i = 0; i < MAX_MENU_STATE_SIZE; ++i )
        DataEERead(&last_menu_state[i], start_addr+i);
    menu_restore_state(menus, last_menu_state);
}

__attribute__((mips16)) void menu_save_settings_to_flash(menuitem* menus, int start_addr) {
    int menu_state[MAX_MENU_STATE_SIZE];
    int i;

    menu_save_state(menus, menu_state);
    for( i = 0; i < MAX_MENU_STATE_SIZE; ++i ) {
        if( menu_state[i] != last_menu_state[i] ) {
            DataEEWrite(menu_state[i], start_addr+i);
            last_menu_state[i] = menu_state[i];
        }
    }
}

__attribute__((mips16)) menuitem* menu_get_current_item(menuitem* menus, menustate* state, unsigned char extra_level) {
    unsigned char editing = 0;
    menuitem* ret = menu_get_submenu(menus, state, &editing);
    if( extra_level )
        return &ret[state->sub[state->level]];
    else
        return ret;
}

__attribute__((mips16)) unsigned char menu_currentitem_handles_alarm_on_key(menuitem* menus, menustate* state) {
    unsigned char editing = 0;
    menuitem* ret = menu_get_submenu(menus, state, &editing);
    return ret >= &alarm_submenu[1] && ret <= &alarm_submenu[7];
}
