#include <stdio.h>
#include <conio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "v2types.h"
#include "cfgtoken.h"
#include "v2loader.h"

#define PROGNAME "Config Utility"
volatile uint16_t cardslot = 3;

#include "v2analog.h"
#include "menu.h"

#define menutop 3
#define menuleft 2

uint16_t cfb;

serialmux_t serialmux[2] = { SERIAL_PRINTER, SERIAL_WIFI };
uint16_t baud[2] = { 19200, 19200 };

usbmux_t usbmux = USB_GUEST_CDC;
compat_t machine = MACHINE_AUTO;
wifimode_t wifimode = WIFI_AP;
char wifi_ssid[32] = "V2RetroNet";
char wifi_psk[32] = "Analog";
uint32_t wifi_address = 0x00000000;
uint32_t wifi_netmask = 0xFFFFFF00;

char jd_host[32] = "192.168.0.1";
uint16_t jd_port = 9100;

uint8_t blockbuffer[4096];

#define CARD_TIMEOUT 0x3fff
uint16_t timeout = CARD_TIMEOUT;

uint8_t default_font = 0;
uint8_t mono_palette = 0;
uint8_t terminal_fgcolor = 0xF;
uint8_t terminal_bgcolor = 0x0;
uint8_t terminal_border = 0x0;

uint8_t config_rev = 0x00;

typedef struct menu_strings_s {
    char *str;
} menu_strings_t;

uint16_t baudvalue[9] = {
    75, 150, 300, 600, 1200, 2400, 4800, 9600, 19200
};

menu_strings_t baud_strings[9] = {
    { "75" },
    { "150" },
    { "300" }, 
    { "600" },
    { "1200" },
    { "2400" },
    { "4800" },
    { "9600" },
    { "19200" }
};

menu_strings_t wifi_mode_str[2] = {
    {"Access Point"},
    {"Client"}
};

menu_strings_t serialmux_strings[4] = {
    { "Loopback" },
    { "USB CDC" },
    { "WiFi Modem" },
    { "Printer" }
};

menu_strings_t font_strings[40] = {
    { "00: US Enhanced     " },
    { "01: US Un-Enhanced  " },
    { "02: Clinton V1 Enh  " },
    { "03: Reactive Micro  " },
    { "04: Dan Paymar Enh  " },
    { "05: Blippo Enhanced " },
    { "06: Byte Enhanced   " },
    { "07: Colossal Enh    " },
    { "08: Count Enhanced  " },
    { "09: Flow Enhanced   " },
    { "0A: Gothic Enhanced " },
    { "0B: Outline Enhanced" },
    { "0C: PigFont Enhanced" },
    { "0D: Pinocchio Enh   " },
    { "0E: Slant Enhanced  " },
    { "0F: Stop Enhanced   " },
    { "10: Euro UnEnhanced " },
    { "11: Euro Enhanced   " },
    { "12: Clinton V2      " },
    { "13: German Enh      " },
    { "14: German Un-Enh   " },
    { "15: French Enh      " },
    { "16: French Un-Enh   " },
    { "17: Hebrew Enh      " },
    { "18: Hebrew Un-Enh   " },
    { "19: Plus Enhanced   " },
    { "1A: Plus Un-Enhanced" },
    { "1B: KataKana        " },
    { "1C: Cyrillic        " },
    { "1D: Greek           " },
    { "1E: Esperanto       " },
    { "1F: Videx Enhanced  " },
    { "20: Apple II Un-Enh " },
    { "21: Apple IIe Enh   " },
    { "22: Apple IIgs Enh  " },
    { "23: Pravetz Enhanced" },
    { "24: PCBold Enhanced " },
    { "25: Custom          " },
    { "26: Custom          " },
    { "27: Custom          " },
};

menu_strings_t monochrome_strings[9] = {
    { "Disabled" },
    { "B&W" },
    { "Inverse" },
    { "Amber" },
    { "Amber I" },
    { "Green" },
    { "Green I" },
    { "C64" },
    { "Custom" }
};

menu_strings_t color_strings[16] = {
    { "Black" },
    { "Magenta" },
    { "D.Blue" },
    { "H.Violet" },
    { "D.Green" },
    { "D.Grey" },
    { "H.Blue" },
    { "L.Blue" },
    { "Brown" },
    { "H.Orange" },
    { "L.Grey" },
    { "Pink" },
    { "H.Green" },
    { "Yellow" },
    { "Aqua" },
    { "White" }
};

menu_strings_t machine_strings[MACHINE_MAXVALUE] = {
    { "Auto Detect" },
    { "Apple II" },
    { "Apple IIe" },
    { "Apple IIgs" },
    { "Pravetz" },
    { "Basis" },
};

void ok_button(void) {
    gotoy(14); gotox(18);
    revers(1);
    cputs(" OK ");
    revers(0);

    for(;;) {
        switch(cgetc()) {
        case 0x0A:
        case 0x0D:
        case 0x1B:
        case 'O':
        case 'o':
            return;
        }
    }
}

void print_menu_select(char *str, int width, int highlighted, int selected) {
    revers(highlighted);
    if(selected) {
        cputs(" [");
    } else {
        cputs("  ");
    }
    printlimited(str, width - 4);
    if(selected) {
        cputs("] ");
    } else {
        cputs("  ");
    }
    width -= longmin(width, strlen(str) + 4);
    if(width > 0)
        repeatchar(' ', width);
    revers(0);
}

void print_menu_select_int(char *str, int i, int width, int highlighted, int selected) {
    revers(highlighted);
    if(selected) {
        cputs(" [");
    } else {
        cputs("  ");
    }
    width -= 4 + cprintf(str, i);
    if(selected) {
        cputs("] ");
    } else {
        cputs("  ");
    }
    if(width > 0)
        repeatchar(' ', width);
    revers(0);
}

void print_menu_select_ip(uint32_t ip, int width, int highlighted, int selected) {
    uint8_t temp_ip[4];
    
    temp_ip[0] = (ip >> 24) & 0xff;
    temp_ip[1] = (ip >> 16) & 0xff;
    temp_ip[2] = (ip >> 8) & 0xff;
    temp_ip[3] = (ip >> 0) & 0xff;

    width -= 11;
    if(temp_ip[0] > 99) width--;
    if(temp_ip[0] > 9) width--;
    if(temp_ip[1] > 99) width--;
    if(temp_ip[1] > 9) width--;
    if(temp_ip[2] > 99) width--;
    if(temp_ip[2] > 9) width--;
    if(temp_ip[3] > 99) width--;
    if(temp_ip[3] > 9) width--;

    revers(highlighted);
    if(selected) {
        cputs(" [");
    } else {
        cputs("  ");
    }
    if(width > 0)
        repeatchar(' ', width);
    cprintf("%d.%d.%d.%d", temp_ip[0], temp_ip[1], temp_ip[2], temp_ip[3]);
    if(selected) {
        cputs("] ");
    } else {
        cputs("  ");
    }
    revers(0);
}

