#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "menu.h"
#include "widget.h"
#include "../interface/katconfig.h"
#include "../interface/util.h"
#include "modal.h"
#include "filebrowser.h"
#include "guitop.h"
#include "theme.h"
#include "../interface/input.h"

extern struct theme g_theme;

static unsigned long long mask_general = 0;
static unsigned long long mask_input[4] = {0};

int tmp_bios;
int tmp_basic;
int tmp_ram;
int tmp_tv;
char tmp_disk[80] = {0};

char * machine_get_string(struct option_widget *wgt, char *label)
{
    switch (*(int*)(wgt->data)) {
        case MACHINE_TYPE_5200: strcpy(label, "Atari 5200"); break;
        case MACHINE_TYPE_800: strcpy(label, "Atari 400/800"); break;
        case MACHINE_TYPE_XL: strcpy(label, "Atari XL/XE"); break;
    }
    return label;
}

char * machine_get_os_string(struct option_widget *wgt, char *label)
{
    switch (*(int*)(wgt->data)) {
        case OS_TYPE_5200_2PORT: strcpy(label, "2-Port"); break;
        case OS_TYPE_5200_4PORT: strcpy(label, "4-Port"); break;
        case OS_TYPE_A: strcpy(label, "OS A (400/800)"); break;
        case OS_TYPE_B: strcpy(label, "OS B (400/800)"); break;
        case OS_TYPE_B_NTSC: strcpy(label, "OS B NTSC (400/800)"); break;
        case OS_TYPE_B_PAL: strcpy(label, "OS B PAL (400/800)"); break;
        case OS_TYPE_REV_10: strcpy(label, "Rev 10 (1200XL)"); break;
        case OS_TYPE_REV_11: strcpy(label, "Rev 11 (1200XL)"); break;
        case OS_TYPE_REV_1: strcpy(label, "Rev 1 (600XL)"); break;
        case OS_TYPE_REV_2: strcpy(label, "Rev 2 (XL/XE)"); break;
        case OS_TYPE_REV_3: strcpy(label, "Rev 3 (800XE)"); break;
        case OS_TYPE_REV_4: strcpy(label, "Rev 4 (XEGS)"); break;
    }
    return label;
}

char * basic_get_string(struct option_widget *wgt, char *label)
{
    switch (*(int*)(wgt->data)) {
        case BASIC_TYPE_REV_A: strcpy(label, "Rev A (400/800)"); break;
        case BASIC_TYPE_REV_B: strcpy(label, "Rev B (XL)"); break;
        case BASIC_TYPE_REV_C: strcpy(label, "Rev C (XL/XE)"); break;
    }
    return label;
}

/*
 * Resolve the live heap pointer for a config file-path field by its config
 * mask.  These strings are freed/realloc'd whenever the config is reloaded
 * (machine_change_type, machine_set_os, machine_set_basic, ...), so widgets
 * must never cache the pointer value -- always read it live through here.
 */
static char *machine_live_path(unsigned long long mask)
{
    t_config *cfg = config_get_global_cfg();

    switch (mask) {
        case KCFG_GENERAL_ROM:   return cfg->general.rom;
        case KCFG_GENERAL_BASIC: return cfg->general.basic;
        case KCFG_MACHINE_BIOS:  return cfg->machine.bios;
    }
    return NULL;
}

/* get_string callback for the ROM/BASIC/BIOS file widgets.  Reads the live
   config pointer every frame so a config reload can never leave a dangling
   pointer cached in wgt->data (use-after-free). */
char *machine_get_path_string(struct option_widget *wgt, char *label)
{
    char *p = machine_live_path(wgt->mask_value);

    if (p) {
        strncpy(label, p, 127);
        label[127] = '\0';
    }
    else
        label[0] = '\0';

    return label;
}

void machine_set_os(struct option_widget *wgt, void *data)
{
    (void)data;
    (void)wgt;

    t_config *cfg = config_get_global_cfg();
    util_alloc_string(&(cfg->machine.bios_type), config_get_os_type_string(tmp_bios));
    int result = config_load_os_file (config_get_global_table());
    config_load_os_file (config_get_rom_table());
    if (!result)
        mask_general |= KCFG_MACHINE_BIOS;
    config_refresh_rom_source_masks();
}