void print_menu_select_str(char *str1, char *str2, int width, int highlighted, int selected) {
    revers(highlighted);
    if(selected) {
        cputs(" [");
    } else {
        cputs("  ");
    }
    width -= 4 + cprintf(str1, str2);
    if(selected) {
        cputs("] ");
    } else {
        cputs("  ");
    }
    if(width > 0)
        repeatchar(' ', width);
    revers(0);
}

int list_menu(char *screen_title, char *window_title, menu_strings_t *menu_str, int max_item, int current_item) {
    int paint_menu = 2;
    int selected_item = (current_item > max_item) ? 0 : current_item;
    int y;
    int top = -1;
    int i;

    y = 12 - 5;

    while(paint_menu >= 0) {
        if(top != ((selected_item / 10) * 10)) {
            top = (selected_item / 10) * 10;
        }
        if(paint_menu == 2) {
            backdrop(screen_title);
            window(window_title, 26, 12, 0);
        }
        if(paint_menu > 0) {
            for(i = 0; i < longmin((max_item-top)+1, 10); i++) {
                gotoy(y+i); gotox(8);
                print_menu_select(menu_str[i+top].str, 24, (selected_item == i+top), (current_item == i+top));
            }
            if(max_item > 9) while(i < 10) {
                gotoy(y+i); gotox(8);
                repeatchar(' ', 24);
                i++;
            }
            paint_menu = 0;
        }
        switch(cgetc()) {
            case 0x08:
            case 0x0B:
                if(selected_item > 0) {
                    selected_item--;
                    paint_menu = 1;
                }
                break;
            case 0x15:
            case 0x0A:
                if(selected_item < max_item) {
                    selected_item++;
                    paint_menu = 1;
                }
                break;
            case 0x0D:
                return selected_item;
            case 0x1B:
                paint_menu = -1;
        }
    }

    return current_item;
}

void default_config() {
    serialmux[0] = SERIAL_PRINTER;
    serialmux[1] = SERIAL_WIFI;
    baud[0] = 19200;
    baud[1] = 19200;
    usbmux = USB_GUEST_CDC;

    wifimode = WIFI_AP;
    strcpy(wifi_ssid, "V2RetroNet");
    strcpy(wifi_psk, "Analog");
    wifi_address = 0x00000000;
    wifi_netmask = 0xFFFFFF00;

    strcpy(jd_host, "192.168.0.1");
    jd_port = 9100;
    machine = MACHINE_AUTO;
}

int parse_config() {
    int i = 0;
    uint32_t *ptr = (uint32_t*)blockbuffer;
    uint32_t configlen = sizeof(blockbuffer)/sizeof(uint32_t);

    if(ptr[i++] != NEWCONFIG_MAGIC) {
        return 0;
    }

    while(i < configlen) {
        if(ptr[i] == NEWCONFIG_EOF_MARKER) {
            configlen = i++;
        } else
        switch(ptr[i] & 0x0000FFFF) {
        case CFGTOKEN_REVISION:
            config_rev = (ptr[i] >> 16) & 0xFF;
            break;
        case CFGTOKEN_HOST_AUTO:
            machine = MACHINE_AUTO;
            break;
        case CFGTOKEN_HOST_II:
            machine = MACHINE_APPLE_II;
            break;
        case CFGTOKEN_HOST_IIE:
            machine = MACHINE_APPLE_IIE;
            break;
        case CFGTOKEN_HOST_IIGS:
            machine = MACHINE_APPLE_IIGS;
            break;
        case CFGTOKEN_HOST_PRAVETZ:
            machine = MACHINE_PRAVETZ;
            break;
        case CFGTOKEN_HOST_BASIS:
            machine = MACHINE_BASIS;
            break;
        case CFGTOKEN_MUX_LOOP:
            serialmux[(ptr[i] >> 16) & 1] = SERIAL_LOOP;
            break;
        case CFGTOKEN_MUX_USB:
            serialmux[(ptr[i] >> 16) & 1] = SERIAL_USB;
            break;
        case CFGTOKEN_MUX_WIFI:
            serialmux[(ptr[i] >> 16) & 1] = SERIAL_WIFI;
            break;
        case CFGTOKEN_MUX_PRN:
            serialmux[(ptr[i] >> 16) & 1] = SERIAL_PRINTER;
            break;
        case CFGTOKEN_SER_BAUD:
            baud[(ptr[i] >> 16) & 1] = ptr[i+1];
            break;
        case CFGTOKEN_USB_HOST:
            usbmux = USB_HOST_CDC;
            break;
        case CFGTOKEN_USB_GUEST:
            usbmux = USB_GUEST_CDC;
            break;
        case CFGTOKEN_USB_MIDI:
            usbmux = USB_GUEST_MIDI;
            break;
        case CFGTOKEN_WIFI_AP:
            wifimode = WIFI_AP;
            break;
        case CFGTOKEN_WIFI_CL:
            wifimode = WIFI_CLIENT;
            break;
        case CFGTOKEN_WIFI_SSID:
            memset(wifi_ssid, 0, sizeof(wifi_ssid));
            strncpy(wifi_ssid, (char*)(&ptr[i+1]), (ptr[i] >> 24));
            break;
        case CFGTOKEN_WIFI_PSK:
            memset(wifi_psk, 0, sizeof(wifi_psk));
            strncpy(wifi_psk, (char*)(&ptr[i+1]), (ptr[i] >> 24));
            break;
        case CFGTOKEN_WIFI_IP:
            wifi_address = ptr[i+1];
            break;
        case CFGTOKEN_WIFI_NM:
            wifi_netmask = ptr[i+1];
            break;
        case CFGTOKEN_JD_HOST:
            memset(jd_host, 0, sizeof(jd_host));
            strncpy(jd_host, (char*)(&ptr[i+1]), (ptr[i] >> 24));
            break;
        case CFGTOKEN_JD_PORT:
            jd_port = ptr[i+1];
            break;
        case CFGTOKEN_FONT_00:
            default_font = (ptr[i] >> 16) & 0x2F;
            break;
        case CFGTOKEN_MONO_00:
            mono_palette = (ptr[i] >> 20) & 0xF;
            if(mono_palette) mono_palette = (mono_palette & 0x7) + 1;
            break;
        case CFGTOKEN_TBCOLOR:
            terminal_fgcolor = (ptr[i] >> 20) & 0xF;
            terminal_bgcolor = (ptr[i] >> 16) & 0xF;
            break;
        case CFGTOKEN_BORDER:
            terminal_border = (ptr[i] >> 16) & 0xF;
            break;
        }

        // Advance by the number of dwords for this token
        i += 1 + (((ptr[i] >> 24) + 3) >> 2);
    }

    return i*4;
}

int build_config(uint32_t rev) {
    int i = 0;
    uint32_t *ptr = (uint32_t*)blockbuffer;

    memset(blockbuffer, 0xFF, sizeof(blockbuffer));

    ptr[i++] = NEWCONFIG_MAGIC;
    ptr[i++] = CFGTOKEN_REVISION | ((rev & 0xff) << 16);

    ptr[i++] = CFGTOKEN_FONT_00 | ((((uint32_t)default_font) & 0x2F) << 20);
    if(mono_palette) {
        ptr[i++] = CFGTOKEN_MONO_80 | ((((uint32_t)mono_palette-1) & 0xF) << 20);
    } else {
        ptr[i++] = CFGTOKEN_MONO_00;
    }
    ptr[i++] = CFGTOKEN_TBCOLOR | ((((uint32_t)terminal_fgcolor) & 0xF) << 20) | ((((uint32_t)terminal_bgcolor) & 0xF) << 16);
    ptr[i++] = CFGTOKEN_BORDER | ((((uint32_t)terminal_border) & 0xF) << 16);

    switch(machine) {
    default:
    case MACHINE_AUTO:
        ptr[i++] = CFGTOKEN_HOST_AUTO;
        break;
    case MACHINE_APPLE_II:
        ptr[i++] = CFGTOKEN_HOST_II;
        break;
    case MACHINE_APPLE_IIE:
        ptr[i++] = CFGTOKEN_HOST_IIE;
        break;
    case MACHINE_APPLE_IIGS:
        ptr[i++] = CFGTOKEN_HOST_IIGS;
        break;
#if 0
    case MACHINE_PRAVETZ:
        ptr[i++] = CFGTOKEN_HOST_PRAVETZ;
        break;
    case MACHINE_BASIS:
        ptr[i++] = CFGTOKEN_HOST_BASIS;
        break;
#endif
    }

    switch(serialmux[0]) {
    case SERIAL_USB:
        ptr[i++] = CFGTOKEN_MUX_USB;
        break;
    case SERIAL_WIFI:
        ptr[i++] = CFGTOKEN_MUX_WIFI;
        break;
    case SERIAL_PRINTER:
        ptr[i++] = CFGTOKEN_MUX_PRN;
        break;
    default:
    case SERIAL_LOOP:
        ptr[i++] = CFGTOKEN_MUX_LOOP;
        break;
    }
    ptr[i++] = CFGTOKEN_SER_BAUD;
    ptr[i++] = baud[0];

    switch(serialmux[1]) {
    case SERIAL_USB:
        ptr[i++] = CFGTOKEN_MUX_USB | 0x010000;
        break;
    case SERIAL_WIFI:
        ptr[i++] = CFGTOKEN_MUX_WIFI | 0x010000;
        break;
    case SERIAL_PRINTER:
        ptr[i++] = CFGTOKEN_MUX_PRN | 0x010000;
        break;
    default:
    case SERIAL_LOOP:
        ptr[i++] = CFGTOKEN_MUX_LOOP | 0x010000;
        break;
    }
    ptr[i++] = CFGTOKEN_SER_BAUD | 0x010000;
    ptr[i++] = baud[1];


    switch(usbmux) {
    case USB_HOST_CDC:
        ptr[i++] = CFGTOKEN_USB_HOST;
        break;
    default:
    case USB_GUEST_CDC:
        ptr[i++] = CFGTOKEN_USB_GUEST;
        break;
    case USB_GUEST_MIDI:
        ptr[i++] = CFGTOKEN_USB_MIDI;
        break;
    }

    switch(wifimode) {
    case WIFI_AP:
        ptr[i++] = CFGTOKEN_WIFI_AP;
        break;
    case WIFI_CLIENT:
        ptr[i++] = CFGTOKEN_WIFI_CL;
        break;
    }

    ptr[i++] = CFGTOKEN_WIFI_SSID | (((uint32_t)strlen(wifi_ssid)+1) << 24);
    strncpy((char*)(&ptr[i]), wifi_ssid, longmin(strlen(wifi_ssid)+1,31));
    i += (strlen(wifi_ssid)+4) >> 2;

    ptr[i++] = CFGTOKEN_WIFI_PSK | (((uint32_t)strlen(wifi_psk)+1) << 24);
    strncpy((char*)(&ptr[i]), wifi_psk, longmin(strlen(wifi_psk)+1,31));
    i += (strlen(wifi_psk)+4) >> 2;

    ptr[i++] = CFGTOKEN_WIFI_IP;
    ptr[i++] = wifi_address;

    ptr[i++] = CFGTOKEN_WIFI_NM;
    ptr[i++] = wifi_netmask;

    ptr[i++] = CFGTOKEN_JD_HOST | (((uint32_t)strlen(jd_host)+1) << 24);
    strncpy((char*)(&ptr[i]), jd_host, longmin(strlen(jd_host)+1,31));
    ptr += (strlen(jd_host)+4) >> 2;

    ptr[i++] = CFGTOKEN_JD_PORT;
    ptr[i++] = jd_port;

    ptr[i++] = NEWCONFIG_EOF_MARKER;

    return i*4;
}

void cfgfile_upload(char *pdfile, uint16_t block) {
    FILE *f;
    size_t bytesread;
    int i;

    backdrop(PROGNAME);
    window(" Please Wait ", 26, 6, 1);
    gotoy(11); gotox(13);
    cputs("Writing file,");
    gotoy(12); gotox(8);
    cputs("your screen may flicker.");
    
    f = fopen(pdfile, "rb");
    if(f == NULL) {
        backdrop(PROGNAME);
        window(" Error ", longmax(28, strlen(pdfile)+2), 7, 1);
        gotoy(11); gotox(7);
        cputs("Unable to open prodos file");
        gotoy(12); gotox(20 - (strlen(pdfile)/2));
        cputs(pdfile);
        ok_button();

        return;
    }

    memset(blockbuffer, 0xff, 4096);
    bytesread = fread(blockbuffer, 1, 4096, f);
    if(bytesread == 0) {
        backdrop(PROGNAME);
        window(" Error ", longmax(28, strlen(pdfile)+2), 7, 1);
        gotoy(11); gotox(7);
        cputs("Unable to read prodos file");
        gotoy(12); gotox(20 - (strlen(pdfile)/2));
        cputs(pdfile);
        ok_button();

        goto cleanup;
    }

    if(cfg_cmd1("fe", block)) {
        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cprintf("Unable to erase block $%4X", block);
        ok_button();

        goto cleanup;
    }

    CF_PTRL = 0;
    CF_PTRH = 0;
    for(i = 0; i < sizeof(blockbuffer); i++) {
        CF_DATW = blockbuffer[i];
    }

    if(cfg_cmd1("fw", block)) {
        ok_button();

        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cprintf("Unable to write block $%4X", block);
        ok_button();

        goto cleanup;
    }

cleanup:
    if(f != NULL)
        fclose(f);
}