void machine_set_basic(struct option_widget *wgt, void *data)
{
    (void)data;
    (void)wgt;

    t_config *cfg = config_get_global_cfg();
    util_alloc_string(&(cfg->general.basic_type),
                      config_get_basic_type_string(tmp_basic));
    int result = config_load_basic_file (config_get_global_table());
    config_load_basic_file (config_get_rom_table());
    if (!result)
        mask_general |= KCFG_GENERAL_BASIC;
    config_refresh_rom_source_masks();
}

void machine_save_all_config(struct option_widget *, void *data)
{
    t_config *cfg = config_get_global_cfg();

    (void)data;
    config_save_general(config_get_global_table(), mask_general);
    mask_general = 0;
    config_save_game_input (cfg->general.machine, config_get_global_table(),
        "All", mask_input, 0 );
    memset(mask_input, 0, sizeof(mask_input));
    input_set_controls();
    guitop_show_saved_toast("Settings saved.");
}

void machine_save_rom_config(struct option_widget *, void *data)
{
    t_config *cfg = config_get_global_cfg();

    (void)data;
    if (!config_ensure_rom_table()) {
        guitop_show_toast_msg("No ROM loaded.");
        return;
    }

    config_save_general(config_get_rom_table(), mask_general);
    mask_general = 0;
    config_save_game_input (cfg->general.machine, config_get_rom_table(),
        "this", mask_input, 0 );
    memset(mask_input, 0, sizeof(mask_input));
    config_refresh_rom_source_masks();
    input_set_controls();
    guitop_show_toast_msg("Saved for this ROM.");
}

static void machine_add_pot_slider(struct option_widget *wid_ary, int *index,
                                   const char *label, int *data,
                                   unsigned long long mask, int min, int max,
                                   int divider)
{
    ++(*index);
    wid_ary[*index].widget_type = WIDGET_TYPE_SLIDER;
    strcpy(wid_ary[*index].label, label);
    wid_ary[*index].data = data;
    wid_ary[*index].mask_value = mask;
    wid_ary[*index].min = min;
    wid_ary[*index].max = max;
    wid_ary[*index].divider = divider;
    wid_ary[*index].get_string = NULL;
    wid_ary[*index].run_task = NULL;
}

void machine_change_type(struct option_widget *, void *data)
{
    (void)data;

    config_save_general(config_get_global_table(), KCFG_GENERAL_MACHINE);
    config_load_general(config_get_global_table());
    config_refresh_rom_source_masks();
}

char * machine_get_ram_string(struct option_widget *wgt, char *label)
{
    t_config *cfg = config_get_global_cfg();
    (void)wgt;

    if (cfg->general.machine == MACHINE_TYPE_800) {
        switch(tmp_ram) {
            case 1: cfg->machine.ram = 16; break;
            case 2: cfg->machine.ram = 48; break;
            case 3: cfg->machine.ram = 52; break;
        }
    }
    else {
        switch(tmp_ram) {
            case 1: cfg->machine.ram = 16; break;
            case 2: cfg->machine.ram = 64; break;
            case 3: cfg->machine.ram = 128; break;
        }
    }

    sprintf(label, "%d KB", cfg->machine.ram);
    return label;
}

void machine_set_ram_value(struct option_widget *, void *data)
{
    t_config *cfg = config_get_global_cfg();

    if (cfg->general.machine == MACHINE_TYPE_800) {
        switch(*(int *)(data)) {
            case 1: cfg->machine.ram = 16; break;
            case 2: cfg->machine.ram = 48; break;
            case 3: cfg->machine.ram = 52; break;
        }
    }
    else {
        switch(*(int *)(data)) {
            case 1: cfg->machine.ram = 16; break;
            case 2: cfg->machine.ram = 64; break;
            case 3: cfg->machine.ram = 128; break;
        }
    }
}

char * machine_get_tv_string(struct option_widget *wgt, char *label)
{
    if (*(int *)(wgt->data) == 1) {
        strcpy(label, "PAL");
    }
    else {
        strcpy(label, "NTSC");
    }
    return label;
}

void machine_set_tv_value(struct option_widget *, void *data)
{
    t_config *cfg = config_get_global_cfg();

    if (*(int*)(data) == 1)
        cfg->machine.tv_standard = PAL;
    else
        cfg->machine.tv_standard = NTSC;

    /*
     * The logical "rev_b" OS resolves to a physical ntsc/pal slot by TV
     * standard, so re-resolve and reload the BIOS file when it changes.
     */
    if (config_load_os_file (config_get_global_table()) == 0) {
        config_load_os_file (config_get_rom_table());
        mask_general |= KCFG_MACHINE_BIOS;
    }
}

void machine_set_load_result(struct modal_config *cfg) 
{
    t_config *p_cfg = config_get_global_cfg();

    if (!cfg->result)
        return;

    switch(cfg->id) {
        case KCFG_GENERAL_BASIC:
            util_alloc_string(&p_cfg->general.basic, filebrowser_result());
            /* Persist the picked file into the active bios.basic.{rev} slot so
               a later config_load_basic_file can't overwrite it from a stale
               slot. */
            config_save_basic(config_get_basic_type_from_string(p_cfg->general.basic_type),
                              filebrowser_result());
            break;
        case KCFG_GENERAL_ROM:
            util_alloc_string(&p_cfg->general.rom, filebrowser_result());
            mask_general |= KCFG_GENERAL_ROM;
            break;
        case KCFG_MACHINE_BIOS:
            util_alloc_string(&p_cfg->machine.bios, filebrowser_result());
            mask_general |= KCFG_MACHINE_BIOS;
            break;
    }

    /* No widget re-sync needed: the ROM/BASIC/BIOS widgets read the live
       config pointer via machine_get_path_string(). */
}

void menu_open_file_browser(struct option_widget *wgt, void *data)
{
    (void)data;
    struct modal_config *cfg = guitop_get_modal_cfg();
    char *live = machine_live_path(wgt->mask_value);

    memset(cfg, 0, sizeof(struct modal_config));
    /* Seed the browser with the current path.  ROM/BASIC/BIOS resolve live so
       a reload can't hand us a freed pointer; the Disk widget keeps its static
       tmp_disk buffer (mask 0 -> live == NULL). */
    cfg->data = live ? live : wgt->data;
    cfg->modal_type = MODAL_FB_LOAD;
    cfg->modal_active = 2;
    cfg->id = wgt->mask_value;
    cfg->msg_type = 0;
    strcpy(cfg->title, "Load File");
    cfg->post_show = machine_set_load_result;

    filebrowser_init(cfg);
}

char * machine_get_port_string(struct option_widget *wgt, char *label)
{
    switch (*(int*)(wgt->data)) {
        case CTRLR_TYPE_NONE: strcpy(label, "None"); break;
        case CTRLR_TYPE_JOYSTICK: strcpy(label, "Joystick"); break;
        case CTRLR_TYPE_TRACKBALL: strcpy(label, "Trackball"); break;
        case CTRLR_TYPE_PADDLES: strcpy(label, "Paddles"); break;
        case CTRLR_TYPE_KEYPAD: strcpy(label, "Keypad"); break;
    }
    return label;
}