void cfgfile_download(char *pdfile, uint16_t block) {
    FILE *f;
    size_t byteswritten;
    int i;

    backdrop(PROGNAME);
    window(" Please Wait ", 26, 6, 1);
    gotoy(11); gotox(13);
    cputs("Reading file,");
    gotoy(12); gotox(8);
    cputs("your screen may flicker.");

    f = fopen(pdfile, "wb");
    if(f == NULL) {
        backdrop(PROGNAME);
        window(" Error ", longmax(28, strlen(pdfile)+2), 7, 1);
        gotoy(11); gotox(7);
        cputs("Unable to open prodos file");
        gotoy(12); gotox(20 - (strlen(pdfile)/2));
        cputs(pdfile);
        ok_button();

        return;
    }

    if(cfg_cmd1("fr", block)) {
        ok_button();

        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cprintf("Unable to read block $%4X", block);
        ok_button();

        goto cleanup;
    }

    CF_PTRL = 0;
    CF_PTRH = 0;
    for(i = 0; i < sizeof(blockbuffer); i++) {
        blockbuffer[i] = CF_DATR;
    }

    byteswritten = fwrite(blockbuffer, 1, sizeof(blockbuffer), f);
    if(byteswritten != sizeof(blockbuffer)) {
        ok_button();

        backdrop(PROGNAME);
        window(" Error ", longmax(30, strlen(pdfile)+2), 7, 1);
        gotoy(11); gotox(6);
        cputs("Unable to write prodos file");
        gotoy(12); gotox(20 - (strlen(pdfile)/2));
        cputs(pdfile);
        ok_button();

        goto cleanup;
    }
    
cleanup:
    if(f != NULL)
        fclose(f);
}

void restore_config() {
    uint16_t last, next;

    // Get current config blocks
    if(cfg_cmd0("fc")) {
        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cputs("Unable to get config block");
        ok_button();
        return;
    }

    next = RPY_BUFFER[5];
    next <<= 8;
    next |= RPY_BUFFER[4];

    last = RPY_BUFFER[3];
    last <<= 8;
    last |= RPY_BUFFER[2];

    if(cfg_cmd1("fe", last)) {
        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cprintf("Unable to erase block $%4X", last);
        ok_button();

        goto cleanup;
    }

    cfgfile_upload("CONFIG.BACKUP", next);

cleanup:
    return;
}

void backup_config() {
    uint16_t last;

    // Get current config blocks
    if(cfg_cmd0("fc")) {
        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cputs("Unable to get config block");
        ok_button();
        goto cleanup;
    }
    
    last = RPY_BUFFER[3];
    last <<= 8;
    last |= RPY_BUFFER[2];

    cfgfile_download("CONFIG.BACKUP", last);

cleanup:
    return;
}

void read_config() {
    int i;
    uint16_t last;

    backdrop(PROGNAME);
    window(" Please Wait ", 26, 6, 1);
    gotoy(11); gotox(13);
    cputs("Reading file,");
    gotoy(12); gotox(8);
    cputs("your screen may flicker.");

    // Get current config blocks
    if(cfg_cmd0("fc")) {
        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cputs("Unable to get config block");
        ok_button();
        goto cleanup;
    }
    
    last = RPY_BUFFER[3];
    last <<= 8;
    last |= RPY_BUFFER[2];

    if(cfg_cmd1("fr", last)) {
        backdrop(PROGNAME);
        window(" Error ", 30, 7, 1);
        gotoy(11); gotox(7);
        cprintf("Unable to read block $%4X", last);
        ok_button();

        goto cleanup;
    }

    CF_PTRL = 0;
    CF_PTRH = 0;
    for(i = 0; i < sizeof(blockbuffer); i++) {
        blockbuffer[i] = CF_DATR;
    }

    parse_config();

cleanup:
    return;
}

int write_config() {
    int i;
    uint16_t next;

    build_config(config_rev+1);

    backdrop(PROGNAME);
    window(" Please Wait ", 26, 6, 1);
    gotoy(11); gotox(10);
    cputs("Saving configuration,");
    gotoy(12); gotox(8);
    cputs("your screen may flicker.");

    // Get current config blocks
    if(cfg_cmd0("fc")) {
        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(7);
        cputs("Unable to get config block");
        ok_button();
        goto cleanup;
    }
    
    next = RPY_BUFFER[5];
    next <<= 8;
    next |= RPY_BUFFER[4];

    if(cfg_cmd1("fe", next)) {
        backdrop(PROGNAME);
        window(" Error ", 32, 7, 1);
        gotoy(11); gotox(6);
        cprintf("Unable to erase block $%4X", next);
        ok_button();

        goto cleanup;
    }

    CF_PTRL = 0;
    CF_PTRH = 0;
    for(i = 0; i < sizeof(blockbuffer); i++) {
        CF_DATW = blockbuffer[i];
    }

    if(cfg_cmd1("fw", next)) {
        ok_button();

        backdrop(PROGNAME);
        window(" Error ", 32, 7, 1);
        gotoy(11); gotox(6);
        cprintf("Unable to write block $%04X", next);
        ok_button();

        goto cleanup;
    }
    return 1;

cleanup:
    return 0;
}

void apply_config(void) {
    timeout = CARD_TIMEOUT;

    backdrop(PROGNAME);
    window(" Please Wait ", 26, 6, 1);
    gotoy(10); gotox(13);
    cputs("Rebooting card");
    gotoy(11); gotox(9);
    cputs("to apply configuration,");
    gotoy(12); gotox(8);
    cputs("your screen may flicker.");

    // Ask card to reboot.
    cfg_cmd0("RB");

    while(timeout--);
}