int menu_options_machine(struct option_widget *wid_ary)
{
    t_config *cfg = config_get_global_cfg();
    int i;

    for (i = 0; i < MAX_OPTIONS; i++) {
        wid_ary[i].widget_type = WIDGET_TYPE_LABEL;
        wid_ary[i].data_type = WIDGET_DATA_TYPE_INT;
        wid_ary[i].menu_prev = KAT_MENU_MAIN;
        wid_ary[i].menu_next = 0;
        wid_ary[i].mask_value = 0;
        wid_ary[i].p_mask = &mask_general;
        wid_ary[i].p_source_mask = &cfg->general_mask;
        wid_ary[i].divider = 0;
        wid_ary[i].data = NULL;
        wid_ary[i].get_string = NULL;
        wid_ary[i].run_task = NULL;
    }

    // Machine
    wid_ary[0].widget_type = WIDGET_TYPE_SELECT;
    strcpy(wid_ary[0].label, "System");
    wid_ary[0].min = 1; wid_ary[0].max = 3;
    wid_ary[0].data = &cfg->general.machine;
    wid_ary[0].mask_value = KCFG_GENERAL_MACHINE;
    wid_ary[0].get_string = machine_get_string;
    wid_ary[0].run_task = machine_change_type;

    // Cart File
    wid_ary[1].widget_type = WIDGET_TYPE_STRING;
    strcpy(wid_ary[1].label, "Cartridge");
    wid_ary[1].data = NULL;
    wid_ary[1].mask_value = KCFG_GENERAL_ROM;
    wid_ary[1].get_string = machine_get_path_string;
    wid_ary[1].run_task = menu_open_file_browser;

    // OS Type
    tmp_bios = config_get_os_type_from_string(cfg->machine.bios_type);
    wid_ary[2].widget_type = WIDGET_TYPE_SELECT;
    strcpy(wid_ary[2].label, "OS");
    if (cfg->general.machine == MACHINE_TYPE_5200) {
        wid_ary[2].min = OS_TYPE_5200_4PORT; wid_ary[2].max = OS_TYPE_5200_2PORT;
    }
    else if (cfg->general.machine == MACHINE_TYPE_800) {
        wid_ary[2].min = OS_TYPE_A; wid_ary[2].max = OS_TYPE_B;
    }
    else {
        wid_ary[2].min = OS_TYPE_REV_10; wid_ary[2].max = OS_TYPE_REV_4;
    }
    wid_ary[2].data = &tmp_bios;
    wid_ary[2].mask_value = KCFG_MACHINE_BIOS_TYP;
    wid_ary[2].get_string = machine_get_os_string;
    wid_ary[2].run_task = machine_set_os;

    i = 2;

    if (cfg->general.machine != MACHINE_TYPE_5200) {

        // RAM
        switch(cfg->machine.ram) {
            case 16: tmp_ram = 1; break;
            case 48: tmp_ram = 2; break;
            case 52: tmp_ram = 3; break;
            case 64: tmp_ram = 2; break;
            case 128: tmp_ram = 3; break;
        }
        wid_ary[++i].widget_type = WIDGET_TYPE_SELECT;
        strcpy(wid_ary[i].label, "RAM");
        wid_ary[i].min = 1; wid_ary[i].max = 3;
        wid_ary[i].data = &tmp_ram;
        wid_ary[i].mask_value = KCFG_MACHINE_RAM;
        wid_ary[i].get_string = machine_get_ram_string;
        wid_ary[i].run_task = machine_set_ram_value;

        // TV Encoding
        if (cfg->machine.tv_standard == NTSC)
            tmp_tv = 2;
        else
            tmp_tv = 1;
        wid_ary[++i].widget_type = WIDGET_TYPE_SELECT;
        strcpy(wid_ary[i].label, "TV Encoding");
        wid_ary[i].min = 1; wid_ary[i].max = 2;
        wid_ary[i].data = &tmp_tv;
        wid_ary[i].mask_value = KCFG_MACHINE_TV_STD;
        wid_ary[i].get_string = machine_get_tv_string;
        wid_ary[i].run_task = machine_set_tv_value;

        // SIO Patch
        wid_ary[++i].widget_type = WIDGET_TYPE_TOGGLE;
        strcpy(wid_ary[i].label, "Enable SIO Patch");
        wid_ary[i].data = &cfg->general.sio_patch;
        wid_ary[i].mask_value = KCFG_GENERAL_SIOP_EN;
        wid_ary[i].get_string = NULL;
        wid_ary[i].run_task = NULL;

        // Basic
        wid_ary[++i].widget_type = WIDGET_TYPE_TOGGLE;
        strcpy(wid_ary[i].label, "Enable BASIC");
        wid_ary[i].data = &cfg->general.basic_enable;
        wid_ary[i].mask_value = KCFG_GENERAL_BASIC_EN;
        wid_ary[i].get_string = NULL;
        wid_ary[i].get_string = NULL;
        wid_ary[i].run_task = NULL;

        // Basic Rev
        tmp_basic = config_get_basic_type_from_string(cfg->general.basic_type);
        wid_ary[++i].widget_type = WIDGET_TYPE_SELECT;
        strcpy(wid_ary[i].label, "BASIC Rev");
        wid_ary[i].min = BASIC_TYPE_REV_A; wid_ary[i].max = BASIC_TYPE_REV_C;
        wid_ary[i].data = &tmp_basic;
        wid_ary[i].mask_value = KCFG_GENERAL_BASIC_TYP;
        wid_ary[i].get_string = basic_get_string;
        wid_ary[i].run_task = machine_set_basic;

        // Basic File
        wid_ary[++i].widget_type = WIDGET_TYPE_STRING;
        strcpy(wid_ary[i].label, "BASIC File");
        wid_ary[i].data = NULL;
        wid_ary[i].mask_value = KCFG_GENERAL_BASIC;
        wid_ary[i].get_string = machine_get_path_string;
        wid_ary[i].run_task = menu_open_file_browser;

        // Disk File
        wid_ary[++i].widget_type = WIDGET_TYPE_STRING;
        strcpy(wid_ary[i].label, "Disk File");
        wid_ary[i].data = tmp_disk;
        wid_ary[i].mask_value = 0;
        wid_ary[i].get_string = NULL;
        wid_ary[i].run_task = menu_open_file_browser;
    }

    // BIOS File
    wid_ary[++i].widget_type = WIDGET_TYPE_STRING;
    strcpy(wid_ary[i].label, "BIOS File");
    wid_ary[i].data = NULL;
    wid_ary[i].mask_value = KCFG_MACHINE_BIOS;
    wid_ary[i].get_string = machine_get_path_string;
    wid_ary[i].run_task = menu_open_file_browser;

    for (int j=0; j < 4; ++j) {

        wid_ary[++i].widget_type = WIDGET_TYPE_SELECT;
        sprintf(wid_ary[i].label, "Port %d", j+1);
        if (cfg->general.machine == MACHINE_TYPE_5200) {
            wid_ary[i].min = 0; wid_ary[i].max = 2;
        }
        else {
            wid_ary[i].min = 0; wid_ary[i].max = 4;
        }
        wid_ary[i].data = &cfg->input.game.players[j].type;
        wid_ary[i].mask_value = KCFG_INP_TYPE;
        wid_ary[i].p_mask = &mask_input[j];
        wid_ary[i].p_source_mask = &cfg->input_mask[j];
        wid_ary[i].get_string = machine_get_port_string;
        wid_ary[i].run_task = NULL;
    }

    // Pokey POT Values
    wid_ary[++i].widget_type = WIDGET_TYPE_LABEL;
    strcpy(wid_ary[i].label, "Pokey POT Values");
    wid_ary[i].data = NULL;
    wid_ary[i].menu_next = KAT_MENU_OPTIONS_MACHINE_POKEY_POTS;
    wid_ary[i].divider = 1;
    wid_ary[i].p_mask = NULL;
    wid_ary[i].p_source_mask = NULL;
    wid_ary[i].get_string = NULL;
    wid_ary[i].run_task = NULL;

    // Save
    wid_ary[++i].widget_type = WIDGET_TYPE_LABEL;
    strcpy(wid_ary[i].label, "  Save");
    wid_ary[i].data = NULL;
    wid_ary[i].divider = 1;
    wid_ary[i].p_mask = NULL;
    wid_ary[i].p_source_mask = NULL;
    wid_ary[i].get_string = NULL;
    wid_ary[i].run_task = machine_save_all_config;

    // Save for ROM
    wid_ary[++i].widget_type = WIDGET_TYPE_LABEL;
    strcpy(wid_ary[i].label, "  Save for ROM only [*]");
    wid_ary[i].data = NULL;
    wid_ary[i].divider = 0;
    wid_ary[i].p_mask = NULL;
    wid_ary[i].p_source_mask = NULL;
    wid_ary[i].get_string = NULL;
    wid_ary[i].run_task = machine_save_rom_config;

    return i+1;
}

int menu_options_machine_pokey_pots(struct option_widget *wid_ary)
{
    t_config *cfg = config_get_global_cfg();
    int i;

    for (i = 0; i < MAX_OPTIONS; i++) {
        wid_ary[i].widget_type = WIDGET_TYPE_LABEL;
        wid_ary[i].data_type = WIDGET_DATA_TYPE_INT;
        wid_ary[i].menu_prev = KAT_MENU_OPTIONS_MACHINE;
        wid_ary[i].menu_next = 0;
        wid_ary[i].mask_value = 0;
        wid_ary[i].p_mask = &mask_general;
        wid_ary[i].p_source_mask = &cfg->general_mask;
        wid_ary[i].divider = 0;
        wid_ary[i].data = NULL;
        wid_ary[i].get_string = NULL;
        wid_ary[i].run_task = NULL;
    }

    i = -1;
    machine_add_pot_slider(wid_ary, &i, "Joystick HPOT Center",
            &cfg->machine.joystick_hpot_center, KCFG_MACHINE_JOY_H_CTR, 0, 227, 0);
    machine_add_pot_slider(wid_ary, &i, "Joystick VPOT Center",
            &cfg->machine.joystick_vpot_center, KCFG_MACHINE_JOY_V_CTR, 0, 227, 0);
    machine_add_pot_slider(wid_ary, &i, "Joystick HPOT Low",
            &cfg->machine.joystick_hpot_low, KCFG_MACHINE_JOY_H_LOW, 0, 227, 0);
    machine_add_pot_slider(wid_ary, &i, "Joystick HPOT High",
            &cfg->machine.joystick_hpot_high, KCFG_MACHINE_JOY_H_HIGH, 0, 227, 0);
    machine_add_pot_slider(wid_ary, &i, "Joystick VPOT Low",
            &cfg->machine.joystick_vpot_low, KCFG_MACHINE_JOY_V_LOW, 0, 227, 0);
    machine_add_pot_slider(wid_ary, &i, "Joystick VPOT High",
            &cfg->machine.joystick_vpot_high, KCFG_MACHINE_JOY_V_HIGH, 0, 227, 0);

    if (cfg->general.machine == MACHINE_TYPE_5200) {
        machine_add_pot_slider(wid_ary, &i, "Trackball HPOT Center",
                &cfg->machine.trackball_hpot_center, KCFG_MACHINE_TRK_H_CTR, 0, 227, 1);
        machine_add_pot_slider(wid_ary, &i, "Trackball VPOT Center",
                &cfg->machine.trackball_vpot_center, KCFG_MACHINE_TRK_V_CTR, 0, 227, 0);
        machine_add_pot_slider(wid_ary, &i, "Trackball DPOT Min",
                &cfg->machine.trackball_dpot_min, KCFG_MACHINE_TRK_DPOT_MIN, 0, 10, 0);
        machine_add_pot_slider(wid_ary, &i, "Trackball DPOT Max",
                &cfg->machine.trackball_dpot_max, KCFG_MACHINE_TRK_DPOT_MAX, 0, 100, 0);
    }

    // Save
    wid_ary[++i].widget_type = WIDGET_TYPE_LABEL;
    strcpy(wid_ary[i].label, "  Save");
    wid_ary[i].data = NULL;
    wid_ary[i].divider = 1;
    wid_ary[i].p_mask = NULL;
    wid_ary[i].p_source_mask = NULL;
    wid_ary[i].get_string = NULL;
    wid_ary[i].run_task = machine_save_all_config;

    // Save for ROM
    wid_ary[++i].widget_type = WIDGET_TYPE_LABEL;
    strcpy(wid_ary[i].label, "  Save for ROM only [*]");
    wid_ary[i].data = NULL;
    wid_ary[i].divider = 0;
    wid_ary[i].p_mask = NULL;
    wid_ary[i].p_source_mask = NULL;
    wid_ary[i].get_string = NULL;
    wid_ary[i].run_task = machine_save_rom_config;

    return i+1;
}