int format_card(void) {
    uint16_t last, next;

    backdrop(PROGNAME);
    window(" Please Wait ", 26, 6, 1);
    gotoy(11); gotox(12);
    cputs("Erasing configs,");
    gotoy(12); gotox(8);
    cputs("your screen may flicker.");

    // Get current config blocks
    if(cfg_cmd0("fc")) {
        backdrop(PROGNAME);
        window(" Error ", 28, 7, 1);
        gotoy(11); gotox(8);
        cputs("Unable to get config block");
        ok_button();

        goto cleanup;
    }
    
    next = RPY_BUFFER[5];
    next <<= 8;
    next |= RPY_BUFFER[4];

    last = RPY_BUFFER[3];
    last <<= 8;
    last |= RPY_BUFFER[2];
    
    if(last != next) {
        if(cfg_cmd1("fe", last)) {
            backdrop(PROGNAME);
            window(" Error ", 28, 7, 1);
            gotoy(11); gotox(8);
            cprintf("Unable to erase block $%04X", last);
            ok_button();

            goto cleanup;
        }
        if(cfg_cmd1("fe", next)) {
            backdrop(PROGNAME);
            window(" Error ", 28, 7, 1);
            gotoy(11); gotox(8);
            cprintf("Unable to erase block $%04X", next);
            ok_button();

            goto cleanup;
        }
    }

    return 1;
    
cleanup:
    return 0;
}

int baud_menu(char *screen_title, int baudrate) {
    int i, selected_item = 0;

    for(i = 0; i < (sizeof(baudvalue)/sizeof(uint16_t)); i++) {
        if(baudrate == baudvalue[i]) {
            selected_item = i;
        }
    }

    selected_item = list_menu(screen_title, " Baud Rate ", baud_strings, (sizeof(baud_strings)/sizeof(menu_strings_t))-1, selected_item);

    return baudvalue[selected_item];
}

static char tolower(char c) {
    if((c >= 'A') && (c <= 'Z')) {
        return (c-'A')+'a';
    }
    return c;
}

void jdhost_edit(void) {
    char temp_host[32];
    int c, l;
    int paint_menu = 1;
    int y = 12 - 1;
    int x = 7;

    strcpy(temp_host, jd_host);
    l = strlen(temp_host);

    for(;;) {
        if(paint_menu) {
            backdrop("JetDirect");
            window(" Server ", 30, 5, 1);
            gotoy(y+0); gotox(x);
            cputs("Enter hostname or IP:");
            gotoy(y+2); gotox(x);
            cputs("_________________________");
            gotoy(y+2); gotox(x);
            cputs(temp_host);
            paint_menu = 0;
        }

        gotox(x+l);
        cputc(CHAR_CURSOR);
        c = cgetc();
        if(c == 0x08) {
            if(l > 0) {
                // Remove flashing cursor
                gotox(x+l);
                cputc('_');

                // Remove deleted character
                l--;
                gotox(x+l);
                cputc('_');
                temp_host[l] = 0;
            }
        } else if(c == 0x0D) {
            strcpy(jd_host, temp_host);
            return;
        } else if(c == 0x1B) {
            if(confirm(" Are you sure? ", "Go back without saving?")) {
                return;
            }
            paint_menu = 1;
        } else if(((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9')) || (c == '.')) {
            if(l < 24) {
                gotox(x+l);
                cputc(tolower(c));
                temp_host[l] = tolower(c);
                l++;
                temp_host[l] = 0;
            }
        }
    }
}

void jdport_edit(void) {
    char temp_port[8];
    int c, l;
    int paint_menu = 1;
    int y = 12 - 1;
    int x = 15;

    sprintf(temp_port, "%i", jd_port);
    l = strlen(temp_port);

    for(;;) {
        if(paint_menu) {
            backdrop("JetDirect");
            window(" TCP/IP Port ", 16, 5, 1);
            gotoy(y+0); gotox(x);
            cputs("Enter port:");
            gotoy(y+2); gotox(x);
            cputs("______");
            gotoy(y+2); gotox(x);
            cputs(temp_port);
            paint_menu = 0;
        }

        gotox(x+l);
        cputc(CHAR_CURSOR);
        c = cgetc();
        if(c == 0x08) {
            if(l > 0) {
                // Remove flashing cursor
                gotox(x+l);
                cputc('_');

                // Remove deleted character
                l--;
                gotox(x+l);
                cputc('_');
                temp_port[l] = 0;
            }
        } else if(c == 0x0D) {
            jd_port = strtoul(temp_port, NULL, 0);
            return;
        } else if(c == 0x1B) {
            gotox(x+l);
            cputc('_');
            if(confirm(" Are you sure? ", "Go back without saving?")) {
                return;
            }
            paint_menu = 1;
        } else if((c >= '0') && (c <= '9')) {
            if(l < 5) {
                gotox(x+l);
                cputc(c);
                temp_port[l] = c;
                l++;
                temp_port[l] = 0;
            }
        }
    }
}

void ssid_edit(void) {
    char temp_ssid[32];
    int c, l;
    int paint_menu = 1;
    int y = 12 - 1;
    int x = 7;

    strcpy(temp_ssid, wifi_ssid);
    l = strlen(temp_ssid);

    for(;;) {
        if(paint_menu) {
            backdrop("WiFi");
            window(" Network Name (SSID) ", 30, 5, 1);
            gotoy(y+0); gotox(x);
            cputs("Enter network name:");
            gotoy(y+2); gotox(x);
            cputs("_________________________");
            gotoy(y+2); gotox(x);
            cputs(temp_ssid);
            paint_menu = 0;
        }

        gotox(x+l);
        cputc(CHAR_CURSOR);
        c = cgetc();
        if(c == 0x08) {
            if(l > 0) {
                // Remove flashing cursor
                gotox(x+l);
                cputc('_');

                // Remove deleted character
                l--;
                gotox(x+l);
                cputc('_');
                temp_ssid[l] = 0;
            }
        } else if(c == 0x0D) {
            strcpy(wifi_ssid, temp_ssid);
            return;
        } else if(c == 0x1B) {
            gotox(x+l);
            cputc('_');
            if(confirm(" Are you sure? ", "Go back without saving?")) {
                return;
            }
            paint_menu = 1;
        } else if(c >= 32) {
            if(l < 24) {
                gotox(x+l);
                cputc(c);
                temp_ssid[l] = c;
                l++;
                temp_ssid[l] = 0;
            }
        }
    }
}

void psk_edit(void) {
    char temp_psk[32];
    int c, l;
    int paint_menu = 1;
    int y = 12 - 1;
    int x = 7;

    strcpy(temp_psk, wifi_psk);
    l = strlen(temp_psk);

    for(;;) {
        if(paint_menu) {
            backdrop("WiFi");
            window(" Passphrase (PSK) ", 30, 5, 1);
            gotoy(y+0); gotox(x);
            cputs("Enter passphrase:");
            gotoy(y+1); gotox(x);
            cputs("_________________________");
            gotoy(y+1); gotox(x);
            cputs(temp_psk);
            paint_menu = 0;
        }

        gotox(x+l);
        cputc(CHAR_CURSOR);
        c = cgetc();
        if(c == 0x08) {
            if(l > 0) {
                // Remove flashing cursor
                gotox(x+l);
                cputc('_');

                // Remove deleted character
                l--;
                gotox(x+l);
                cputc('_');
                temp_psk[l] = 0;
            }
        } else if(c == 0x0D) {
            strcpy(wifi_psk, temp_psk);
            return;
        } else if(c == 0x1B) {
            gotox(x+l);
            cputc('_');
            if(confirm(" Are you sure? ", "Go back without saving?")) {
                return;
            }
            paint_menu = 1;
        } else if(c >= 32) {
            if(l < 24) {
                gotox(x+l);
                cputc(c);
                temp_psk[l] = c;
                l++;
                temp_psk[l] = 0;
            }
        }
    }
}

int valid_netmask(uint32_t ip) {
    int i;
    uint32_t first_one = 32;
    uint32_t bit_test = 1;
    
    if((ip & 0x80000000) == 0) return 0;

    for(i = 0; i < 31; i++) {
        if(ip & bit_test) { // Bit is set
            if(i < first_one) {
                first_one = i;
            }
        } else { // Bit is clear
            if(i > first_one) { // But we've seen a 1 already
                return 0;
            }
        }
        bit_test <<= 1;
    }

    return 1;
}

int valid_ip_or_dhcp(uint32_t ip) {
    if(ip == 0) return 1;

    if((ip & 0xFF000000) == 0) return 0;
    if(ip == wifi_netmask) return 0;

    if((ip & ~wifi_netmask) == 0) return 0;
    if((ip & ~wifi_netmask) == ~wifi_netmask) return 0;

    return 1;
}

// Parse a decimal dotted quad segment
uint16_t parse_dq(char *ptr, char **ptr_out) {
    uint16_t rv = 0;
    while((*ptr != 0) && (*ptr != '.')) {
        if((*ptr >= '0') && (*ptr <= '9')) {
            rv *= 10;
            rv += *ptr - '0';
        } else {
            *ptr_out = ptr;
            return 0xFFFF;
        }
        ptr++;
    }

    if(*ptr == 0) {
        *ptr_out = NULL;
    } else {
        *ptr_out = ptr;
    }

    return rv;
}

uint32_t address_edit(uint32_t ip, int is_netmask) {
    char temp_address[32];
    int c, l;
    int paint_menu = 1;
    uint16_t temp_ip[4];
    int y = 12 - 1;
    int x = is_netmask ? 12 : 5;
    
    temp_ip[0] = (ip >> 24) & 0xff;
    temp_ip[1] = (ip >> 16) & 0xff;
    temp_ip[2] = (ip >> 8) & 0xff;
    temp_ip[3] = (ip >> 0) & 0xff;

    sprintf(temp_address, "%d.%d.%d.%d", temp_ip[0], temp_ip[1], temp_ip[2], temp_ip[3]);
    l = strlen(temp_address);

    for(;;) {
        if(paint_menu) {
            backdrop("WiFi");
            window(is_netmask ? "Netmask" : "IP Address", is_netmask ? 20 : 32, 5, 1);
            if(is_netmask) {
                gotoy(y+0); gotox(x);
                cputs("Enter netmask:");
            } else {
                gotoy(y+0); gotox(x);
                cputs("Enter IP or 0.0.0.0 for DHCP:");
            }
            gotoy(y+2); gotox(x);
            cputs("________________");
            gotoy(y+2); gotox(x);
            cputs(temp_address);
            paint_menu = 0;
        }

        gotox(x+l);
        cputc(CHAR_CURSOR);
        c = cgetc();
        if(c == 0x08) {
            if(l > 0) {
                // Remove flashing cursor
                gotox(x+l);
                cputc('_');

                // Remove deleted character
                l--;
                gotox(x+l);
                cputc('_');
                temp_address[l] = 0;
            }
        } else if((c == 0x0A) || (c == 0x0D)) {
            char *ptr = temp_address;
            temp_ip[0] = parse_dq(ptr, &ptr);
            if((ptr != NULL) && (*ptr == '.')) {
                ptr++;
                temp_ip[1] = parse_dq(ptr, &ptr);
            }
            if((ptr != NULL) && (*ptr == '.')) {
                ptr++;
                temp_ip[2] = parse_dq(ptr, &ptr);
            }
            if((ptr != NULL) && (*ptr == '.')) {
                ptr++;
                temp_ip[3] = parse_dq(ptr, &ptr);
                if((temp_ip[0] < 256) && (temp_ip[1] < 256) &&
                  (temp_ip[2] < 256) && (temp_ip[3] < 256)) {
                    ip = temp_ip[0];
                    ip <<= 8;
                    ip |= temp_ip[1];
                    ip <<= 8;
                    ip |= temp_ip[2];
                    ip <<= 8;
                    ip |= temp_ip[3];
                    if((is_netmask && valid_netmask(ip)) || (!is_netmask && valid_ip_or_dhcp(ip))) 
                        return ip;
                }
            }
            message(" Error ", "Invalid Address Specified");
            paint_menu = 1;
        } else if(c == 0x1B) {
            backdrop("WiFi");
            if(confirm(" Are you sure? ", "Go back without saving?")) {
                return ip;
            }
            paint_menu = 2;
        } else if(c >= 32) {
            if(l < 15) {
                gotox(x+l);
                cputc(c);
                temp_address[l] = c;
                l++;
                temp_address[l] = 0;
            }
        }
    }
}

int video_menu_action(int action) {
    switch(action) {
        case 0:
            default_font = list_menu("Video Settings", "Default Font", font_strings, (sizeof(font_strings)/sizeof(menu_strings_t))-1, default_font);
            return 2;
        case 1:
            mono_palette = list_menu("Video Settings", "Monochrome Mode", monochrome_strings, (sizeof(monochrome_strings)/sizeof(menu_strings_t))-1, mono_palette);
            return 2;
        case 2:
            terminal_fgcolor = list_menu("Video Settings", "Foreground Color", color_strings, (sizeof(color_strings)/sizeof(menu_strings_t))-1, terminal_fgcolor);
            return 2;
        case 3:
            terminal_bgcolor = list_menu("Video Settings", "Background Color", color_strings, (sizeof(color_strings)/sizeof(menu_strings_t))-1, terminal_bgcolor);
            return 2;
        case 4:
            terminal_border = list_menu("Video Settings", "Border Color", color_strings, (sizeof(color_strings)/sizeof(menu_strings_t))-1, terminal_border);
            return 2;
        case 5:
            return -1;
    }
}

void video_menu(void) {
    int paint_menu = 2;
    int selected_item = 0;
    int y = 12 - 7;

    while(paint_menu >= 0) {
        if(paint_menu == 2) {
            backdrop("Video Settings");
            window(" Video Settings ", 26, 17, 0);
        }
        if(paint_menu > 0) {
            gotoy(y+0); gotox(8);
            print_menu_select_int("Default Font: %6x", default_font, 24, (selected_item == 0), 0);
            gotoy(y+2); gotox(8);
            print_menu_select_str("Monochrome: %8s", monochrome_strings[mono_palette].str, 24, (selected_item == 1), 0);
            gotoy(y+4); gotox(8);
            print_menu_select_str("Foreground: %8s", color_strings[terminal_fgcolor].str, 24, (selected_item == 2), 0);
            gotoy(y+6); gotox(8);
            print_menu_select_str("Background: %8s", color_strings[terminal_bgcolor].str, 24, (selected_item == 3), 0);
            gotoy(y+8); gotox(8);
            print_menu_select_str("Border: %12s", color_strings[terminal_border & 0xf].str, 24, (selected_item == 4), 0);

            gotoy(y+14); gotox(8);
            print_menu_select(" Back to Main Menu", 24, (selected_item == 5), 0);

            paint_menu = 0;
        }
        switch(cgetc()) {
            case 0x08:
            case 0x0B:
                if(selected_item > 0) {
                    selected_item--;
                    paint_menu = 1;
                }
                break;
            case 0x15:
            case 0x0A:
                if(selected_item < 5) {
                    selected_item++;
                    paint_menu = 1;
                }
                break;
            case 0x0D:
                paint_menu = video_menu_action(selected_item);
                break;
            case 0x1B:
                paint_menu = -1;
        }
    }
}

int wifi_menu_action(int action) {
    switch(action) {
        case 0:
            wifimode = (wifimode == WIFI_AP) ? WIFI_CLIENT : WIFI_AP;
            return 1;
        case 1:
            ssid_edit();
            return 2;
        case 2:
            psk_edit();
            return 2;
        case 3:
            if(wifimode != WIFI_AP) {
                wifi_address = address_edit(wifi_address, 0);
                return 2;
            }
            return 0;
        case 4:
            if(wifimode != WIFI_AP) {
                wifi_netmask = address_edit(wifi_netmask, 1);
                return 2;
            }
            return 0;
        case 5:
            return -1;
    }
}

void wifi_menu(void) {
    int paint_menu = 2;
    int selected_item = 0;
    int y = 12 - 7;

    while(paint_menu >= 0) {
        if(paint_menu == 2) {
            backdrop(PROGNAME);
            window(" WiFi Options ", 26, 17, 0);
        }
        if(paint_menu > 0) {
            gotoy(y+0); gotox(8);
            print_menu_select_str("Mode: %14s", wifi_mode_str[wifimode == WIFI_CLIENT].str, 24, (selected_item == 0), 0);

            gotoy(y+2); gotox(8);
            print_menu_select("Network Name:", 24, (selected_item == 1), 0);
            gotoy(y+3); gotox(8);
            print_menu_select_str("%20s", wifi_ssid, 24, (selected_item == 1), 1);

            gotoy(y+5); gotox(8);
            print_menu_select("Pre-Shared Key:", 24, (selected_item == 2), 0);
            gotoy(y+6); gotox(8);
            print_menu_select_str("%20s", "*****", 24, (selected_item == 2), 1);

            gotoy(y+8); gotox(8);
            print_menu_select("IP Address:", 24, (selected_item == 3), 0);
            gotoy(y+9); gotox(8);
            if(wifimode == WIFI_AP) {
                print_menu_select_str("%20s", "10.65.50.1", 24, (selected_item == 3), 1);
            } else if(wifi_address == 0) {
                print_menu_select_str("%20s", "DHCP", 24, (selected_item == 3), 1);
            } else {
                print_menu_select_ip(wifi_address, 24, (selected_item == 3), 1);
            }

            gotoy(y+11); gotox(8);
            print_menu_select("Netmask:", 24, (selected_item == 4), 0);
            gotoy(y+12); gotox(8);
            if(wifimode == WIFI_AP) {
                print_menu_select_str("%20s", "255.255.255.0", 24, (selected_item == 4), 1);
            } else {
                print_menu_select_ip(wifi_netmask, 24, (selected_item == 4), 1);
            }

            gotoy(y+14); gotox(8);
            print_menu_select("Back to Main Menu", 24, (selected_item == 5), 0);

            paint_menu = 0;
        }

        switch(cgetc()) {
            case 0x08:
            case 0x0B:
                if(selected_item > 0) {
                    selected_item--;
                    paint_menu = 1;
                }
                break;
            case 0x15:
            case 0x0A:
                if(selected_item < 5) {
                    selected_item++;
                    paint_menu = 1;
                }
                break;
            case 0x0D:
                paint_menu = wifi_menu_action(selected_item);
                break;
            case 0x1B:
                paint_menu = -1;
        }
    }
}

int io_menu_action(int action) {
    switch(action) {
        case 0:
            serialmux[0] = list_menu("Serial Port 0", " Port Mux ", serialmux_strings, (sizeof(serialmux_strings)/sizeof(menu_strings_t))-1, serialmux[0]);
            return 2;
        case 1:
            baud[0] = baud_menu("Serial Port 0", baud[0]);
            return 2;
        case 2:
            serialmux[1] = list_menu("Serial Port 1", " Port Mux ", serialmux_strings, (sizeof(serialmux_strings)/sizeof(menu_strings_t))-1, serialmux[1]);
            return 2;
        case 3:
            baud[1] = baud_menu("Serial Port 1", baud[1]);
            return 2;
        case 4:
            jdhost_edit();
            return 2;
        case 5:
            jdport_edit();
            return 2;
        case 6:
            return -1;
    }
}

void io_menu(void) {
    int paint_menu = 2;
    int selected_item = 0;
    int y = 12 - 7;

    while(paint_menu >= 0) {
        if(paint_menu == 2) {
            backdrop(PROGNAME);
            window(" I/O Settings ", 26, 17, 0);
        }
        if(paint_menu > 0) {
            gotoy(y+0); gotox(8);
            print_menu_select_str("Serial 0: %10s", serialmux_strings[serialmux[0]].str, 24, (selected_item == 0), 0);
            gotoy(y+1); gotox(8);
            print_menu_select_int(" Baud: %13d", baud[0], 24, (selected_item == 1), 0);

            gotoy(y+3); gotox(8);
            print_menu_select_str("Serial 1: %10s", serialmux_strings[serialmux[1]].str, 24, (selected_item == 2), 0);
            gotoy(y+4); gotox(8);
            print_menu_select_int(" Baud: %13d", baud[1], 24, (selected_item == 3), 0);

            gotoy(y+6); gotox(8);
            print_menu_select("JetDirect Server:", 24, (selected_item == 4), 0);
            gotoy(y+7); gotox(8);
            print_menu_select_str("%20s", jd_host, 24, (selected_item == 4), 1);

            gotoy(y+9); gotox(8);
            print_menu_select("JetDirect Port:", 24, (selected_item == 5), 0);
            gotoy(y+10); gotox(8);
            print_menu_select_int("%20d", jd_port, 24, (selected_item == 5), 1);

            gotoy(y+14); gotox(8);
            print_menu_select("Back to Main Menu", 24, (selected_item == 6), 0);

            paint_menu = 0;
        }

        switch(cgetc()) {
            case 0x08:
            case 0x0B:
                if(selected_item > 0) {
                    selected_item--;
                    paint_menu = 1;
                }
                break;
            case 0x15:
            case 0x0A:
                if(selected_item < 6) {
                    selected_item++;
                    paint_menu = 1;
                }
                break;
            case 0x0D:
                paint_menu = io_menu_action(selected_item);
                break;
            case 0x1B:
                paint_menu = -1;
        }
    }
}

int main_menu_action(int action) {
    switch(action) {
        case 0:
            machine = list_menu(PROGNAME, " Host Type ", machine_strings, (sizeof(machine_strings)/sizeof(menu_strings_t))-1, machine);
            return 2;
        case 1:
            video_menu();
            return 2;
        case 2:
            wifi_menu();
            return 2;
        case 3:
            io_menu();
            return 2;
        case 4:
            backup_config();
            return 2;
        case 5:
            restore_config();
            return 2;
        case 6:
            backdrop(PROGNAME);
            if(confirm(" Are you sure? ", "Format Card?")) {
                format_card();
                default_config();
                return 2;
            }
            return 2;
        case 7:
            backdrop(PROGNAME);
            if(confirm(" Are you sure? ", "Quit without saving?")) {
                clrscr();
                exec("MENU.SYSTEM", "");
                return -1;
            }
            return 2;
        case 8:
            backdrop(PROGNAME);
            if(confirm(" Are you sure? ", "Save and exit?")) {
                write_config();
                clrscr();
                exec("MENU.SYSTEM", "");
                return -1;
            }
            return 2;
   }
}

void main (void) {
    int paint_menu = 2;
    int selected_item = 0;
    int y = 12 - 6;

    if(!prompt_slot(PROGNAME)) {
        goto cleanup;
    }

    switch(get_ostype() & 0xF0) {
        default:
        case 0x10:
            machine = MACHINE_APPLE_II;
            break;
        case 0x30:
            machine = MACHINE_APPLE_IIE;
            break;
        case 0x80:
            machine = MACHINE_APPLE_IIGS;
            break;
    }

    backdrop(PROGNAME);
    window(" Please Wait ", 26, 6, 1);
    gotoy(11); gotox(9);
    cputs("Reading configuration,");
    gotoy(12); gotox(8);
    cputs("your screen may flicker.");

    read_config();

    while(paint_menu >= 0) {
        if(paint_menu == 2) {
            backdrop(PROGNAME);

            window(" Main Menu ", 26, 15, 0);
            gotoy(y+4); gotox(7);
            repeatchar(CHAR_SEPARATOR, 26);
            gotoy(y+9); gotox(7);
            repeatchar(CHAR_SEPARATOR, 26);
        }
        if(paint_menu) {
            gotoy(y+0); gotox(8);
            if(machine < MACHINE_MAXVALUE) {
                print_menu_select_str("Machine: %11s", machine_strings[machine].str, 24, (selected_item == 0), 0);
            } else {
                print_menu_select("Machine:     Unknown", 24, (selected_item == 0), 0);
            }
            gotoy(y+1); gotox(8);
            print_menu_select("Video Settings", 24, (selected_item == 1), 0);
            gotoy(y+2); gotox(8);
            print_menu_select("WiFi Settings", 24, (selected_item == 2), 0);
            gotoy(y+3); gotox(8);
            print_menu_select("I/O Settings", 24, (selected_item == 3), 0);

            gotoy(y+6); gotox(8);
            print_menu_select("Backup config file", 24, (selected_item == 4), 0);
            gotoy(y+7); gotox(8);
            print_menu_select("Restore config file", 24, (selected_item == 5), 0);
            gotoy(y+8); gotox(8);
            print_menu_select("Erase config files", 24, (selected_item == 6), 0);

            gotoy(y+11); gotox(8);
            print_menu_select("Quit without saving", 24, (selected_item == 7), 0);
            gotoy(y+12); gotox(8);
            print_menu_select("Save config & Exit", 24, (selected_item == 8), 0);
            paint_menu = 0;
        }

        switch(cgetc()) {
            case 0x08:
            case 0x0B:
                if(selected_item > 0) {
                    selected_item--;
                    paint_menu = 1;
                }
                break;
            case 0x15:
            case 0x0A:
                if(selected_item < 8) {
                    selected_item++;
                    paint_menu = 1;
                }
                break;
            case 'M':
            case 'm':
                paint_menu = main_menu_action(selected_item = 0);
                break;
            case 'V':
            case 'v':
                paint_menu = main_menu_action(selected_item = 1);
                break;
            case 'W':
            case 'w':
                paint_menu = main_menu_action(selected_item = 2);
                break;
            case 'I':
            case 'i':
                paint_menu = main_menu_action(selected_item = 3);
                break;
            case 'B':
            case 'b':
                paint_menu = main_menu_action(selected_item = 4);
                break;
            case 'R':
            case 'r':
                paint_menu = main_menu_action(selected_item = 5);
                break;
            case 'F':
            case 'f':
                paint_menu = main_menu_action(selected_item = 6);
                break;
            case 0x1B:
            case 'Q':
            case 'q':
                paint_menu = main_menu_action(selected_item = 7);
                break;
            case 'S':
            case 's':
                paint_menu = main_menu_action(selected_item = 8);
                break;
            case 0x0D:
                paint_menu = main_menu_action(selected_item);
                break;
        }
    }

cleanup:
    backdrop(PROGNAME);
    gotoy(12); gotox(13);
    cputs("Launching Menu");

    exec("MENU.SYSTEM", "");
}
